ruby on rails - كيف يمكنني تعيين القيم الافتراضية في ActiveRecord؟




ruby-on-rails rails-activerecord (17)

لقد رأيت أيضًا أشخاصًا يضعونها في هجرتهم ، لكنني أفضل رؤيتها معرّفة في شفرة النموذج.

هل هناك طريقة متعارف عليها لتعيين القيمة الافتراضية للحقول في نموذج ActiveRecord؟

طريقة القضبان المتعارف عليها ، قبل Rails 5 ، كانت في الواقع db/schema.rb في الترحيل ، وإلقاء نظرة فقط في db/schema.rb عندما تريد رؤية القيم الافتراضية التي يتم تعيينها بواسطة DB لأي طراز.

على عكس ما يجيبه @ Jeff Perrin على الحالات (وهو قديم قليلاً) ، سيطبق نهج الترحيل حتى الوضع الافتراضي عند استخدام Model.new ، نظرًا لوجود بعض سحر Rails. التحقق من العمل في القضبان 4.1.16.

أبسط شيء في كثير من الأحيان هو الأفضل. الديون أقل المعرفة والنقاط المحتملة من الارتباك في مصدر البرمجة. وانها تعمل فقط.

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

القيمة null: false تسمح القيم الفارغة بالقيم null: false في DB ، وكميزة إضافية ، فإنها تقوم أيضًا بتحديث جميع سجلات DB الموجودة مسبقًا والتي تم تعيينها مع القيمة الافتراضية لهذا الحقل أيضًا. يمكنك استبعاد هذه المعلمة في الترحيل إذا كنت ترغب في ذلك ، ولكنني وجدت أنه مفيد للغاية!

الطريقة القانونية في Rails 5+ هي كما قالLucas Caton:

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

كيف يمكنني تعيين القيمة الافتراضية في 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؟


أسئلة مماثلة ، ولكن جميعها لها سياق مختلف قليلاً: - كيف يمكنني إنشاء قيمة افتراضية للسمات في نموذج Activerecord Rails؟

أفضل جواب: يعتمد على ما تريد!

إذا كنت تريد أن يبدأ كل كائن بقيمة: استخدم after_initialize :init

هل تريد أن new.html للنموذج new.html قيمة افتراضية عند فتح الصفحة؟ استخدم https://.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

أنا واجهت مشاكل مع after_initialize إعطاء أخطاء ActiveModel::MissingAttributeError عند القيام يجد معقدة:

على سبيل المثال:

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

"البحث" في. هو تجزئة الشروط

لذلك انتهى بي الأمر عن طريق تجاوز التهيئة بهذه الطريقة:

def initialize
  super
  default_values
end

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

يلزم إجراء مكالمة super للتأكد من تهيئة الكائن بشكل صحيح من ActiveRecord::Base قبل تنفيذ رمز التخصيص ، أي: default_values


أول الأشياء أولاً: لا أتفق مع جواب جيف. من المنطقي عندما يكون تطبيقك صغيرًا ومنطقك بسيطًا. أنا هنا أحاول أن أعطي فكرة عن كيف يمكن أن يكون مشكلة عند بناء والحفاظ على تطبيق أكبر. لا أوصي باستخدام هذا المنهج أولاً عند بناء شيء صغير ، ولكن لنضعه في الاعتبار كنهج بديل:

السؤال هنا هو ما إذا كان هذا الافتراضي على السجلات هو منطق الأعمال. إذا كان الأمر كذلك ، سأكون حذرا لوضعها في نموذج ORM. بما أن إشارات ryw الحقل نشطة ، فإن هذا يبدو وكأنه منطق عمل. على سبيل المثال ، المستخدم نشط.

لماذا أكون حذرا في وضع مخاوف العمل في نموذج ORM؟

  1. يكسر SRP . تقوم أي فئة ترث من ActiveRecord :: Base بالفعل بأشياء مختلفة ، أهمها تناسق البيانات (التحقق من الصحة) والثبات (حفظ). وضع منطق الأعمال ، مهما كان صغيرا ، في AR :: Base يكسر SRP.

  2. هو أبطأ للاختبار. إذا كنت أرغب في اختبار أي شكل من أشكال المنطق في نموذج ORM الخاص بي ، يجب أن تقوم اختباراتي بتهيئة القضبان لكي يتم تشغيلها. لن تكون هذه مشكلة كبيرة في بداية التطبيق ، ولكن ستتراكم حتى تستغرق اختبارات الوحدة وقتًا طويلاً.

  3. سوف يكسر SRP أكثر على طول الخط ، وبطرق ملموسة. لنفترض أن نشاطنا التجاري يتطلب منا إرسال بريد إلكتروني إلى المستخدمين عندما يصبح العنصر نشطًا؟ الآن نقوم بإضافة منطق البريد الإلكتروني إلى نموذج البند ORM ، الذي تتمثل مسؤوليته الأساسية في وضع نموذج لعنصر. يجب أن لا يهمني منطق البريد الإلكتروني. هذه هي حالة من الآثار الجانبية التجارية . هذه لا تنتمي إلى نموذج ORM.

  4. من الصعب تنويع. لقد شاهدت تطبيقات Rails الناضجة مع أشياء مثل init_type مدعومة بقاعدة بيانات: حقل السلسلة ، والغرض الوحيد منه هو التحكم في منطق التهيئة. هذا هو تلويث قاعدة البيانات لإصلاح مشكلة هيكلية. هناك طرق أفضل ، على ما أعتقد.

