Rails 3.1 Advanced Has_many and belongs_to model joins - ruby-on-rails

I have two tables. Items, and Vendors. Items are sold by Vendors. So Item belongs_to :vendor and Vendor has_many :items. That works fine.
However, Items are not always manufactured by the Vendors that sell them, but sometimes they are. So I have a new column in my Item table called "manufacturer_id". Rather than generate a new model called Manufacturer that duplicates Vendor identically, I tried to do a complex has_many and belongs_to to define manufacturer.
See here:
class Item < ActiveRecord::Base
belongs_to :vendor
belongs_to :manufacturer, :class_name => "Vendor", :foreign_key => "manufacturer_id"
end
class Vendor < ActiveRecord::Base
has_many :items
has_many :manufactured_items, :class_name => "Item", :foreign_key => "manufacturer_id"
end
Populating manufacturer_id in the items table works as expected on Create commands:
Item.create(:manufacturer => Vendor.find_by_abbrev("INV"))
And I can even get the manufacturer as the operation
item.manufacturer
which returns:
<Vendor:0x007ff06684e398>
HOWEVER:
item.manufacturer.name
fails completely with a hard exeption and I get the error:
undefined method `name' for nil:NilClass
running
debug item.manufacturer
gives
--- !ruby/object:Vendor
attributes:
id: 181
name: Invitrogen
website: http://www.invitrogen.com/
created_at: 2012-01-08 01:39:07.486375000Z
updated_at: 2012-01-08 01:39:07.486375000Z
abbrev: INV
so item.manufacturer.name should return the name for that vendor object above, Vendor: 0x007ff06684e398.
What am I doing wrong here?
Also, once I get this working I'd like to be able to similarly call:
vendor.manufactured_items
to get all the items that have the manufacturer_id of that vendor. is there a straightforward way to do that too?
My last ditch effort may involve having to do:
manufacturer = Vendor.new(item.manufacturer)
But that seems totally wrong, and goes against the rails documentation here:
http://guides.rubyonrails.org/association_basics.html#self-joins
please help!

Okay, I've actually built a demo Rails 3.1 project for you and posted it on GitHub. I've included the console output in the README file to prove that calls like item.seller.name and item.manufacturer.name work, as well as round-trip calls like vendor.sold_items.first.manufacturer.name, which allow you to get the name of the manufacturer of the first sold item for a particular vendor, for example.
I think the root of the thing, as you noted, is that a vendor and a manufacturer, for all intents and purposes, are identical. For that reason I combined them simply into the Vendor class, and setup the foreign key relationships in such a way that it should work the way I think you want it to.
In particular, you should pay attention to the README file, which has the console session output that I ran to show it working. You'll also want to take a look at the two model classes and how their associations are defined, as well as the spec/factories.rb file for how it sets up the fake database data (I've included those below).
In re-reading your question this morning, I'm not sure what you were doing wrong, but you can probably chalk it up to a subtle error in your associations somewhere. It's probably a case of you being really close, but not quite there. :D
Here's some snipets from the code:
app/models/item.rb
class Item < ActiveRecord::Base
belongs_to :seller, :class_name => "Vendor"
belongs_to :manufacturer, :class_name => "Vendor"
end
app/models/vendor.rb
class Vendor < ActiveRecord::Base
has_many :sold_items, :class_name => "Item", :foreign_key => :seller_id
has_many :manufactured_items, :class_name => "Item", :foreign_key => :manufacturer_id
end
spec/factories.rb
require 'populator'
require 'faker'
FactoryGirl.define do
factory :vendor do |v|
v.name {Populator.words(1..3)}
v.website {Faker::Internet.domain_name}
v.abbr {(["ABC", "DEF", "GHI", "JKL", "MNO", "PQR", "STU", "VWX", "YZ1"])[rand(9)]}
end
factory :item do |i|
i.association :seller, :factory => :vendor
i.association :manufacturer, :factory => :vendor
i.name {Populator.words(3..5)}
end
end
lib/tasks/populator.rake
namespace :db do
desc "Erase database"
task :erase => :environment do
puts "Erasing..."
[Vendor, Item].each(&:delete_all)
end
desc "Erase and fill database"
task :populate => [:environment, :erase] do
require 'populator'
require 'faker'
puts "Populating: enjoy this random pattern generator while you wait..."
50.times{Factory.create(:vendor)}
Vendor.all.each do |v|
# This line actually has a bug in it that makes all `seller_id` and `manufacturer_id`
# columns always contain a value in the range 0..50. That means
# `rake db:populate` will only actually work the first time, but
# I think you get the idea of how this should work.
10.times{Factory.create(:item, :seller_id => (rand(50) + 1), :manufacturer_id => (rand(50) + 1))}
print (['\\', '/', '_', '|'])[rand(4)]
end
puts ""
end
end

Related

Rails 5 how to form association between tables on multiple shared attributes

In Rails 5, given a relationship between two tables that involves joining them on multiple shared attributes, how can I form an association between the models corresponding to these tables?
SQL:
SELECT *
FROM trips
JOIN stop_times ON trips.guid = stop_times.trip_guid AND trips.schedule_id = stop_times.schedule_id
I tried the following configuration, which works in general...
class Trip < ApplicationRecord
has_many :stop_times, ->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) }, :inverse_of => :trip, :primary_key => :guid, :foreign_key => :trip_guid, :dependent => :destroy
end
class StopTime < ApplicationRecord
belongs_to :trip, :inverse_of => :stop_times, :primary_key => :guid, :foreign_key => :trip_guid
end
Trip.first.stop_times.first #> StopTime object, as expected
Trip.first.stop_times.first.trip #> Trip object, as expected
... but when I try to use it in more advanced queries, it triggers ArgumentError: The association scope 'stop_times' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported....
Trip.joins(:stop_times).first #=> the unexpected ArgumentError
StopTime.joins(:trip).first #> StopTime object, as expected
I understand what the error is referencing, but I'm unsure of how to fix it.
EDIT:
I was hoping a single association would be sufficient, but it has been noted two different associations can do the job:
class Trip < ApplicationRecord
has_many :stop_times,
->(trip){ where("stop_times.schedule_id = ?", trip.schedule_id) },
:primary_key => :guid,
:foreign_key => :trip_guid # use trip.stop_times instead of trip.joined_stop_times to avoid error about missing attribute due to missing join clause
has_many :joined_stop_times,
->{ where("stop_times.schedule_id = trips.schedule_id") },
:class_name => "StopTime",
:primary_key => :guid,
:foreign_key => :trip_guid # use joins(:joined_stop_times) instead of joins(:stop_times) to avoid error about instance-specific association
end
Trip.first.stop_times
Trip.eager_load(:joined_stop_times).to_a.first.joined_stop_times # executes a single query
If anyone reading this knows how to use a single association, please at-mention me.
I don't think it is the right solution, but it can help. You can add another similar instance independent association that will be used for preloading only. It will work with :joins and :eager_load but not with :preload.
Note that :includes might internally use either :eager_load or :preload. So, :includes will not always work with that association. You should explicitly use :eager_load instead.
class Trip < ApplicationRecord
has_many :preloaded_stop_times,
-> { where("stop_times.schedule_id = trips.schedule_id") },
class_name: "StopTime",
primary_key: :guid,
foreign_key: :trip_guid
end
# Usage
trips = Trip.joins(:preloaded_stop_times).where(...)
# ...
# with :eager_load
trips = Trip.eager_load(:preloaded_stop_times)
trips.each do |trip|
stop_times = trip.preloaded_stop_times
# ...
end

Rails has_one/belongs_to associations not working as expected

I'm in the process of teaching myself Rails and I'm stumped as to why an association isn't working correctly. I think I'm missing something pretty basic but I can't snuff out exactly what.
I have two classes -- Builds and Equipment. Builds are made of 2 pieces of equipment, deviously titled position_1 and position_2. Here's what my definitions look like:
class Build < ActiveRecord::Base
has_one :position_1, :class_name => "Equipment"
has_one :position_2, :class_name => "Equipment"
attr_accessor :position_1, :position_2
end
and
class Equipment < ActiveRecord::Base
belongs_to :build, :foreign_key => :position_1
belongs_to :build, :foreign_key => :position_2
end
(Ignore for the moment that this could be handled by a relationship table to support any number of positions -- I'm basically trying to figure out how to have a class with two has_one relationships to another class.)
Now if I try and do something simple like this....
position_1 = Equipment.find(params[:build][:position_1])
position_2 = Equipment.find(params[:build][:position_2])
#build = Build.new
#build.position_1 = position_1
#build.position_2 = position_2
logger.debug("THE BUILD IS #{#build.inspect}")
I will have successfully created a build object with the equipment objects correctly assigned to the position_1 parameter, but the position_1 and position_2 fields of the build parameter are left nil.
logger.debug("THE EQUIPMENT IS #{#build.position_1}")
> EQUIPMENT IS #<Equipment:0x007fa0581705c0>
logger.debug("THE BUILD IS #{#build.inspect}")
> THE BUILD IS #<Build id: nil, position_1_id: nil, position_2_id: nil, created_at: "2013-05-27 18:00:32", updated_at: "2013-05-27 18:00:32">
What am I getting wrong here?
First, I would not include the associations as "attr_accessors" in the "parent" ActiveRecord. Rails creates the correct association field for Build for you.
When you create instances of the positions, like an instance of Equipment, the correct way to assign them to a new Build would be:
position_1 = Equipment.find(params[:build][:position_1])
position_2 = Equipment.find(params[:build][:position_2])
#build = Build.new
#build.position_1 << position_1
#build.position_2 << position_2
Here's good guide on Rails associations.
Figured it out -- I had misunderstood part of how Rails does associations -- what I really needed was...
class Equipment < ActiveRecord::Base
has_many :builds, :foreign_key => :position_1
has_many :builds, :foreign_key => :position_2
end
class Build < ActiveRecord::Base
belongs_to :position_1, :class_name => "Equipment", :foreign_key => "position_1_id"
belongs_to :position_2, :class_name => "Equipment", :foreign_key => "position_2_id"
end
With this in place everything works as I expected.

loaded? always returns true for belongs_to associations?

I have some geography related classed defined as follows:
class Location < ActiveRecord::Base
end
class State < Location
has_many :geographies
has_many :cities
has_many :counties
end
class County < Location
belongs_to :state
has_many :geographies
has_many :cities, :through => :geographies, :uniq => true
end
class City < Location
belongs_to :state
has_many :geographies
has_many :counties, :through => :geographies, :uniq => true
end
class Geography < ActiveRecord::Base
belongs_to :state
belongs_to :city
belongs_to :county
end
The following console output demonstrates the problem that I'm having.
>> County.first.cities.loaded?
=> false
>> County.first.state.loaded?
=> true
Looking in the logs I see that when I call state.loaded? it runs a SQL statement to load the state, i.e., the state was not loaded until I "touched" it. However, when I call cities.loaded? no SQL is executed and false is returned. This behavior seems inconsistent to me. I searched around a bit on the interwebs and couldn't find anything regarding this so I'm guessing it's my mistake, though, I don't see how.
Any help is much appreciated.
Thanks in advance.
I think it comes down to the type of relationship being used and the interaction with lazy loading.
When you call County.first.state, Rails will load the state object that belongs to the County - there is only one, and therefore a call to .state is actually a call to a 'concrete' object in the database that can be loaded.
However, when you call County.first.cities, you are actually calling the relationship collection that belongs to the county object. As you don't actually call on a specific object or set of conditions, Rails is smart enough not to load the collection.
If you said something like:
County.first.cities.all
County.first.cities.each
County.first.cities.first
Rails would then kick-off the SQL statement and load the data.
I found that you can use the Object method instance_variable_get to check to see if a belongs_to association is loaded without triggering the association to be loaded during your check. So...
>> c = County.first
=> #<County id: 1, ...>
>> c.instance_variable_get("#state")
=> nil
>> c.state
=> #<State id: 1, ...>
>> c.instance_variable_get("#state")
=> #<State id: 1, ...>
>> c = County.first(:include => :state)
=> #<County id: 1, ...>
>> c.instance_variable_get("#state")
=> #<State id: 1, ...>

Many-to-many relationship with the same model in rails?

How can I make a many-to-many relationship with the same model in rails?
For example, each post is connected to many posts.
There are several kinds of many-to-many relationships; you have to ask yourself the following questions:
Do I want to store additional information with the association? (Additional fields in the join table.)
Do the associations need to be implicitly bi-directional?
(If post A is connected to post B, then post B is also connected to post A.)
That leaves four different possibilities. I'll walk over these below.
For reference: the Rails documentation on the subject. There's a section called “Many-to-many”, and of course the documentation on the class methods themselves.
Simplest scenario, uni-directional, no additional fields
This is the most compact in code.
I'll start out with this basic schema for your posts:
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
For any many-to-many relationship, you need a join table. Here's the schema for that:
create_table "post_connections", :force => true, :id => false do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
end
By default, Rails will call this table a combination of the names of the two tables we're joining. But that would turn out as posts_posts in this situation, so I decided to take post_connections instead.
Very important here is :id => false, to omit the default id column. Rails wants that column everywhere except on join tables for has_and_belongs_to_many. It will complain loudly.
Finally, notice that the column names are non-standard as well (not post_id), to prevent conflict.
Now in your model, you simply need to tell Rails about these couple of non-standard things. It will look as follows:
class Post < ActiveRecord::Base
has_and_belongs_to_many(:posts,
:join_table => "post_connections",
:foreign_key => "post_a_id",
:association_foreign_key => "post_b_id")
end
And that should simply work! Here's an example irb session run through script/console:
>> a = Post.create :name => 'First post!'
=> #<Post id: 1, name: "First post!">
>> b = Post.create :name => 'Second post?'
=> #<Post id: 2, name: "Second post?">
>> c = Post.create :name => 'Definitely the third post.'
=> #<Post id: 3, name: "Definitely the third post.">
>> a.posts = [b, c]
=> [#<Post id: 2, name: "Second post?">, #<Post id: 3, name: "Definitely the third post.">]
>> b.posts
=> []
>> b.posts = [a]
=> [#<Post id: 1, name: "First post!">]
You'll find that assigning to the posts association will create records in the post_connections table as appropriate.
Some things to note:
You can see in the above irb session that the association is uni-directional, because after a.posts = [b, c], the output of b.posts does not include the first post.
Another thing you may have noticed is that there is no model PostConnection. You normally don't use models for a has_and_belongs_to_many association. For this reason, you won't be able to access any additional fields.
Uni-directional, with additional fields
Right, now... You've got a regular user who has today made a post on your site about how eels are delicious. This total stranger comes around to your site, signs up, and writes a scolding post on regular user's ineptitude. After all, eels are an endangered species!
So you'd like to make clear in your database that post B is a scolding rant on post A. To do that, you want to add a category field to the association.
What we need is no longer a has_and_belongs_to_many, but a combination of has_many, belongs_to, has_many ..., :through => ... and an extra model for the join table. This extra model is what gives us the power to add additional information to the association itself.
Here's another schema, very similar to the above:
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
create_table "post_connections", :force => true do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
t.string "category"
end
Notice how, in this situation, post_connections does have an id column. (There's no :id => false parameter.) This is required, because there'll be a regular ActiveRecord model for accessing the table.
I'll start with the PostConnection model, because it's dead simple:
class PostConnection < ActiveRecord::Base
belongs_to :post_a, :class_name => :Post
belongs_to :post_b, :class_name => :Post
end
The only thing going on here is :class_name, which is necessary, because Rails cannot infer from post_a or post_b that we're dealing with a Post here. We have to tell it explicitly.
Now the Post model:
class Post < ActiveRecord::Base
has_many :post_connections, :foreign_key => :post_a_id
has_many :posts, :through => :post_connections, :source => :post_b
end
With the first has_many association, we tell the model to join post_connections on posts.id = post_connections.post_a_id.
With the second association, we are telling Rails that we can reach the other posts, the ones connected to this one, through our first association post_connections, followed by the post_b association of PostConnection.
There's just one more thing missing, and that is that we need to tell Rails that a PostConnection is dependent on the posts it belongs to. If one or both of post_a_id and post_b_id were NULL, then that connection wouldn't tell us much, would it? Here's how we do that in our Post model:
class Post < ActiveRecord::Base
has_many(:post_connections, :foreign_key => :post_a_id, :dependent => :destroy)
has_many(:reverse_post_connections, :class_name => :PostConnection,
:foreign_key => :post_b_id, :dependent => :destroy)
has_many :posts, :through => :post_connections, :source => :post_b
end
Besides the slight change in syntax, two real things are different here:
The has_many :post_connections has an extra :dependent parameter. With the value :destroy, we tell Rails that, once this post disappears, it can go ahead and destroy these objects. An alternative value you can use here is :delete_all, which is faster, but will not call any destroy hooks if you are using those.
We've added a has_many association for the reverse connections as well, the ones that have linked us through post_b_id. This way, Rails can neatly destroy those as well. Note that we have to specify :class_name here, because the model's class name can no longer be inferred from :reverse_post_connections.
With this in place, I bring you another irb session through script/console:
>> a = Post.create :name => 'Eels are delicious!'
=> #<Post id: 16, name: "Eels are delicious!">
>> b = Post.create :name => 'You insensitive cloth!'
=> #<Post id: 17, name: "You insensitive cloth!">
>> b.posts = [a]
=> [#<Post id: 16, name: "Eels are delicious!">]
>> b.post_connections
=> [#<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>]
>> connection = b.post_connections[0]
=> #<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>
>> connection.category = "scolding"
=> "scolding"
>> connection.save!
=> true
Instead of creating the association and then setting the category separately, you can also just create a PostConnection and be done with it:
>> b.posts = []
=> []
>> PostConnection.create(
?> :post_a => b, :post_b => a,
?> :category => "scolding"
>> )
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> b.posts(true) # 'true' means force a reload
=> [#<Post id: 16, name: "Eels are delicious!">]
And we can also manipulate the post_connections and reverse_post_connections associations; it will neatly reflect in the posts association:
>> a.reverse_post_connections
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> a.reverse_post_connections = []
=> []
>> b.posts(true) # 'true' means force a reload
=> []
Bi-directional looped associations
In normal has_and_belongs_to_many associations, the association is defined in both models involved. And the association is bi-directional.
But there is just one Post model in this case. And the association is only specified once. That's exactly why in this specific case, associations are uni-directional.
The same is true for the alternative method with has_many and a model for the join table.
This is best seen when simply accessing the associations from irb, and looking at the SQL that Rails generates in the log file. You'll find something like the following:
SELECT * FROM "posts"
INNER JOIN "post_connections" ON "posts".id = "post_connections".post_b_id
WHERE ("post_connections".post_a_id = 1 )
To make the association bi-directional, we'd have to find a way to make Rails OR the above conditions with post_a_id and post_b_id reversed, so it will look in both directions.
Unfortunately, the only way to do this that I know of is rather hacky. You'll have to manually specify your SQL using options to has_and_belongs_to_many such as :finder_sql, :delete_sql, etc. It's not pretty. (I'm open to suggestions here too. Anyone?)
To answer the question posed by Shteef:
Bi-directional looped associations
The follower-followee relationship among Users is a good example of a Bi-directional looped association. A User can have many:
followers in its capacity as followee
followees in its capacity as follower.
Here's how the code for user.rb might look:
class User < ActiveRecord::Base
# follower_follows "names" the Follow join table for accessing through the follower association
has_many :follower_follows, foreign_key: :followee_id, class_name: "Follow"
# source: :follower matches with the belong_to :follower identification in the Follow model
has_many :followers, through: :follower_follows, source: :follower
# followee_follows "names" the Follow join table for accessing through the followee association
has_many :followee_follows, foreign_key: :follower_id, class_name: "Follow"
# source: :followee matches with the belong_to :followee identification in the Follow model
has_many :followees, through: :followee_follows, source: :followee
end
Here's how the code for follow.rb:
class Follow < ActiveRecord::Base
belongs_to :follower, foreign_key: "follower_id", class_name: "User"
belongs_to :followee, foreign_key: "followee_id", class_name: "User"
end
The most important things to note are probably the terms :follower_follows and :followee_follows in user.rb. To use a run of the mill (non-looped) association as an example, a Team may have many :players through :contracts. This is no different for a Player, who may have many :teams through :contracts as well (over the course of such Player's career). But in this case, where only one named model exists (i.e. a User), naming the through: relationship identically (e.g. through: :follow, or, like was done above in the posts example, through: :post_connections) would result in a naming collision for different use cases of (or access points into) the join table. :follower_follows and :followee_follows were created to avoid such a naming collision. Now, a User can have many :followers through :follower_follows and many :followees through :followee_follows.
To determine a User’s :followees (upon an #user.followees call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the follower (i.e. foreign_key: :follower_id) through: such User’s :followee_follows. To determine a User’s :followers (upon an #user.followers call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the followee (i.e. foreign_key: :followee_id) through: such User’s :follower_follows.
If anyone came here to try to find out how to create friend relationships in Rails, then I would refer them to what I finally decided to use, which is to copy what 'Community Engine' did.
You can refer to:
https://github.com/bborn/communityengine/blob/master/app/models/friendship.rb
and
https://github.com/bborn/communityengine/blob/master/app/models/user.rb
for more information.
TL;DR
# user.rb
has_many :friendships, :foreign_key => "user_id", :dependent => :destroy
has_many :occurances_as_friend, :class_name => "Friendship", :foreign_key => "friend_id", :dependent => :destroy
..
# friendship.rb
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
Inspired by #Stéphan Kochen,
this could work for bi-directional associations
class Post < ActiveRecord::Base
has_and_belongs_to_many(:posts,
join_table: 'post_connections',
foreign_key: 'post_a_id',
association_foreign_key: 'post_b_id')
has_and_belongs_to_many(:reversed_posts,
class_namy: Post,
join_table: 'post_connections',
foreign_key: 'post_b_id',
association_foreign_key: 'post_a_id')
end
then post.posts && post.reversed_posts should both works, at least worked for me.
For bi-directional belongs_to_and_has_many, refer to the great answer already posted, and then create another association with a different name, the foreign keys reversed and ensure that you have class_name set to point back to the correct model. Cheers.
If anyone had issues getting the excellent answer to work, such as:
(Object doesn't support #inspect)
=>
or
NoMethodError: undefined method `split' for :Mission:Symbol
Then the solution is to replace :PostConnection with "PostConnection", substituting your classname of course.

