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


417

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

ฉันเห็นโพสต์จาก Pratik ที่อธิบายโค้ดอันน่าเกลียดและซับซ้อน: http://m.onkey.org/2007/7/24/how-to-set-default-values-in-your-model

class Item < ActiveRecord::Base  
  def initialize_with_defaults(attrs = nil, &block)
    initialize_without_defaults(attrs) do
      setter = lambda { |key, value| self.send("#{key.to_s}=", value) unless
        !attrs.nil? && attrs.keys.map(&:to_s).include?(key.to_s) }
      setter.call('scheduler_type', 'hotseat')
      yield self if block_given?
    end
  end
  alias_method_chain :initialize, :defaults
end

ฉันได้เห็นตัวอย่างต่อไปนี้ googling ไปรอบ ๆ :

  def initialize 
    super
    self.status = ACTIVE unless self.status
  end

และ

  def after_initialize 
    return unless new_record?
    self.status = ACTIVE
  end

ฉันเคยเห็นคนใส่ไว้ในการย้ายถิ่นของพวกเขา แต่ฉันอยากจะเห็นมันกำหนดไว้ในรหัสรุ่น

มีวิธีบัญญัติมาตรฐานเพื่อตั้งค่าเริ่มต้นสำหรับเขตข้อมูลในรูปแบบ ActiveRecord หรือไม่?


ดูเหมือนว่าคุณจะตอบคำถามของตัวเองในสองสายพันธุ์ที่แตกต่างกัน :)
อดัม Byrtek

19
โปรดทราบว่า Ruby สำนวน "มาตรฐาน" สำหรับ 'self.status = ACTIVE ยกเว้น self.status' คือ 'self.status || = ACTIVE'
Mike Woodhouse เมื่อ

1
คำตอบของ Jeff Perrin นั้นดีกว่าคำตอบที่ได้รับการยอมรับในปัจจุบัน default_scope เป็นวิธีการแก้ปัญหาที่ยอมรับไม่ได้สำหรับการตั้งค่าเริ่มต้นเพราะมันมีผลขนาดใหญ่ด้านการเปลี่ยนพฤติกรรมของแบบสอบถาม
lawrence


2
ให้ upvotes ทั้งหมดสำหรับคำถามนี้ฉันจะบอกว่า Ruby ต้องการวิธี setDefaultValue สำหรับ ActiveRecord
spartikus

คำตอบ:


557

มีปัญหาหลายอย่างสำหรับแต่ละวิธีที่มี แต่ฉันเชื่อว่าการกำหนดการafter_initializeโทรกลับเป็นวิธีที่จะไปด้วยเหตุผลต่อไปนี้:

  1. default_scopeจะเริ่มต้นค่าสำหรับรุ่นใหม่ แต่จากนั้นจะกลายเป็นขอบเขตที่คุณค้นหารูปแบบ หากคุณเพียงต้องการเริ่มต้นตัวเลขเป็น 0 นี่ไม่ใช่สิ่งที่คุณต้องการ
  2. การกำหนดค่าเริ่มต้นในการย้ายข้อมูลของคุณยังทำงานได้บางส่วน ... ตามที่ได้รับการกล่าวถึงแล้วสิ่งนี้จะไม่ทำงานเมื่อคุณเรียกใช้ Model.new
  3. การเอาชนะinitializeสามารถทำงานได้ แต่อย่าลืมโทรsuper!
  4. การใช้งานปลั๊กอินอย่างฟิวชั่นนั้นค่อนข้างไร้สาระ นี่คือทับทิมเราต้องใช้ปลั๊กอินเพื่อเริ่มต้นค่าเริ่มต้นจริง ๆ หรือไม่?
  5. การแทนที่จะafter_initialize เลิกในขณะที่ Rails 3 เมื่อฉันแทนที่after_initializeใน Rails 3.0.3 ฉันได้รับคำเตือนต่อไปนี้ในคอนโซล:

คำเตือนการเลิกใช้งาน: Base # after_initialize เลิกใช้แล้วโปรดใช้ Base.after_initialize: method แทน (เรียกจาก / Users / me / myapp / app / models / my_model: 15)

ดังนั้นฉันจะบอกว่าเขียนafter_initializeโทรกลับซึ่งช่วยให้คุณเริ่มต้นคุณลักษณะนอกเหนือจากการให้คุณตั้งค่าเริ่มต้นในการเชื่อมโยงเช่น:

  class Person < ActiveRecord::Base
    has_one :address
    after_initialize :init

    def init
      self.number  ||= 0.0           #will set the default value only if it's nil
      self.address ||= build_address #let's you set a default association
    end
  end    

