Validate presence of polymorphic parent

后端 未结 5 914
耶瑟儿~
耶瑟儿~ 2021-02-19 09:49

I am developing a Rails 3.2 application with the following models:

class User < ActiveRecord::Base
  # Associations
  belongs_to :authenticatable, polymorphic         


        
相关标签:
5条回答
  • 2021-02-19 10:01

    You can just move validation to before_save callback and it will work fine:

    class User < ActiveRecord::Base
      # Associations
      belongs_to :authenticatable, polymorphic: true
    
      # Validations
      before_save :check_authenticatable
    
      def check_authenticatable
        unless authenticatable
          errors[:customizable] << "can't be blank"
          false
        end
      end
    end
    
    0 讨论(0)
  • 2021-02-19 10:12

    I'm not sure if this solves your problem, but I use something like this when validating that a polymorphic parent exists.

    Here is some code that I used in a video model with the parent as the polymorphic association. This went in video.rb.

      validates_presence_of :parent_id, :unless => Proc.new { |p|
          # if it's a new record and parent is nil and addressable_type is set
          # then try to find the parent object in the ObjectSpace
          # if the parent object exists, then we're valid;
          # if not, let validates_presence_of do it's thing
          # Based on http://www.rebeccamiller-webster.com/2011/09/validate-polymorphic/
          if (new_record? && !parent && parent_type)
            parent = nil
            ObjectSpace.each_object(parent_type.constantize) do |o|
              parent = o if o.videos.include?(p) unless parent
            end
          end
          parent
        }
    
    0 讨论(0)
  • 2021-02-19 10:15

    In the create action, I had to assign it manually:

    @physician = Physician.new(params[:physician])
    @physician.user.authenticatable = @physician
    

    My problem is a little different (has_many and with different validation), but I think this should work.

    0 讨论(0)
  • 2021-02-19 10:17

    I was able to get this to work by overriding the nested attribute setter.

    class Physician
      has_one :user, as: :authenticatable
      accepts_nested_attributes_for :user
    
      def user_attributes=(attribute_set)
        super(attribute_set.merge(authenticatable: self))
      end
    end
    

    To DRY it up, I moved the polymorphic code to a concern:

    module Authenticatable
      extend ActiveSupport::Concern
    
      included do
        has_one :user, as: :authenticatable
        accepts_nested_attributes_for :user
    
        def user_attributes=(attribute_set)
          super(attribute_set.merge(authenticatable: self))
        end
      end
    end
    
    class Physician
      include Authenticatable
      ...
    end
    

    For has_many associations, the same can be accomplished with a map:

    class Physician
      has_many :users, as: :authenticatable
      accepts_nested_attributes_for :users
    
      def users_attributes=(attribute_sets)
        super(
          attribute_sets.map do |attribute_set|
            attribute_set.merge(authenticatable: self)
          end
        )
      end
    end
    
    class User
      belongs_to :authenticatable, polymorphic: true
      validates :authenticatable, presence: true
    end
    

    All that said, I think konung's last comment is correct - your example does not look like a good candidate for polymorphism.

    0 讨论(0)
  • 2021-02-19 10:25

    I believe this is expected:

    https://github.com/rails/rails/issues/1629#issuecomment-11033182 ( last two comments).

    Also check this out from rails api:

    One-to-one associations

    Assigning an object to a has_one association automatically saves that object and the object being replaced (if there is one), in order to update their foreign keys - except if the parent object is unsaved (new_record? == true).

    If either of these saves fail (due to one of the objects being invalid), an ActiveRecord::RecordNotSaved exception is raised and the assignment is cancelled.

    If you wish to assign an object to a has_one association without saving it, use the build_association method (documented below). The object being replaced will still be saved to update its foreign key.

    Assigning an object to a belongs_to association does not save the object, since the foreign key field belongs on the parent. It does not save the parent either.

    and this

    build_association(attributes = {}) Returns a new object of the associated type that has been instantiated with attributes and linked to this object through a foreign key, but has not yet been saved.

    You have to create a Parent first. Then assign it's id to polymorphic object.

    From what I can see, you create an object Physician.new which builds User but at this point it's not saved yet, so it doesn't have an id, so there is nothing to assign to polymorphic object. So validation will always fail since it's called before save.

    In other words: In your case when you call build_user, it returns User.new NOT User.create . Therefore authenticatable doesn't have a authenticatable_id assigned.

    You have several options:

    • Save associated user first.

      OR

    • Move validation in to after_save callback ( Possible but very annoying and bad)

      OR

    • Change your app structure - maybe avoid polymorphic association and switch to has_many through? Hard for me to judge since I don't know internals and business requirements. But it seems to me this is not a good candidate for polymorphic association. Will you have more models than just User that will be authenticatable?

    IMHO the best candidates for polymorphic associations are things like Phones, Addresses, etc. Address can belong to User, Customer, Company, Organization, Area51 etc, be Home, Shipping or Billing category i.e. It can MORPH to accommodate multiple uses, so it's a good object to extract. But Authenticatable seems to me a bit contrived and adds complexity where there is no need for it. I don't see any other object needing to be authenticable.

    If you could present your Authenticatable model and your reasoning and maybe migrations (?) I could advise you more. Right now I'm just pulling this out of thin air :-) But it seems like a good candidate for refactoring.

    0 讨论(0)
提交回复
热议问题