SimpleDelegator and ActiveRecord::AssociationTypeMismatch

433 views
Skip to first unread message

Kris Leech

unread,
Jun 24, 2013, 9:23:20 AM6/24/13
to objects-...@googlegroups.com
When wrapping an object using SimpleDelegator, instead of extending it using a Module, the class changes which causes issues with ActiveRecord assosiations which are type checked, giving `ActiveRecord::AssociationTypeMismatch`.

customer   = Customer.new(user)
order.user = customer
order.save #=> ActiveRecord::AssociationTypeMismatch (Got Customer, Expected User)

I could do `order.user_id = customer.id` but it doesn't look so nice.

Does anyone know of a nice way of getting around this?

Jim Gay

unread,
Jun 24, 2013, 10:24:32 AM6/24/13
to objects-...@googlegroups.com
You can explicitly get the object that you want from the Customer

order.user = customer.__getobj__

But it probably depends on what else you may be doing with the customer object and that's almost the same as your user_id = id example. 

You could also override the user= method to assign the id from whatever object you're handing to it

def user=(user)
  self.user_id = user.id
end

For situations similar to this I'm using a gem I wrote that applies methods from a module http://github.com/saturnflyer/casting
But for that you'd really need Ruby 2.x (though it would work in 1.9) and you'd need to accept that method_missing would be implemented on your user class. I tried to make the readme informative if you'd like to check it out. 

There's also http://github.com/steveklabnik/becoming that relies on the same behavior in Ruby 2.x
--
Write intention revealing code #=> http://www.clean-ruby.com

Jim Gay
Saturn Flyer LLC

Chris Flipse

unread,
Jun 24, 2013, 11:07:26 AM6/24/13
to objects-...@googlegroups.com
On Mon, Jun 24, 2013 at 10:24 AM, Jim Gay <j...@saturnflyer.com> wrote:
You can explicitly get the object that you want from the Customer

order.user = customer.__getobj__

But it probably depends on what else you may be doing with the customer object and that's almost the same as your user_id = id example. 

You could also override the user= method to assign the id from whatever object you're handing to it

def user=(user)
  self.user_id = user.id
end


Careful doing this, though, as that could likely break some of the save cascades that you may be expecting from active record; also if the user has already been loaded, I don't think that it will get _reloaded_ (ie, you may have different users, even after assigning the user.)  Fails the "least surprise" test.
 
For situations similar to this I'm using a gem I wrote that applies methods from a module http://github.com/saturnflyer/casting
But for that you'd really need Ruby 2.x (though it would work in 1.9) and you'd need to accept that method_missing would be implemented on your user class. I tried to make the readme informative if you'd like to check it out. 

There's also http://github.com/steveklabnik/becoming that relies on the same behavior in Ruby 2.x




On Jun 24, 2013, at 8:23, Kris Leech <kris....@gmail.com> wrote:

When wrapping an object using SimpleDelegator, instead of extending it using a Module, the class changes which causes issues with ActiveRecord assosiations which are type checked, giving `ActiveRecord::AssociationTypeMismatch`.

customer   = Customer.new(user)
order.user = customer
order.save #=> ActiveRecord::AssociationTypeMismatch (Got Customer, Expected User)

I could do `order.user_id = customer.id` but it doesn't look so nice.

Does anyone know of a nice way of getting around this?

--
Write intention revealing code #=> http://www.clean-ruby.com

Jim Gay
Saturn Flyer LLC

--
You received this message because you are subscribed to the Google Groups "Objects on Rails" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objects-on-rai...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
:wq
Message has been deleted

Kris Leech

unread,
Jun 24, 2013, 2:58:04 PM6/24/13
to objects-...@googlegroups.com
Jim Gay wrote:
You can explicitly get the object that you want from the Customer

order.user = customer.__getobj__
That reminds me, I actually tried this:

class Customer < SimpleDelegator
  def class
    __getobj__.class
  end
end
but get the rarther odd error: User(#2154403100) expected, got User(#2154403100).


But it probably depends on what else you may be doing with the customer object and that's almost the same as your user_id = id example. 

You could also override the user= method to assign the id from whatever object you're handing to it

def user=(user)
  self.user_id = user.id
end

For situations similar to this I'm using a gem I wrote that applies methods from a module http://github.com/saturnflyer/casting
But for that you'd really need Ruby 2.x (though it would work in 1.9) and you'd need to accept that method_missing would be implemented on your user class. I tried to make the readme informative if you'd like to check it out. 

There's also http://github.com/steveklabnik/becoming that relies on the same behavior in Ruby 2.x
I will definitely check these gems out. Is rebinding methods a technique you feel you will stick with?




On Jun 24, 2013, at 8:23, Kris Leech <kris....@gmail.com> wrote:

When wrapping an object using SimpleDelegator, instead of extending it using a Module, the class changes which causes issues with ActiveRecord assosiations which are type checked, giving `ActiveRecord::AssociationTypeMismatch`.

customer   = Customer.new(user)
order.user = customer
order.save #=> ActiveRecord::AssociationTypeMismatch (Got Customer, Expected User)

I could do `order.user_id = customer.id` but it doesn't look so nice.

Does anyone know of a nice way of getting around this?

--
Write intention revealing code #=> http://www.clean-ruby.com

Jim Gay
Saturn Flyer LLC
--
You received this message because you are subscribed to the Google Groups "Objects on Rails" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objects-on-rai...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


--
===========================================================
I welcome VSRE emails. Learn more at http://vsre.info
===========================================================

Message has been deleted

Steve Klabnik

unread,
Jun 24, 2013, 11:55:22 PM6/24/13
to objects-...@googlegroups.com
Yup, this is one of the reasons why I turned Avdi's code into
becoming; it's one of those features that's great in rails 90% of the
time but bad the other 10% :/
Message has been deleted

Amos King

unread,
Jun 25, 2013, 3:59:59 PM6/25/13
to objects-on-rails
Steve does that work with validations? Can I add validations on the fly and not have them add to the core User class?


--
You received this message because you are subscribed to the Google Groups "Objects on Rails" group.
To unsubscribe from this group and stop receiving emails from it, send an email to objects-on-rai...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Kris Leech

unread,
Jun 26, 2013, 9:59:01 AM6/26/13
to objects-...@googlegroups.com
I've been playing with casting, I'm still on 1.9 as I couldn't get Rails 4 generators not to segfault on Ruby 2.0, so have yet to try becoming.

So far its very pleasing. My only minor concern is that casting an object in a role does not change the inheritence chain, so there are no hints the object has been extended. Similar to what happens when a object/class is re-opened and behaviour added to the invisible singelton class. Out of interest is it the singelton class to which the methods are re-bound too?

Greg H.

unread,
Jan 21, 2015, 4:57:17 PM1/21/15
to objects-...@googlegroups.com
I ran into the same issue but trying to do something else... 
class Customer < DelegateClass(User)
  def foo
    accounts.build(options)
  end
end
This will end up with a 
ActiveRecord::AssociationTypeMismatch: User(#123) expected, got User(#124)

The solution here is to start from the decorated object: 

class Customer < DelegateClass(User)
  def foo
    user.accounts.build(options)
  end

  private
  def user
    __getobj__
  end
end
Reply all
Reply to author
Forward
0 new messages