ตอนนี้คุณมีที่เดียวที่จะมองหาการเริ่มต้นโมเดลของคุณ ฉันใช้วิธีนี้จนกว่าจะมีคนคิดวิธีที่ดีกว่า

คำเตือน:

  1. สำหรับฟิลด์บูลีนให้ทำดังนี้

    self.bool_field = true if self.bool_field.nil?

    ดูความคิดเห็นของ Paul Russell ในคำตอบนี้สำหรับรายละเอียดเพิ่มเติม

  2. หากคุณเลือกเฉพาะชุดย่อยของคอลัมน์สำหรับแบบจำลอง (เช่นการใช้selectในการค้นหาPerson.select(:firstname, :lastname).all) คุณจะได้รับMissingAttributeErrorถ้าinitวิธีการของคุณเข้าถึงคอลัมน์ที่ไม่ได้รวมอยู่ในselectข้อ คุณสามารถป้องกันกรณีเช่นนี้:

    self.number ||= 0.0 if self.has_attribute? :number

    และสำหรับคอลัมน์บูลีน ...

    self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?

    โปรดทราบว่าไวยากรณ์นั้นแตกต่างจาก Rails 3.2 (ดูความคิดเห็นของ Cliff Darling ด้านล่าง)


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

106
สิ่งหนึ่งที่ทราบเกี่ยวกับสิ่งนี้ - ถ้าคุณมีฟิลด์บูลีนที่คุณต้องการเริ่มต้นอย่าทำself.bool_field ||= trueเช่นนี้เพราะจะบังคับให้ฟิลด์เป็นจริงแม้ว่าคุณจะกำหนดค่าเริ่มต้นเป็นเท็จอย่างชัดเจน self.bool_field = true if self.bool_field.nil?แทนที่จะทำ
พอลรัสเซล

2
เกี่ยวกับจุด # 2 Model.new ใช้งานได้จริง (สำหรับฉันเท่านั้น) ร่วมกับค่าเริ่มต้นที่กำหนดในการย้ายข้อมูลหรือมากกว่านั้นโดยมีค่าเริ่มต้นสำหรับคอลัมน์ตาราง แต่ฉันรู้ว่าวิธีการของ Jeff ที่อิงจาก after_initialize callback น่าจะเป็นวิธีที่ดีที่สุด แค่คำถาม: มันใช้งานได้กับวัตถุที่สกปรก แต่ยังไม่ได้บันทึก? ในตัวอย่างของคุณ Person.new.number_ จะกลับมาเป็น 0.0 หรือไม่
Laurent Farcy

21
ข้อควรระวังเมื่อใช้วิธีการนี้ร่วมกับการเลือกคอลัมน์เฉพาะที่มีการบันทึกที่ใช้งาน ในกรณีนี้เท่านั้นแอตทริบิวต์ที่ระบุไว้ในแบบสอบถามจะพบในวัตถุและรหัส init MissingAttributeErrorจะโยน คุณสามารถเพิ่มการตรวจสอบพิเศษตามที่แสดง: self.number ||= 0.0 if self.has_attribute? :number สำหรับ self.bool_field = true if (self.has_attribute? :bool_value) && self.bool_field.nil?booleans: นี่คือ Rails 3.2+ - สำหรับรุ่นก่อนหน้าใช้self.attributes.has_key?และคุณต้องใช้สตริงแทนสัญลักษณ์
Cliff Darling

6
การทำสิ่งนี้กับการเชื่อมโยงจะกระตือรือร้นที่จะโหลดการเชื่อมโยงเหล่านั้นในการค้นหา เริ่มต้นinitializeด้วยreturn if !new_record?เพื่อหลีกเลี่ยงปัญหาเรื่องประสิทธิภาพ
Kyle Macey

68

Rails 5+

คุณสามารถใช้วิธีการแอตทริบิวต์ในรุ่นของคุณเช่น:

class Account < ApplicationRecord
  attribute :locale, :string, default: 'en'
end

คุณยังสามารถส่งแลมบ์ดาไปที่defaultพารามิเตอร์ ตัวอย่าง:

attribute :uuid, UuidType.new, default: -> { SecureRandom.uuid }

3
ahhhhh นี่คืออัญมณีที่ฉันกำลังมองหา! ค่าเริ่มต้นสามารถใช้ proc ได้เช่นกันเช่นค่าเริ่มต้น: -> {Time.current.to_date}
schpet

