จะตั้งค่าเริ่มต้นใน Rails ได้อย่างไร?


108

ฉันพยายามหาวิธีที่ดีที่สุดในการตั้งค่าเริ่มต้นสำหรับวัตถุใน Rails

สิ่งที่ดีที่สุดที่ฉันคิดได้คือการตั้งค่าเริ่มต้นในnewวิธีการในคอนโทรลเลอร์

ใครมีข้อมูลเข้าบ้างไหมว่าสิ่งนี้ยอมรับได้หรือมีวิธีที่ดีกว่านี้ไหม


1
วัตถุเหล่านี้คืออะไร บริโภค / ใช้อย่างไร? พวกเขาใช้ในขณะแสดงมุมมองหรือตรรกะคอนโทรลเลอร์หรือไม่
Gishu

3
หากคุณกำลังพูดถึงวัตถุ ActiveRecord ฉันต้องบอกคุณว่าไม่มีวิธีแก้ปัญหาที่ดีสำหรับปัญหา 'ค่าเริ่มต้น' มีเพียงแฮ็กที่บ้าคลั่งและผู้เขียนทางรถไฟดูเหมือนจะไม่คิดว่าคุณลักษณะนี้คุ้มค่า (น่าทึ่งเพราะมีเพียงชุมชนรางเท่านั้น .. )
Mauricio

เนื่องจากคำตอบที่ได้รับการยอมรับและส่วนใหญ่มุ่งเน้นไปที่ ActiveRecords เราจึงถือว่าคำถามเดิมเกี่ยวกับ AcitveRecords จึงเป็นไปได้ที่จะซ้ำกันของstackoverflow.com/questions/328525/…
Ciro Santilli 郝海东冠状病六四事件法轮功

คำตอบ:


98

"ถูกต้อง" เป็นคำอันตรายใน Ruby โดยปกติจะมีวิธีทำอะไรได้มากกว่าหนึ่งวิธี ถ้าคุณรู้ว่าคุณจะเสมอต้องการค่าเริ่มต้นที่สำหรับคอลัมน์ที่บนโต๊ะที่ตั้งค่าไว้ในแฟ้มการโยกย้ายฐานข้อมูลเป็นวิธีที่ง่ายที่สุด:

class SetDefault < ActiveRecord::Migration
  def self.up
    change_column :people, :last_name, :type, :default => "Doe"
  end

  def self.down
    # You can't currently remove default values in Rails
    raise ActiveRecord::IrreversibleMigration, "Can't remove the default"
  end
end

เนื่องจาก ActiveRecord จะค้นหาคุณสมบัติตารางและคอลัมน์ของคุณโดยอัตโนมัติสิ่งนี้จะทำให้ค่าเริ่มต้นเดียวกันถูกตั้งค่าในโมเดลใด ๆ โดยใช้ในแอป Rails มาตรฐานใด ๆ

อย่างไรก็ตามหากคุณต้องการเฉพาะค่าเริ่มต้นที่กำหนดในบางกรณีเช่นเป็นโมเดลที่สืบทอดมาซึ่งแชร์ตารางกับคนอื่น ๆ จากนั้นอีกวิธีหนึ่งที่สวยงามคือทำโดยตรงในรหัส Rails ของคุณเมื่อสร้างวัตถุโมเดล:

class GenericPerson < Person
  def initialize(attributes=nil)
    attr_with_defaults = {:last_name => "Doe"}.merge(attributes)
    super(attr_with_defaults)
  end
end

จากนั้นเมื่อคุณทำ a GenericPerson.new()มันจะปล่อยแอตทริบิวต์ "Doe" ขึ้นมาเสมอPerson.new()เว้นแต่คุณจะแทนที่ด้วยอย่างอื่น


2
ลองมัน. มันจะทำงานกับวัตถุโมเดลใหม่ที่เรียกด้วย.newเมธอดคลาส การสนทนาของโพสต์ในบล็อกเกี่ยวกับการเรียกใช้ ActiveRecord โดยตรง.allocateนั้นเกี่ยวกับวัตถุโมเดลที่โหลดด้วยข้อมูลที่มีอยู่จากฐานข้อมูล ( และเป็นความคิดที่แย่มากสำหรับ ActiveRecord ที่จะทำงานแบบนั้น IMO แต่นั่นก็อยู่ข้างประเด็นนี้)
SFEley