طريقة PORO: بينما يعد هذا رمزًا أكثر قليلاً ، فإنه يسمح لك بالاحتفاظ بنموذجي ORM و Business Logic منفصلين. يتم تبسيط الشفرة هنا ، ولكن يجب أن تظهر الفكرة:

class SellableItemFactory
  def self.new(attributes = {})
    record = Item.new(attributes)
    record.active = true if record.active.nil?
    record
  end
end

ثم مع هذا في مكان ، فإن طريقة إنشاء عنصر جديد سيكون

SellableItemFactory.new

وبإمكان اختباراتي الآن التحقق ببساطة من أن منتج ItemFactory ينشط على عنصر إذا لم يكن له قيمة. لا تحتاج إلى قَصْلة التهيئة ، لا كسر ل SRP. عندما تصبح صياغة العنصر أكثر تقدمًا (على سبيل المثال ، تعيين حقل الحالة ، نوع افتراضي ، وما إلى ذلك) يمكن لـ ItemFactory إضافة هذا. إذا انتهى الأمر بنوعين من الافتراضات ، يمكننا إنشاء BusinesCaseItemFactory جديد للقيام بذلك.

ملاحظة: قد يكون من المفيد أيضًا استخدام حقن التبعية هنا للسماح للمصنع ببناء العديد من الأشياء النشطة ، لكني تركتها للتبسيط. ومن هنا: self.new (klass = Item، attributes = {})


استخدم default_scope في القضبان 3

api doc

يحجب ActiveRecord الفرق بين التعطيل المعرّف في قاعدة البيانات (المخطط) والتقصير في التطبيق (النموذج). أثناء التهيئة ، يقوم بتوزيع مخطط قاعدة البيانات ويلاحظ أي قيم افتراضية محددة هناك. لاحقاً ، عند إنشاء الكائنات ، تقوم بتعيين القيم الافتراضية المحددة للمخطط بدون لمس قاعدة البيانات.

discussion


الرجال Phusion لديهم بعض plugin لطيفة لهذا.


بعد off_initialize تم إهمال الأسلوب ، استخدم رد الاتصال بدلاً من ذلك.

after_initialize :defaults

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

ومع ذلك ، استخدام : الافتراضي في عمليات الترحيل الخاصة بك لا يزال أنظف طريقة.


سوب الرجال ، انتهى بي الأمر بما يلي:

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

يعمل كالسحر!


في Rails 5+ ، يمكنك استخدام طريقة attribute داخل النماذج الخاصة بك ، على سبيل المثال:

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

لقد تم الرد على ذلك لفترة طويلة ، لكني أحتاج إلى قيم افتراضية بشكل متكرر وأفضّل عدم وضعها في قاعدة البيانات. أقوم بإنشاء قلق 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

من api docs http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html استخدم طريقة before_validation في نموذجك ، فهو يمنحك خيارات إنشاء تهيئة محددة لإنشاء وتحديث المكالمات على سبيل المثال في هذا المثال (رمز جديد مأخوذ من مثال api docs) تتم تهيئة حقل الرقم لبطاقة الائتمان. يمكنك بسهولة تهيئة هذا لتعيين القيم التي تريدها

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

فوجئت أنه لم يتم اقتراحه هنا


نضع القيم الافتراضية في قاعدة البيانات من خلال عمليات الترحيل (عن طريق تحديد :default الخيار :default لكل تعريف عمود) وتسمح للسجل النشط باستخدام هذه القيم لتعيين الإعداد الافتراضي لكل سمة.

IMHO ، يتماشى هذا النهج مع مبادئ AR: الاتفاقية على التكوين ، جاف ، تعريف الجدول يقود النموذج ، وليس العكس.

لاحظ أن الإعدادات الافتراضية لا تزال في رمز التطبيق (Ruby) ، ولكن ليس في النموذج ولكن في الترحيل (الترحيل).



يمكن التعامل مع بعض الحالات البسيطة من خلال تحديد الافتراضي في مخطط قاعدة البيانات ولكن هذا لا يعالج عددًا من الحالات الأصعب بما في ذلك القيم المحسوبة ومفاتيح النماذج الأخرى. في هذه الحالات أفعل هذا:

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_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

يمكنني استخدام جوهرة attribute-defaults

من الوثائق: قم بتشغيل sudo gem install attribute-defaults وإضافة add 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"


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

  before_save{ self.status ||= ACTIVE }
end




rails-activerecord