1
ตรวจสอบให้แน่ใจว่าได้ระบุประเภทเป็นอาร์กิวเมนต์ที่สองมิฉะนั้นจะเป็นประเภทValueและจะไม่มีการพิมพ์ดีด
null

เพื่อความสุขของฉันนี้ยังทำงานร่วมกับ store_accessor เช่นให้store_accessor :my_jsonb_column, :localeคุณสามารถกำหนดattribute :locale, :string, default: 'en'
Ryan Romanchuk

โอ้ยอดเยี่ยมมากฉันต้องการค่าเริ่มต้นเพื่อแสดงในรูปแบบและใช้งานได้ดี ขอบคุณลูคัส
พอลวัตสัน

nilหนึ่งยังสามารถตั้งค่าเหล่านี้ไป หากพวกเขาไม่สามารถnilDB not nullเริ่มต้น + DB + github.com/sshaw/keep_defaultsเป็นวิธีที่จะไปจากประสบการณ์ของผม
sshaw

47

เราใส่ค่าเริ่มต้นในฐานข้อมูลผ่านการย้ายข้อมูล (โดยการระบุ :defaultตัวเลือกในแต่ละคำจำกัดความของคอลัมน์) และให้ Active Record ใช้ค่าเหล่านี้เพื่อตั้งค่าเริ่มต้นสำหรับแต่ละแอตทริบิวต์

IMHO แนวทางนี้สอดคล้องกับหลักการของ AR: อนุสัญญาว่าด้วยการกำหนดค่า DRY คำจำกัดความของตารางจะขับเคลื่อนโมเดลไม่ใช่วิธีอื่น

โปรดทราบว่าค่าเริ่มต้นยังคงอยู่ในรหัสแอปพลิเคชัน (Ruby) แม้ว่าจะไม่ได้อยู่ในรูปแบบ แต่อยู่ในการโยกย้าย


3
ปัญหาอื่นคือเมื่อคุณต้องการค่าเริ่มต้นสำหรับ foreign key คุณไม่สามารถเข้ารหัสค่า ID ในฮาร์ดโค้ดคีย์ต่างประเทศได้เนื่องจากในฐานข้อมูลที่ต่างกัน ID อาจแตกต่างกัน
shmichael

2
อีกปัญหาหนึ่งคือวิธีนี้คุณไม่สามารถเริ่มต้นการเข้าถึงแบบไม่ถาวร (คุณลักษณะที่ไม่ใช่คอลัมน์ db)
Viktor Trón

2
ปัญหาอื่นคือคุณไม่สามารถเห็นค่าเริ่มต้นทั้งหมดในที่เดียว พวกเขาอาจกระจัดกระจายผ่านการโยกย้ายที่แตกต่างกัน
ประกาศ

8
declan มี db / schema.rb อยู่
Benjamin Atkin

6
ฉันต้องการพูดถึงสำหรับผู้อ่านในอนาคต: อย่างน้อยจากสิ่งที่ฉันได้อ่านนี้เป็นสิ่งที่ตรงกันข้ามกับหลักการของ AR ตรรกะสำหรับแบบจำลองควรอยู่ในคลาสของแบบจำลองและฐานข้อมูลควรมีความโง่ที่สุด ค่าเริ่มต้นสำหรับฉันถือเป็นตรรกะเฉพาะเกี่ยวกับแบบจำลอง
darethas

40

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

after_initialize :defaults

def defaults
   unless persisted?
    self.extras||={}
    self.other_stuff||="This stuff"
    self.assoc = [OtherModel.find_by_name('special')]
  end
end

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

เมื่อเห็นคำตอบของแบรดเมอร์เรย์สิ่งนี้จะยิ่งดีกว่าหากเงื่อนไขถูกย้ายไปที่คำขอโทรกลับ:

after_initialize :defaults, unless: :persisted?
              # ":if => :new_record?" is equivalent in this context

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
  self.assoc = [OtherModel.find_by_name('special')]
end

4
นี่เป็นจุดสำคัญจริงๆ ฉันต้องจินตนาการว่าในกรณีส่วนใหญ่การตั้งค่าเริ่มต้นในบันทึกเป็นสิ่งที่ต้องทำก่อนที่จะยังคงบันทึกใหม่ไม่ใช่เมื่อโหลดบันทึกที่ยังคงอยู่
รัสเซลซิลวา