2
หากคุณย้อนกลับไปอ่านคำถามของผู้โพสต์ต้นฉบับอีกครั้ง Nikita และจากนั้นแสดงความคิดเห็นของฉันตามลำดับอาจเหมาะสมกับคุณมากขึ้น ถ้าไม่ ... คำถามได้รับคำตอบแล้ว ขอให้มีความสุขในวันนี้
SFEley

8
ดีกว่าสำหรับการโยกย้ายลง: change_column_default :people, :last_name, nil stackoverflow.com/a/1746246/483520
Nolan Amy

9
โปรดทราบว่าสำหรับรางรุ่นที่ใหม่กว่าคุณต้องมีพารามิเตอร์ประเภทเพิ่มเติมก่อนพารามิเตอร์เริ่มต้น ค้นหา: พิมพ์ในหน้านี้
Ben Wheeler

3
@JoelBrewer วันนี้ฉันเจอสิ่งนี้ - สำหรับ Rails 4 คุณต้องระบุประเภทคอลัมน์ด้วย:change_column :people, :last_name, :string, default: "Doe"
GoBusto

56

จากคำตอบของ SFEley นี่คือการปรับปรุง / แก้ไขสำหรับ Rails เวอร์ชันใหม่:

class SetDefault < ActiveRecord::Migration
  def change
    change_column :table_name, :column_name, :type, default: "Your value"
  end
end

2
ระวังสิ่งนี้ใช้ไม่ได้กับ Rails 4.2.x (ไม่ได้ทดสอบในเวอร์ชันที่ใหม่กว่า) เนื่องจากchange_columnสามารถ "จัดโครงสร้าง" ได้ค่อนข้างมากการดำเนินการย้อนกลับไม่สามารถอนุมานได้ หากคุณไม่แน่ใจให้ทดสอบโดยการเรียกใช้db:migrateและdb:rollbackทันทีหลังจากนั้น คำตอบที่ได้รับการยอมรับว่าเป็นผลลัพธ์เดียวกัน แต่อย่างน้อยก็ถือว่า!
gfd

1
นี่คือคำตอบที่ถูกต้องสำหรับฉัน .. มันใช้ได้กับราง 5.1 แต่คุณจะต้องทำupและdownเหมือนที่อธิบายไว้ข้างต้นจาก @GoBusto
Fabrizio

22

ก่อนอื่นคุณไม่สามารถโอเวอร์โหลดได้initialize(*args)เนื่องจากไม่ได้เรียกใช้ในทุกกรณี

ตัวเลือกที่ดีที่สุดคือใส่ค่าเริ่มต้นในการย้ายข้อมูลของคุณ:

add_column :accounts, :max_users, :integer, :default => 10

ประการที่สองที่ดีที่สุดคือการวางค่าเริ่มต้นลงในโมเดลของคุณ แต่จะใช้ได้เฉพาะกับแอตทริบิวต์ที่เป็นศูนย์เริ่มต้นเท่านั้น คุณอาจมีปัญหาเหมือนกับที่ฉันทำกับbooleanคอลัมน์:

def after_initialize
  if new_record?
    max_users ||= 10
  end
end

คุณต้องใช้new_record?ค่าเริ่มต้นจึงไม่แทนที่ค่าที่โหลดจากฐานข้อมูล

คุณต้อง||=หยุด Rails จากการแทนที่พารามิเตอร์ที่ส่งผ่านไปยังวิธีการเริ่มต้น


12
หมายเหตุเล็กน้อย - คุณต้องการทำสองสิ่ง: .... 1) อย่าเรียกเมธอดของคุณ after_initialize คุณต้องการafter_initiation :your_method_name.... 2 ใช้self.max_users ||= 10
Jesse Wolgamott

5
สำหรับบูลีนให้ทำสิ่งนี้: prop = true if prop.nil?
Francis Potter

2
หรือafter_initialize doแทนที่จะเป็นdef after_initialize
fotanus

self.max_users ให้ปลอดภัย
Ken Ratanachai S.

15

คุณยังสามารถลองchange_column_defaultในการย้ายข้อมูลของคุณ (ทดสอบใน Rails 3.2.8):

class SetDefault < ActiveRecord::Migration
  def up
    # Set default value
    change_column_default :people, :last_name, "Smith"
  end

  def down
    # Remove default
    change_column_default :people, :last_name, nil
  end
end

change_column_default เอกสาร Rails API


