开发者

silently skip add with before_add association callback instead of raising an exception?

开发者 https://www.devze.com 2023-04-02 08:15 出处:网络
I\'m trying to do this has_many :roles, :before_add => :enforce_unique def enforce_unique(assoc) false if exists? assoc

I'm trying to do this

 has_many :roles, :before_add => :enforce_unique

 def enforce_unique(assoc)
   false if exists? assoc
 end

From the docs: "If a before_add callback throws an exception, the object does not get added to the collection". The using false above does not prevent the add, so I'm forced to do this:

 def enforce_unique(a开发者_开发百科ssoc)
   raise if exists? assoc
 end

This way, it's true that it doesn't get added, but it also raises an exception that has to be handled. Not very useful to me here. I would prefer this to behave more like regular AR callback before_save, where returning FALSE also prevents the save (or add) but doesn't raise an exception.

In this case above, I would prefer this to just not add the assoc silently. Is there a way to do this? I missing something? Or is raising an exception the only option here?


The way to solve it I think is to use throw and catch, which in Ruby are meant for flow control. Raising an exception is not a good fit, since this isn't an exceptional circumstance.

I ended up doing:

catch(:duplicate) do
  association.create({})
end

And then in the before_add callback, I did:

if(Class.where({}).first)
  throw :duplicate
end

More on throw/catch here:

http://rubylearning.com/blog/2011/07/12/throw-catch-raise-rescue-im-so-confused/


If the association isn't polymorphic you can do something like:

validates_uniqueness_of :name_of_model

inside of Role where name_of_model us what you are associating with


this question is a bit old, but i came across the same problem recently. here is how i solved it:

def enforce_unique |obj, x|
  v = obj.roles
  if i = v.index(x)
    v.slice! i
  end
end


Since this question is about saving rather than preventing it being included in the list temporarily (eg by a controller that is not interested in controlling its models) you could try overriding save in the related model and just not save it if the role exists:

class Role < ActiveRecord::Base
  belongs_to :user, inverse_of: :roles

  def save
    super unless self.new_record? && user.has_existing_role?(self)
  end
end

Sidenote: I don't buy the skinny controller argument when used with the Active Record pattern as business logic has to be put somewhere. With a business domain poor pattern like Active Record (not referring to the Ruby AR gem specifically) it really needs to exist a layer above (ie in the controller layer), you may use service objects or the decorator pattern as a means to achieve this.

Another approach would be to override update methods like << for the association and silently drop the role if it matches an existing one. Details on overriding association methods are in the ActiveRecord Association Class Methods Documentation

0

精彩评论

暂无评论...
验证码 换一张
取 消