ขอบคุณมากคุณช่วยชีวิตฉันไว้
stephanfriedrich

2
แล้วไง:before_createล่ะ
Franklin Yu

วิธี: before_create จัดการแยกใหม่และบันทึกการโทร? ฉันต้องการตรวจสอบว่าและเข้าใจจริง ๆ ก่อนที่จะเปลี่ยนไป
โจเซฟลอร์ด

17

รูปแบบการเรียกกลับ after_initialize สามารถปรับปรุงได้โดยทำดังต่อไปนี้

after_initialize :some_method_goes_here, :if => :new_record?

สิ่งนี้มีประโยชน์ที่ไม่สำคัญหากรหัสเริ่มต้นของคุณจำเป็นต้องจัดการกับการเชื่อมโยงเนื่องจากรหัสต่อไปนี้จะทำให้เกิดการ +1 เล็กน้อยหากคุณอ่านบันทึกเริ่มต้นโดยไม่รวมถึงการเชื่อมโยงที่เกี่ยวข้อง

class Account

  has_one :config
  after_initialize :init_config

  def init_config
    self.config ||= build_config
  end

end

16

พวกฟิวชั่นมีปลั๊กอินที่ดีสำหรับสิ่งนี้


หมายเหตุปลั๊กอินนี้จะช่วยให้:defaultค่าในการโยกย้ายคีมาเป็น Model.new'เพียงแค่การทำงานกับ
jchook

ฉันสามารถรับ:defaultค่าในการย้ายถิ่นเพื่อ 'เพียงแค่ทำงาน' Model.newซึ่งตรงกันข้ามกับสิ่งที่เจฟฟ์พูดในโพสต์ของเขา ตรวจสอบแล้วว่าทำงานใน Rails 4.1.16
Magne

8

วิธีที่มีศักยภาพดีกว่า / สะอาดกว่าคำตอบที่เสนอคือการเขียนทับ accessor เช่นนี้

def status
  self['status'] || ACTIVE
end

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


attributesสถานะจะยังคงเป็นศูนย์ในกัญชากลับมาจาก ทดสอบในราง 5.2.0
spyle

8

ฉันใช้attribute-defaultsอัญมณี

จากเอกสาร: เรียกใช้sudo gem install attribute-defaultsและเพิ่มลงrequire 'attribute_defaults'ในแอปของคุณ

class Foo < ActiveRecord::Base
  attr_default :age, 18
  attr_default :last_seen do
    Time.now
  end
end

Foo.new()           # => age: 18, last_seen => "2014-10-17 09:44:27"
Foo.new(:age => 25) # => age: 25, last_seen => "2014-10-17 09:44:28"

7

คำถามที่คล้ายกัน แต่ทั้งหมดมีบริบทแตกต่างกันเล็กน้อย: - ฉันจะสร้างค่าเริ่มต้นสำหรับคุณลักษณะในโมเดลของ Rails activerecord ได้อย่างไร

คำตอบที่ดีที่สุด: ขึ้นอยู่กับสิ่งที่คุณต้องการ!

หากคุณต้องการให้ทุกวัตถุเริ่มต้นด้วยค่า:ใช้after_initialize :init

คุณต้องการให้new.htmlแบบฟอร์มมีค่าเริ่มต้นเมื่อเปิดหน้าหรือไม่ ใช้https://stackoverflow.com/a/5127684/1536309

class Person < ActiveRecord::Base
  has_one :address
  after_initialize :init

  def init
    self.number  ||= 0.0           #will set the default value only if it's nil
    self.address ||= build_address #let's you set a default association
  end
  ...
end 

หากคุณต้องการให้ทุกวัตถุมีค่าที่คำนวณจากอินพุตของผู้ใช้:ใช้before_save :default_values คุณต้องการให้ผู้ใช้ป้อนXแล้วY = X+'foo'หรือไม่? ใช้:

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P'
  end
end

4

นี่คือสิ่งที่ก่อสร้างสำหรับ! แทนที่initializeเมธอดของโมเดล

ใช้after_initializeวิธีการ


2
โดยปกติคุณจะถูกต้อง แต่คุณไม่ควรลบล้างการเริ่มต้นในรูปแบบ ActiveRecord เนื่องจากอาจไม่ถูกเรียกใช้เสมอ คุณควรใช้after_initializeวิธีการแทน
ลุค Redpath

การใช้ default_scope เพียงเพื่อตั้งค่าเริ่มต้นนั้นผิดอย่างแน่นอน after_initialize เป็นคำตอบที่ถูกต้อง
joaomilho