1
คำตอบที่ยอมรับนั้นสมบูรณ์กว่า แต่ฉันชอบคำตอบที่สะอาดและมีเอกสาร ... ขอบคุณ!
gfd

7

หากคุณอ้างถึงวัตถุ ActiveRecord คุณมี (มากกว่า) สองวิธีในการดำเนินการนี้:

1. ใช้พารามิเตอร์ a: default ใน DB

เช่น

class AddSsl < ActiveRecord::Migration
  def self.up
    add_column :accounts, :ssl_enabled, :boolean, :default => true
  end

  def self.down
    remove_column :accounts, :ssl_enabled
  end
end

ข้อมูลเพิ่มเติมที่นี่: http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

2. ใช้การโทรกลับ

เช่น before_validation_on_create

ข้อมูลเพิ่มเติมที่นี่: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M002147


2
ไม่ใช่ปัญหาในการใช้ค่าเริ่มต้นในฐานข้อมูลที่พวกเขาไม่ได้รับการตั้งค่าจนกว่าวัตถุจะถูกบันทึกหรือไม่? ถ้าฉันต้องการสร้างออบเจ็กต์ใหม่โดยมีการเติมค่าเริ่มต้นฉันต้องตั้งค่าในตัวเริ่มต้น
Rafe

บูลีนไม่สามารถเริ่มต้นเป็น 1 หรือ 0 ได้ - ต้องตั้งค่าเป็นจริงหรือเท็จ (ดูคำตอบของ Silasj)
Jamon Holmgren

@JamonHolmgren ขอบคุณสำหรับคำพูดฉันแก้ไขคำตอบแล้ว :)
Vlad Zloteanu

ไม่มีปัญหา @VladZloteanu
Jamon Holmgren

7

ใน Ruby on Rails v3.2.8 โดยใช้การafter_initializeเรียกกลับ ActiveRecord คุณสามารถเรียกใช้เมธอดในโมเดลของคุณที่จะกำหนดค่าเริ่มต้นสำหรับอ็อบเจ็กต์ใหม่

after_initialize callback ถูกทริกเกอร์สำหรับแต่ละอ็อบเจ็กต์ที่พบและสร้างอินสแตนซ์โดย finder โดย after_initialize จะถูกทริกเกอร์หลังจากอ็อบเจ็กต์ใหม่ถูกสร้างอินสแตนซ์เช่นกัน ( ดูการเรียกกลับ ActiveRecord )

ดังนั้น IMO ควรมีลักษณะดังนี้:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    # required to check an attribute for existence to weed out existing records
    self.bar = default_value unless self.attribute_whose_presence_has_been_validated
  end
end

Foo.bar = default_valueสำหรับอินสแตนซ์นี้เว้นแต่อินสแตนซ์จะมีการattribute_whose_presence_has_been_validatedบันทึก / อัปเดตก่อนหน้านี้ default_valueจะถูกใช้ร่วมกับมุมมองของคุณจะทำให้รูปแบบโดยใช้default_valueสำหรับbarแอตทริบิวต์

ที่ดีที่สุดคือแฮ็ค ...

แก้ไข - ใช้ 'new_record?' เพื่อตรวจสอบว่ามีการสร้างอินสแตนซ์จากการโทรใหม่หรือไม่

แทนที่จะตรวจสอบค่าแอตทริบิวต์ให้ใช้new_record?วิธีการในตัวกับราง ดังนั้นตัวอย่างข้างต้นควรมีลักษณะดังนี้:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo, if: 'new_record?'
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    self.bar = default_value
  end
end

นี่สะอาดกว่ามาก อาวิเศษของ Rails - ฉลาดกว่าฉัน


สิ่งนี้ไม่ได้ผลตามที่ฉันคาดไว้ - ควรตั้งค่าเริ่มต้นเป็นใหม่เท่านั้น แต่ยังตั้งค่าเริ่มต้นเป็นแอตทริบิวต์สำหรับแต่ละวัตถุที่พบและสร้างอินสแตนซ์ (เช่นระเบียนที่โหลดจากฐานข้อมูล) คุณสามารถตรวจสอบแอตทริบิวต์ของวัตถุเพื่อค้นหาค่าก่อนกำหนดค่าเริ่มต้นได้ แต่นั่นไม่ใช่วิธีการแก้ปัญหาที่หรูหราหรือมีประสิทธิภาพ
ข้อผิดพลาด