Multiple has_many_polymorphs in one model

I'm trying to define multiple polymorphic relations (has_many_polymorphs plugin) from a single parent to same children.
Note has many viewers
Note has many editors
Viewers could be either Users or Groups
Editors could be either Users or Groups
Permission is the association model with note_id, viewer_id, viewer_type, editor_id, editor_type fields
Everything works out as expect as long as I have only one has_many_polymorphs relation defined in Note model
class User < ActiveRecord::Base; end
class Group < ActiveRecord::Base; end
class Note < ActiveRecord::Base
has_many_polymorphs :viewers, :through => :permissions, :from => [:users, :groups]
end
class Permission < ActiveRecord::Base
belongs_to :note
belongs_to :viewer, :polymorphic => true
end
Note.first.viewers << User.first # => [#<User id: 1, ....>]
Note.first.viewers << Group.first # => [#<User id: 1, ....>, #<Group ...>]
Note.first.viewers.first # => #<User ....>
Note.first.viewers.second # => #<Group ....>
Now, problems start to appear when I add the second relation
class Note < ActiveRecord::Base
has_many_polymorphs :viewers, :through => :permissions, :from => [:users, :groups]
has_many_polymorphs :editors, :through => :permissions, :from => [:users, :groups]
end
class Permission < ActiveRecord::Base
belongs_to :note
belongs_to :viewer, :polymorphic => true
belongs_to :editor, :polymorphic => true
end
Note.first.viewers << User.first # => [#<User id: ....>]
# >>>>>>>>
Note.first.editors << User.first
NoMethodError: You have a nil object when you didn't expect it!
The error occurred while evaluating nil.constantize
... vendor/plugins/has_many_polymorphs/lib/has_many_polymorphs/base.rb:18:in `instantiate'
I've tried refining the definition of has_many_polymorphs but it didn't work. Not even with an STI model for ViewPermission < Permission, and EditPermission < Permission.
Any thoughts / workarounds / issue pointers are appreciated.
Rails 2.3.0
Dont you need to add
has_many :permissions
to your Note.
FYI. I used has_many_polymorphs once but then dropped it, it wasn't working as expected.
Can you post the schema that you are using for Permission? My guess is the root of the problem lies there, you need to have multiple type, id pairs in the schema since you have two different belongs_to in the definition.
Edit:
I see you have posted the question on github as well. Not sure if you tried using the Double sided polymorphism. You probably have... like I said, I was not impressed by this plugin, as it brought in some instability when I used it.
== Double-sided polymorphism
Double-sided relationships are defined on the join model:
class Devouring < ActiveRecord::Base
belongs_to :guest, :polymorphic => true
belongs_to :eaten, :polymorphic => true
acts_as_double_polymorphic_join(
:guests =>[:dogs, :cats],
:eatens => [:cats, :birds]
)
end
Now, dogs and cats can eat birds and cats. Birds can't eat anything (they aren't <tt>guests</tt>) and dogs can't be
eaten by anything (since they aren't <tt>eatens</tt>). The keys stand for what the models are, not what they do.
#Tamer
I was getting the same error. The problem was that has_many_polymorphs creates the record in the join table using mass association and was failing. I added attr_accessible :note_id, :editor_id, and :editor_type to my Permission class and it worked afterwards. (Note: I substituted your model names for mine.)
I haven't looked too much into it, but I'd be curious if there's a way to alter this behavior. I'm fairly new to this framework and letting anything sensitive (like an Order-Payment association) be mass-assigned seems like asking to shoot myself in the foot. Let me know if this fixed your problem, and if you figure anything else out.
Best,
Steve

Resources