Removing an association on an AR model via monkey patching


#1

How can I remove an association from an AR model with a monkey patch?

Assume:

class Foo < AR::Base
has_many :bars
end

It’s a long story, but in one particular data loading (not running the
app. context), I’d like to remove the has_many association to :bars.

Can I do that?

Thanks,
Wes


#2

On Jan 16, 12:59 pm, Wes G. removed_email_address@domain.invalid
wrote:

Can I do that?

It sounds like you want to make sure that code that does this:

@foo.bars

would raise a NoMethodFound exception? If so, you can just reopen the
class:

class Foo
def bars
raise NoMethodError.new(“undefined method ‘bars’”)
end
end

Then,

@foo.bars # => raises exception

However, this isn’t really removing all of remnants of the
association, so again it depends on what constitutes “removed enough”
for your situation.

Jeff
purpleworkshops.com


#3

Here’s my real problem:

I am trying to load a fixture into a database which acts as a secondary
DB to my application.

The fixture file uses Erb to generate appropriate fixtures based on
something like this:

<% Invoice.find_all_by_im_invoice_state(“Paid”).each do |inv| %>
…generate a bunch of fixtures…
<% end %>

The problem is that the Invoice class has an association which is not
present in the DB into which I am loading the data. The Invoice class’s
table is present, which is all that I need to properly generate the
fixtures. However, the class for this other has_many association that
I’m not interested in is not present.

So I’d like to temporarily remove the association for the duration of
the fixture load. Hoping I can just monkey patch within the Erb.

Thanks,
Wes


#4

When I call Invoice.find_all_by_im_invoice_state(“Paid”).each the
Invoice class is loaded.

When the Invoice class is loaded, all of its class level association
directives are executed. When it hits the one that doesn’t have a
backing table, it fails.

So I need to be able to modify the Invoice class to remove the offending
association before the fixture load occurs.

Wes


#5

On Jan 16, 2009, at 3:19 PM, Wes G. wrote:

…generate a bunch of fixtures…
the fixture load. Hoping I can just monkey patch within the Erb.

Thanks,
Wes

Unless you reference the association, it won’t be accessed. Am I (Are
we) missing something here? Just don’t use the association.

-Rob

Rob B. http://agileconsultingllc.com
removed_email_address@domain.invalid


#6

Frederick C. wrote:

On 16 Jan 2009, at 20:44, Wes G. wrote:

When the Invoice class is loaded, all of its class level association
directives are executed. When it hits the one that doesn’t have a
backing table, it fails.

So it’s at the point where the class (not the data) is loaded that the
problem occurs ? You might be in a tight spot then because it’s going
to be hard to remove the association before the class exists.

Actually, the class needs to exist and I just need to temporarily remove
the association.

define a new class Invoice < ActiveRecord::Base inside your fixture
generation code. if you don’t need any of Invoice’s methods then
you’re fine with just this

I need to be able to do a find on Invoice, which is why I have the
problem in the first place, so this probably won’t work.

Override ActiveRecord::Base.has_many to check if self is invoice and
the association name is the bad one. if it is, do nothing. if not let
the normal code run.

This may work and I will give it a shot. Thanks for the idea.

Wes


#7

The has_many part of the Invoice class is actually provided by a plugin.
And inside of that plugin, a set_table_name was being done to point to
the offensive table.

I got this to work by making the inclusion of that plugin dependent on
the setting of RAILS_ENV. Which isn’t pretty, but works.

Not sure that I will leave the change in, but I did (finally) get my
data loaded. Thankfully, I have Erb to make the fixtures as flexible as
I need them to be.

Thanks for all of the help, everyone.

Wes


#8

On 16 Jan 2009, at 20:44, Wes G. wrote:

When I call Invoice.find_all_by_im_invoice_state(“Paid”).each the
Invoice class is loaded.

When the Invoice class is loaded, all of its class level association
directives are executed. When it hits the one that doesn’t have a
backing table, it fails.

So it’s at the point where the class (not the data) is loaded that the
problem occurs ? You might be in a tight spot then because it’s going
to be hard to remove the association before the class exists. I can
think of two ways, neither of which is very nice.

define a new class Invoice < ActiveRecord::Base inside your fixture
generation code. if you don’t need any of Invoice’s methods then
you’re fine with just this

Override ActiveRecord::Base.has_many to check if self is invoice and
the association name is the bad one. if it is, do nothing. if not let
the normal code run.

Fred


#9

On Jan 16, 2009, at 4:54 PM, Wes G. wrote:

problem occurs ? You might be in a tight spot then because it’s going
to be hard to remove the association before the class exists.

Actually, the class needs to exist and I just need to temporarily
remove
the association.

Can you show us the has_many part of your Invoice class?

Product is a model in one of my client projects. I can do:
$ script/console
Loading development environment.
irb> Product
=> Product
irb> class Product; has_many :foobars; end
=> [:build_to_foobars]
irb> p=Product.find(:first)
=> #<Product:0x3edb0e0 @attributes={…}>
irb> p.methods.include?(‘foobars’)
=> true
irb> p.foobars
NameError: uninitialized constant Product::Foobar

Notice that there is no error when the:
has_many :foobars
gets processed. When I find a product, it has a foobars method, but
it doesn’t work.

Perhaps you are defining your association is a way that causes pre-
mature loading of the associated model?

the normal code run.

This may work and I will give it a shot. Thanks for the idea.

Wes

'Cause this seems like a lot of work that might not even be necessary.

-Rob

Rob B. http://agileconsultingllc.com
removed_email_address@domain.invalid