1
ทำไมคุณถึงแนะนำให้after_initializeโทรกลับที่นี่ เอกสารเกี่ยวกับการโทรกลับมีตัวอย่างการตั้งค่าเริ่มต้นbefore_createโดยไม่ต้องตรวจสอบเงื่อนไขเพิ่มเติม
Dfr

@Dfr - ฉันคงพลาดไปแล้วคุณช่วยส่งลิงค์เพื่อตรวจสอบให้ฉันได้ไหมและฉันจะอัปเดตคำตอบ ...
ข้อผิดพลาด

ใช่ที่นี่api.rubyonrails.org/classes/ActiveRecord/Callbacks.htmlตัวอย่างแรกคลาสSubscription
Dfr

5
@Dfr - เหตุผลที่เราใช้after_initializeแทนการbefore_createเรียกกลับคือเราต้องการตั้งค่าเริ่มต้นสำหรับผู้ใช้ (สำหรับใช้ในมุมมอง) เมื่อพวกเขากำลังสร้างวัตถุใหม่ การbefore_createเรียกกลับจะถูกเรียกหลังจากที่ผู้ใช้ได้รับอ็อบเจ็กต์ใหม่จัดเตรียมอินพุตของพวกเขาและส่งอ็อบเจ็กต์เพื่อสร้างไปยังคอนโทรลเลอร์ จากนั้นคอนโทรลเลอร์จะตรวจสอบการbefore_createโทรกลับใด ๆ ดูเหมือนจะสวนทางกับสัญชาตญาณ แต่มันเป็นระบบการตั้งชื่อ - before_createหมายถึงการcreateกระทำ การสร้างอินสแตนซ์วัตถุใหม่ไม่ใช่createวัตถุ
ข้อผิดพลาด

5

สำหรับฟิลด์บูลีนใน Rails 3.2.6 อย่างน้อยสิ่งนี้จะใช้ได้ในการย้ายข้อมูลของคุณ

def change
  add_column :users, :eula_accepted, :boolean, default: false
end

การใส่1หรือ0เป็นค่าเริ่มต้นจะไม่ทำงานที่นี่เนื่องจากเป็นฟิลด์บูลีน ต้องเป็น a trueหรือfalseค่า


4

ในกรณีที่คุณกำลังจัดการกับ Model คุณสามารถใช้ Attriutes API ใน Rails 5+ http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute

เพียงเพิ่มการย้ายข้อมูลด้วยชื่อคอลัมน์ที่เหมาะสมจากนั้นในโมเดลตั้งค่าด้วย:

class StoreListing < ActiveRecord::Base
  attribute :country, :string, default: 'PT'
end

2

สร้างการโยกย้ายและการใช้งานที่change_column_defaultรวบรัดและย้อนกลับได้:

class SetDefaultAgeInPeople < ActiveRecord::Migration[5.2]
  def change
    change_column_default :people, :age, { from: nil, to: 0 }
  end
end

1

หากคุณเพิ่งตั้งค่าเริ่มต้นสำหรับแอตทริบิวต์บางอย่างของโมเดลที่ได้รับการสนับสนุนฐานข้อมูลฉันจะพิจารณาใช้ค่าคอลัมน์เริ่มต้นของ sql - คุณสามารถอธิบายได้ไหมว่าคุณใช้ค่าเริ่มต้นประเภทใด

มีหลายวิธีในการจัดการปลั๊กอินนี้ดูเหมือนเป็นตัวเลือกที่น่าสนใจ


1
ฉันคิดว่าคุณไม่ควรขึ้นอยู่กับฐานข้อมูลเพื่อจัดการกับค่าเริ่มต้นและข้อ จำกัด ทุกสิ่งที่ควรจัดเรียงในเลเยอร์โมเดล ปลั๊กอินนั้นใช้ได้เฉพาะกับรุ่น ActiveRecord เท่านั้นไม่ใช่วิธีทั่วไปในการตั้งค่าเริ่มต้นสำหรับวัตถุ
Lukas Stejskal

ฉันขอยืนยันว่ามันขึ้นอยู่กับประเภทของค่าเริ่มต้นที่คุณพยายามใช้มันไม่ใช่สิ่งที่ฉันต้องทำบ่อยนัก แต่ฉันจะบอกว่าข้อ จำกัด นั้นดีกว่ามากในการตั้งค่าทั้งในฐานข้อมูลและ แบบจำลอง - เพื่อป้องกันไม่ให้ข้อมูลของคุณไม่ถูกต้องในฐานข้อมูล
paulthenerd