4

Sup guys, ฉันลงเอยด้วยการทำสิ่งต่อไปนี้:

def after_initialize 
 self.extras||={}
 self.other_stuff||="This stuff"
end

ทำงานเหมือนจับใจ!


3

สิ่งนี้ได้รับคำตอบมาเป็นเวลานาน แต่ฉันต้องการค่าเริ่มต้นบ่อยๆและไม่ต้องการเก็บไว้ในฐานข้อมูล ฉันสร้างDefaultValuesความกังวล:

module DefaultValues
  extend ActiveSupport::Concern

  class_methods do
    def defaults(attr, to: nil, on: :initialize)
      method_name = "set_default_#{attr}"
      send "after_#{on}", method_name.to_sym

      define_method(method_name) do
        if send(attr)
          send(attr)
        else
          value = to.is_a?(Proc) ? to.call : to
          send("#{attr}=", value)
        end
      end

      private method_name
    end
  end
end

และจากนั้นใช้ในรูปแบบของฉันเช่น:

class Widget < ApplicationRecord
  include DefaultValues

  defaults :category, to: 'uncategorized'
  defaults :token, to: -> { SecureRandom.uuid }
end

3

ฉันเคยเห็นคนใส่ไว้ในการย้ายถิ่นของพวกเขา แต่ฉันอยากจะเห็นมันกำหนดไว้ในรหัสรุ่น

มีวิธีบัญญัติมาตรฐานเพื่อตั้งค่าเริ่มต้นสำหรับเขตข้อมูลในรูปแบบ ActiveRecord หรือไม่?

วิธี canonical Rails ก่อน Rails 5 คือการตั้งค่าในการย้ายข้อมูลและเพียงมองหาdb/schema.rbเมื่อใดก็ตามที่ต้องการดูว่ามีการตั้งค่าเริ่มต้นโดย DB สำหรับรุ่นใด

ตรงกันข้ามกับสิ่งที่ @Jeff Perrin ตอบ (ซึ่งค่อนข้างเก่า) วิธีการย้ายข้อมูลจะใช้ค่าเริ่มต้นเมื่อใช้งาน Model.newงานเนื่องจากเวทมนตร์ของ Rails บางตัว ตรวจสอบแล้วว่าทำงานใน Rails 4.1.16

สิ่งที่ง่ายที่สุดมักจะดีที่สุด หนี้ที่มีความรู้น้อยลงและมีความสับสนใน codebase และมัน 'ใช้งานได้'

class AddStatusToItem < ActiveRecord::Migration
  def change
    add_column :items, :scheduler_type, :string, { null: false, default: "hotseat" }
  end
end

หรือสำหรับการเปลี่ยนคอลัมน์โดยไม่ต้องสร้างใหม่ให้ทำอย่างใดอย่างหนึ่ง:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column_default :items, :scheduler_type, "hotseat"
  end
end

หรืออาจจะดีกว่า:

class AddStatusToItem < ActiveRecord::Migration
  def change
    change_column :items, :scheduler_type, :string, default: "hotseat"
  end
end

ตรวจสอบคู่มือ RoRอย่างเป็นทางการสำหรับตัวเลือกในวิธีการเปลี่ยนคอลัมน์

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

วิธีที่ยอมรับใน Rails 5+ คือ @Lucas Caton กล่าวว่า:

class Item < ActiveRecord::Base
  attribute :scheduler_type, :string, default: 'hotseat'
end

1

ปัญหาเกี่ยวกับการแก้ปัญหา after_initialize คือคุณต้องเพิ่ม after_initialize ให้กับทุก ๆ ออบเจ็กต์ที่คุณค้นหาจากฐานข้อมูลไม่ว่าคุณจะเข้าถึงแอททริบิวต์นี้หรือไม่ก็ตาม ฉันแนะนำวิธีขี้เกียจ

วิธีการแอตทริบิวต์ (getters) เป็นวิธีการเรียนการสอนด้วยตนเองดังนั้นคุณสามารถแทนที่พวกเขาและให้ค่าเริ่มต้น สิ่งที่ต้องการ:

Class Foo < ActiveRecord::Base
  # has a DB column/field atttribute called 'status'
  def status
    (val = read_attribute(:status)).nil? ? 'ACTIVE' : val
  end
end

คุณจะต้องทำ Foo.find_by_status ('ACTIVE') เว้นแต่อย่างเช่นมีคนชี้ให้เห็น ในกรณีนี้ฉันคิดว่าคุณจำเป็นต้องตั้งค่าเริ่มต้นในข้อ จำกัด ฐานข้อมูลของคุณถ้าฐานข้อมูลรองรับ


โซลูชันนี้และตัวเลือกที่เสนอไม่ทำงานในกรณีของฉัน: ฉันมีลำดับชั้นของคลาส STI ซึ่งมีเพียงหนึ่งคลาสเท่านั้นที่มีแอตทริบิวต์นั้นและคอลัมน์ที่เกี่ยวข้องที่จะใช้ในเงื่อนไขการสืบค้น DB
cmoran92

1

ฉันพบปัญหากับการafter_initializeให้ActiveModel::MissingAttributeErrorข้อผิดพลาดเมื่อทำการค้นหาที่ซับซ้อน:

เช่น:

@bottles = Bottle.includes(:supplier, :substance).where(search).order("suppliers.name ASC").paginate(:page => page_no)

"ค้นหา" ในที่.whereเป็นเงื่อนไข

ดังนั้นฉันจึงลงเอยด้วยการเริ่มต้นด้วยวิธีนี้:

def initialize
  super
  default_values
end

private
 def default_values
     self.date_received ||= Date.current
 end

superโทรเป็นสิ่งที่จำเป็นเพื่อให้แน่ใจว่าวัตถุที่เริ่มต้นอย่างถูกต้องจากActiveRecord::Baseก่อนที่จะทำรหัสการปรับแต่งของฉันคือ: default_values


ฉันชอบมัน. ฉันต้องทำdef initialize(*); super; default_values; endใน Rails 5.2.0 นอกจากนี้ยังมีค่าเริ่มต้นที่ใช้ได้แม้ใน.attributesแฮช
spyle

1
class Item < ActiveRecord::Base
  def status
    self[:status] or ACTIVE
  end

  before_save{ self.status ||= ACTIVE }
end

2
อืมดูเหมือนว่าจะเป็นคนที่มีความคิดสร้างสรรค์ในตอนแรก แต่หลังจากคิดไปเล็กน้อยฉันก็พบปัญหาเล็กน้อย ก่อนอื่นค่าเริ่มต้นทั้งหมดไม่ได้อยู่ในจุดเดียว แต่กระจัดกระจายไปทั่วชั้นเรียน (ลองนึกภาพค้นหาหรือเปลี่ยนแปลงค่า) ที่สองและที่เลวร้ายที่สุดคุณไม่สามารถใส่ได้ในภายหลังเป็นค่า Null (หรือแม้แต่ของปลอม!)
paradoja

ทำไมคุณต้องตั้งค่า null เป็นค่าเริ่มต้น คุณเอามันออกจากกล่องพร้อม AR โดยไม่ทำอะไรเลย สำหรับเท็จเมื่อใช้คอลัมน์บูลีนแล้วคุณพูดถูกนี่ไม่ใช่วิธีที่ดีที่สุด
Mike Breen

ฉันไม่สามารถพูดให้ผู้อื่นนิสัยการเขียนรหัสฉันไม่ได้มีปัญหาเพราะฉันไม่กระจายของ getters / setters ของฉันรอบ ๆ ไฟล์คลาส นอกจากนี้โปรแกรมแก้ไขข้อความที่ทันสมัยควรทำให้ง่ายต่อการไปยังวิธีการ (shift-cmd-t ใน textmate)
Mike Breen

@paradoja - ฉันจะเอามันกลับมาตอนนี้ฉันเห็นว่ามันพังลงด้วยการใช้ null ยัง ไม่จำเป็นต้องใช้ null เป็นค่าเริ่มต้น แต่ถ้าคุณต้องการเปลี่ยนค่าเป็น null ในบางจุด ดีจับ @ paradoja ขอบคุณ
Mike Breen

ฉันใช้วิธีนี้เนื่องจากทำงานได้ดีกับแอตทริบิวต์ที่สร้างขึ้นแบบไดนามิก
Dale Campbell

0

แม้ว่าการทำเช่นนั้นสำหรับการตั้งค่าเริ่มต้นจะทำให้เกิดความสับสนและอึดอัดในกรณีส่วนใหญ่คุณสามารถใช้:default_scopeเช่นกัน ตรวจสอบความคิดเห็น squil ที่นี่


0

after_initialize วิธีการเลิกใช้ใช้โทรกลับแทน

after_initialize :defaults

def defaults
  self.extras||={}
  self.other_stuff||="This stuff"