1

คำแนะนำในการแทนที่ new / initialize อาจจะไม่สมบูรณ์ Rails จะ (บ่อยครั้ง) เรียกการจัดสรรสำหรับวัตถุ ActiveRecord และการเรียกเพื่อจัดสรรจะไม่ส่งผลให้มีการเรียกเพื่อเริ่มต้น

หากคุณกำลังพูดถึงวัตถุ ActiveRecord ให้ดูที่การแทนที่ after_initialize

โพสต์บล็อกเหล่านี้ (ไม่ใช่ของฉัน) มีประโยชน์:

ค่า ดีฟอลต์ตัวสร้างเริ่มต้นไม่ถูกเรียก

[แก้ไข: SFEley ชี้ให้เห็นว่าจริง ๆ แล้ว Rails ดูที่ค่าเริ่มต้นในฐานข้อมูลเมื่อสร้างอินสแตนซ์วัตถุใหม่ในหน่วยความจำ - ฉันไม่ได้ตระหนักถึงสิ่งนั้น]


1

ฉันต้องการตั้งค่าเริ่มต้นเหมือนกับที่ระบุเป็นค่าคอลัมน์เริ่มต้นใน DB มันก็เป็นแบบนี้

a = Item.new
a.published_at # => my default value

a = Item.new(:published_at => nil)
a.published_at # => nil

เนื่องจาก after_initialize เรียกกลับหลังจากตั้งค่าแอตทริบิวต์จากอาร์กิวเมนต์จึงไม่มีทางทราบได้ว่าแอตทริบิวต์เป็นศูนย์เนื่องจากไม่เคยตั้งค่าหรือเนื่องจากตั้งใจตั้งค่าเป็นศูนย์ ดังนั้นฉันต้องโผล่เข้าไปข้างในเล็กน้อยและมาพร้อมกับวิธีง่ายๆนี้

class Item < ActiveRecord::Base
  def self.column_defaults
    super.merge('published_at' => Time.now)
  end
end

ใช้งานได้ดีสำหรับฉัน (ราง 3.2.x)


1

วิธีที่อาจดีกว่า / สะอาดกว่าคำตอบที่เสนอไว้คือการเขียนทับ accessor เช่นนี้:

def status
  self['name_of_var'] || 'desired_default_value'
end

โปรดดูที่ "การเขียนทับ accessors เริ่มต้น" ในเอกสาร ActiveRecord :: ฐานและอื่น ๆ จาก StackOverflow เกี่ยวกับการใช้ตัวเอง


0

ฉันตอบคำถามที่คล้ายกันที่นี่ .. วิธีง่ายๆในการทำสิ่งนี้คือการใช้ Rails attr_accessor_with_default

class SOF
  attr_accessor_with_default :is_awesome,true
end

sof = SOF.new
sof.is_awesome

=> true

อัปเดต

attr_accessor_with_defaultเลิกใช้แล้วใน Rails 3.2 .. คุณสามารถทำสิ่งนี้แทนด้วย Ruby แท้

class SOF
  attr_writer :is_awesome

  def is_awesome
    @is_awesome ||= true
  end
end

sof = SOF.new
sof.is_awesome

#=> true

attr_accessor_with_defaultเลิกใช้แล้วเมื่อราง> 3.1.0
flynfish

ในตัวอย่างของคุณ is_awesome จะเป็นจริงเสมอแม้ว่า @is_awesome == false
Ritchie



-3

คุณสามารถแทนที่ตัวสร้างสำหรับโมเดล ActiveRecord

แบบนี้:

def initialize(*args)
  super(*args)
  self.attribute_that_needs_default_value ||= default_value
  self.attribute_that_needs_another_default_value ||= another_default_value
  #ad nauseum
end

2
นี่เป็นสถานที่ที่แย่มากในการเปลี่ยนการทำงานของรางแกน มีความเสถียรอื่น ๆ น้อยกว่าที่จะทำลายสิ่งอื่น ๆ วิธีการทำสิ่งนี้ที่กล่าวถึงในคำตอบอื่น ๆ การปะลิงควรเป็นทางเลือกสุดท้ายสำหรับสิ่งที่ไม่สามารถทำได้ด้วยวิธีอื่น
จะ
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.