end

อย่างไรก็ตามการใช้: ค่าเริ่มต้นในการย้ายข้อมูลของคุณยังคงเป็นวิธีที่สะอาดที่สุด


4
ในรางที่ 3: after_initializeวิธีการจะไม่เลิก ในความเป็นจริงการเรียกกลับแมโครสไตล์คุณให้ตัวอย่างของการเลิก รายละเอียด: guide.rubyonrails.org/…
Zabba

0

ฉันพบว่าการใช้วิธีการตรวจสอบความถูกต้องให้การควบคุมการตั้งค่าเริ่มต้นจำนวนมาก คุณสามารถตั้งค่าเริ่มต้น (หรือการตรวจสอบความล้มเหลว) สำหรับการปรับปรุง คุณยังตั้งค่าเริ่มต้นที่แตกต่างกันสำหรับการแทรกและการอัพเดทหากคุณต้องการ โปรดทราบว่าค่าเริ่มต้นจะไม่ถูกตั้งค่าจนกว่า #valid? ถูกเรียก.

class MyModel
  validate :init_defaults

  private
  def init_defaults
    if new_record?
      self.some_int ||= 1
    elsif some_int.nil?
      errors.add(:some_int, "can't be blank on update")
    end
  end
end

เกี่ยวกับการกำหนดเมธอด after_initialize อาจมีปัญหาประสิทธิภาพเนื่องจาก after_initialize ถูกเรียกโดยแต่ละอ็อบเจ็กต์ที่ส่งคืนโดย: find: http://guides.rubyonrails.org/active_record_validations_callbacks.html#after_initialize


การตรวจสอบจะไม่เกิดขึ้นก่อนบันทึกหรือไม่ ถ้าคุณต้องการแสดงค่าเริ่มต้นก่อนบันทึก
nurettin

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

0

หากคอลัมน์เกิดขึ้นเป็นคอลัมน์ประเภท 'สถานะ' และโมเดลของคุณยืมตัวเองไปใช้เครื่องจักรสถานะลองพิจารณาใช้อัญมณี aasmหลังจากนั้นคุณก็สามารถทำได้

  aasm column: "status" do
    state :available, initial: true
    state :used
    # transitions
  end

มันยังไม่ได้เริ่มต้นค่าสำหรับเร็กคอร์ดที่ไม่ได้บันทึก แต่มันค่อนข้างสะอาดกว่าการกลิ้งของคุณเองด้วยinitหรืออะไรก็ตามและคุณเก็บเกี่ยวผลประโยชน์อื่น ๆ ของ aasm เช่นขอบเขตสำหรับสถานะทั้งหมดของคุณ



0

ฉันขอแนะนำอย่างยิ่งให้ใช้ gem "default_value_for": https://github.com/FooBarWidget/default_value_for

มีสถานการณ์บางอย่างที่ค่อนข้างยุ่งยากซึ่งต้องใช้วิธีการเริ่มต้นซึ่งอัญมณีทำ

ตัวอย่าง:

ค่าเริ่มต้น db ของคุณคือ NULL รูปแบบของคุณ / ค่าเริ่มต้นที่กำหนดเป็นทับทิมคือ "สตริงบางตัว" แต่คุณต้องการตั้งค่าให้เป็นศูนย์ด้วยเหตุผลใดก็ตาม:MyModel.new(my_attr: nil)

โซลูชันส่วนใหญ่ที่นี่จะไม่สามารถตั้งค่าเป็นศูนย์และจะตั้งเป็นค่าเริ่มต้นแทน

ตกลงดังนั้นแทนที่จะเข้า||=ใกล้คุณเปลี่ยนเป็นmy_attr_changed?...

แต่ตอนนี้จินตนาการว่าค่าเริ่มต้น db ของคุณคือ "สตริงบางตัว" รุ่น / ทับทิมที่กำหนดเป็นค่าเริ่มต้นคือ "สตริงอื่น ๆ " แต่ภายใต้สถานการณ์บางอย่างคุณต้องการตั้งค่าเป็น "สตริงบางส่วน" (ค่าเริ่มต้น db):MyModel.new(my_attr: 'some_string')

สิ่งนี้จะส่งผลให้เกิดmy_attr_changed?การเท็จเพราะค่าตรงกับค่าเริ่มต้น db ซึ่งจะเปิดไฟรหัสเริ่มต้นที่กำหนดทับทิมของคุณและตั้งค่าเป็น "สตริงอื่น ๆ " - อีกครั้งไม่ใช่สิ่งที่คุณต้องการ


ด้วยเหตุผลเหล่านั้นฉันไม่คิดว่าวิธีนี้สามารถทำได้โดยใช้ตะขอ after_initialize

อีกครั้งฉันคิดว่า gem "default_value_for" กำลังใช้แนวทางที่ถูกต้อง: https://github.com/FooBarWidget/default_value_for


0

นี่เป็นวิธีแก้ปัญหาที่ฉันใช้เมื่อฉันรู้สึกประหลาดใจเล็กน้อยที่ยังไม่ได้เพิ่ม

มีสองส่วนด้วยกัน ส่วนแรกคือการตั้งค่าเริ่มต้นในการย้ายข้อมูลจริงและส่วนที่สองคือการเพิ่มการตรวจสอบความถูกต้องในโมเดลเพื่อให้แน่ใจว่าการแสดงตนเป็นจริง

add_column :teams, :new_team_signature, :string, default: 'Welcome to the Team'

ดังนั้นคุณจะเห็นที่นี่ว่ามีการตั้งค่าเริ่มต้นไว้แล้ว ตอนนี้ในการตรวจสอบที่คุณต้องการให้แน่ใจว่ามีค่าสำหรับสตริงเสมอดังนั้นเพียงแค่ทำ

 validates :new_team_signature, presence: true

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

หวังว่าจะช่วย!


0
# db/schema.rb
create_table :store_listings, force: true do |t|
  t.string :my_string, default: "original default"
end

StoreListing.new.my_string # => "original default"

# app/models/store_listing.rb
class StoreListing < ActiveRecord::Base
  attribute :my_string, :string, default: "new default"
end

StoreListing.new.my_string # => "new default"

class Product < ActiveRecord::Base
  attribute :my_default_proc, :datetime, default: -> { Time.now }
end

Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
sleep 1
Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600

-2

ใช้ default_scope ในราง 3

api doc

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

อภิปรายผล


หากคุณใช้ meta_where, default_scope อาจไม่ทำงานสำหรับการกำหนดค่าเริ่มต้นให้กับวัตถุ AR ใหม่เนื่องจากข้อบกพร่อง
Viktor Trón

ปัญหา meta_where นี้ได้รับการแก้ไขแล้ว [ metautonomous.lighthouseapp.com/projects/53011/tickets/ …
Viktor Trón

3
default_scopeไม่ใช้ สิ่งนี้จะทำให้ข้อความค้นหาทั้งหมดของคุณเพิ่มเงื่อนไขนี้ในฟิลด์ที่คุณตั้งไว้ มันเกือบจะไม่เคยมีสิ่งที่คุณต้องการ
แบรด

@ แบรดตลกที่คุณพูดถึงฉันเห็นด้วยทั้งหมดมันเป็นความชั่วร้าย :) ดูความคิดเห็นของฉันในstackoverflow.com/questions/10680845/...
Viktor Trón

-3

จาก api docs http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html ใช้before_validationวิธีการในแบบจำลองของคุณมันให้ตัวเลือกในการสร้างการเริ่มต้นเฉพาะสำหรับการสร้างและอัปเดตการโทรเช่นในตัวอย่างนี้ (รหัสอีกครั้ง จากตัวอย่างเอกสาร api) ฟิลด์หมายเลขจะถูกกำหนดค่าเริ่มต้นสำหรับบัตรเครดิต คุณสามารถปรับเปลี่ยนสิ่งนี้ได้อย่างง่ายดายเพื่อกำหนดสิ่งที่คุณต้องการ

class CreditCard < ActiveRecord::Base
  # Strip everything but digits, so the user can specify "555 234 34" or
  # "5552-3434" or both will mean "55523434"
  before_validation(:on => :create) do
    self.number = number.gsub(%r[^0-9]/, "") if attribute_present?("number")
  end
end

class Subscription < ActiveRecord::Base
  before_create :record_signup

  private
    def record_signup
      self.signed_up_on = Date.today
    end
end

class Firm < ActiveRecord::Base
  # Destroys the associated clients and people when the firm is destroyed
  before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end

แปลกใจที่เขาไม่ได้รับการแนะนำที่นี่


before_validation จะไม่ตั้งค่าเริ่มต้นจนกว่าวัตถุจะพร้อมที่จะยืนยัน หากกระบวนการจำเป็นต้องอ่านค่าเริ่มต้นก่อนที่จะคงอยู่ค่าจะไม่พร้อม
mmell

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