I have a table ProductOrder that has multiple foreign keys which link to a SecurityUser table.
I'm trying to figure out a way to configure my model so that I can load up multiple SecurityUser foreign keys when I return a ProductOrder from a query.
Here is a section of my product_order.rb:
class ProductOrder < ActiveRecord::Base
self.table_name = 'product_order'
belongs_to :security_user, :class_name => 'SecurityUser', :foreign_key => :ordered_by_client_user
belongs_to :security_user, :class_name => 'SecurityUser', :foreign_key => :staff_engager1
belongs_to :security_user, :class_name => 'SecurityUser', :foreign_key => :staff_engager2
belongs_to :security_user, :class_name => 'SecurityUser', :foreign_key => :last_updated_by_user
Here is the corresponding section of security_user.rb:
class SecurityUser < ActiveRecord::Base
self.table_name = 'security_user'
has_many :product_orders, :class_name => 'ProductOrder'
These models were generated using a gem designed to create models from a DB schema.
So what I would like to be able to do is load a ProductOrder from an ActiveRecord query and from that result I could access information from some of the SecurityUser objects, such as ordered_by_client_user or staff_engager1.
I'm re-writing an application from Java to RoR, so I previously used Hibernate and JPA query and I was used to having all of the objects load up with the query. I've tried the following with the same result every time:
ProductOrder.includes(:security_user)
ProductOrder.joins(:security_user)
ProductOrder.eager_load(:security_user)
ProductOrder.preload(:security_user)
But the problem is that RoR doesn't know which foreign key to load. I'd like to avoid having to make "get" methods on the model just to access these foreign keys, but if that's what it takes then so be it. My current workaround is just building dynamic, messy .joins() statements.
Any suggestions or insight is greatly appreciated.
The problem is that you are using same name i.e, security_user in your ProductOrder model for all the associations.So it probably won't work.
You should change those names(1.e, security_user) to some meaningful and different names to get it worked.
Related
I'm starting out with the whole (wonderful) idea of database associations in Rails but I'm having problems because I'm working with an existing database that does not conform to Rails standards and cannot figure out how to name the associations. There are a couple of similar posts, but I can't wrap my head around the naming for my particular situation which is as follows:
table book with book.formatId looks up values in book_format.id
So foreign key book.formatId
My models are named: Book and BookFormat (I read that you use camelCase when your tables are separated by underscore).
Under the Book model I have this:
has_one :bookFormat, :foreign_key => 'book_format.id' # not sure if this format table.field is correct or I have to use something else here. Also not sure about the bookFormat, should it be BookFormat or bookformat?
The BookFormat model has this:
belongs_to :book
But when I try to do
book = Book.first
book.bookFormat.empty?
I get an error of method not found for bookFormat. So obviously something's wrong, but I can't figure out where.
A second part of the question is the use of many to many relationships. Example:
Tables
book, book_subjects, book_subjects2title
book_subjects.id => book_subjects2title.pId
book.id => book_subjects2title.bookId
I'm reading the Beginning Rails 3 book from Apress (which is a great book) but it's not very clear on all this or I'm just not getting it.
Thanks.
Since the book stores the formatId on it, you should use belongs_to, and change the foreign key as such:
belongs_to :book_format, :class_name => 'BookFormat', :foreign_key => 'formatId'
For the table name, i did some quick searching, and found the following method: set_table_name
So you should be able to add it at the top of your model, like so:
class Book < ActiveRecord::Base
set_table_name 'book'
# the rest of book model code
end
class BookFormat < ActiveRecord::Base
set_table_name 'book_format'
# rest of book_format model code
end
Normally rails uses plural table names, so hence why you need to specify it there.
agmcleod put me on the right track, so here's the full answer in the hopes that this helps other people with similar problems:
I created the model with a different name for easier reading. So model Books will have:
class Books < ActiveRecord::Base
set_table_name 'bookpedia'
belongs_to :format, :foreign_key => 'formatId' # we need to specify the foreign key because it is not the rails default naming
has_many :author2title, :foreign_key => 'bookId'
has_many :author, :through => :author2title
end
model Format:
class Format < ActiveRecord::Base
set_table_name 'book_format'
has_many :books
end
Then an instance of the class will have the format method:
#book = Books.first
#book.format # Hardcover
For many to many relationships I'll paste the syntax here as well since that took me a while to figure out:
class Author < ActiveRecord::Base
set_table_name 'book_author'
has_many :author2title, :foreign_key => 'pId' # junction table
has_many :books, :through => :author2title # establishing the relationship through the junction table
end
This is the actual junction table:
class Author2title < ActiveRecord::Base
set_table_name 'book_author2title'
belongs_to :author, :foreign_key => 'pId' # foreign key needs to be here as well
belongs_to :books, :foreign_key => 'bookId'
end
The book model above has the necessary entries for this many to many relationship already in it.
If anyone needs clarification on this I'd be happy to oblige since I struggled for more than a day to figure this out so would be glad to be of help.
Cheers.
I am trying to build a relationship model between users. A user can either initiate a relation, or receive a relation from another user. Therefore, the relations table in the db has the foreign keys "initiator_id" and "recipient_id".
Now, I can figure what relations the user initiated or received using the following associations:
has_many :initiated_relations, :foreign_key => :initiator_id, :class_name => 'Relation', :dependent => :destroy
has_many :received_relations, :foreign_key => :recipient_id, :class_name => 'Relation', :dependent => :destroy
What I am trying to do, is build an association that will fetch me all relations that belong to a user (either initiated or received). Trying the following does not work, and complains about the lack of "user_id" field:
has_many :relations, :conditions => 'recipient_id = #{id} or initiator_id = #{id}'
How can I create an association that is solely based on the conditions field, without looking for the default foreign_key? Or is there perhaps a completely different approach to solving this?
From your comments to #neutrino's answer I understand, that you only need this "relation" for read only operations. If you're on Rails 3 you can utilize the fact, that it uses lazy fetching. The where() method returns ActiveRecord::Relation object, which you can later modify. So you can define a method like this:
def User < ActiveRecord::Base
def all_relations
Relation.where("initiator_id => ? OR recipient_id = ?", id, id)
end
end
And then you can do:
User.all_relations.where(:confirmed => true).all
Well, I can think of using finder_sql for that:
has_many :relations, :finder_sql => 'select * from relations right outer join users
on relations.recipient_id = #{id} or relations.initiator_id = #{id}'
Apart from that, you can just write a method that will return a united array of the two relations associations', but you will lose the advantage of an association interface (phew).
Perhaps someone will come up with a better solution.
I have the following setup:
class Publication < ActiveRecord::Base
has_and_belongs_to_many :authors, :class_name=>'Person', :join_table => 'authors_publications'
has_and_belongs_to_many :editors, :class_name=>'Person', :join_table => 'editors_publications'
end
class Person < ActiveRecord::Base
has_and_belongs_to_many :publications
end
With this setup I can do stuff like Publication.first.authors. But if I want to list all publications in which a person is involved Person.first.publications, an error about a missing join table people_publications it thrown. How could I fix that?
Should I maybe switch to separate models for authors and editors? It would however introduce some redundancy to the database, since a person can be an author of one publication and an editor of another.
The other end of your associations should probably be called something like authored_publications and edited_publications with an extra read-only publications accessor that returns the union of the two.
Otherwise, you'll run in to sticky situations if you try to do stuff like
person.publications << Publication.new
because you'll never know whether the person was an author or an editor. Not that this couldn't be solved differently by slightly changing your object model.
There's also hacks you can do in ActiveRecord to change the SQL queries or change the behavior of the association, but maybe just keep it simple?
I believe you should have another association on person model
class Person < ActiveRecord::Base
# I'm assuming you're using this names for your foreign keys
has_and_belongs_to_many :author_publications, :foreign_key => :author_id
has_and_belongs_to_many :editor_publications, :foreign_key => :editor_id
end
I am using a legacy database, so i do not have any control over the datamodel. They use a lot of polymorphic link/join-tables, like this
create table person(per_ident, name, ...)
create table person_links(per_ident, obj_name, obj_r_ident)
create table report(rep_ident, name, ...)
where obj_name is the table-name, and obj_r_ident is the identifier.
So linked reports would be inserted as follows:
insert into person(1, ...)
insert into report(1, ...)
insert into report(2, ...)
insert into person_links(1, 'REPORT', 1)
insert into person_links(1, 'REPORT', 2)
And then person 1 would have 2 linked reports, 1 and 2.
I can understand possible benefits having a datamodel like this, but i mostly see one big shortcoming: using constraints is not possible to ensure data integrity. But alas, i cannot change this anymore.
But to use this in Rails, i was looking at polymorphic associations but did not find a nice way to solve this (since i cannot change the columns-names, and did not readily find a way to do that).
I did come up with a solution though. Please provide suggestions.
class Person < ActiveRecord::Base
set_primary_key "per_ident"
set_table_name "person"
has_and_belongs_to_many :reports,
:join_table => "person_links",
:foreign_key => "per_ident",
:association_foreign_key => "obj_r_ident",
:conditions => "OBJ_NAME='REPORT'"
end
class Report < ActiveRecord::Base
set_primary_key "rep_ident"
set_table_name "report"
has_and_belongs_to_many :persons,
:join_table => "person_links",
:foreign_key => "obj_r_ident",
:association_foreign_key => "per_ident",
:conditions => "OBJ_NAME='REPORT'"
end
This works, but i wonder if there would be a better solution, using polymorphic associations.
At least as of Rails 4.2.1, you can pass foreign_type to a belongs_to declaration to specify the name of the column to be used for the 'type' of the polymorphic association
http://apidock.com/rails/v4.2.1/ActiveRecord/Associations/ClassMethods/belongs_to
You can override the column names, sure, but a quick scan of the Rails API didn't show me anywhere to override the polymorphic 'type' column. So, you wouldn't be able to set that to 'obj_name'.
It's ugly, but I think you'll need a HABTM for each type of object in your table.
You might be able to do something like this:
{:report => 'REPORT'}.each do |sym, text|
has_and_belongs_to_many sym,
:join_table => "person_links",
:foreign_key => "obj_r_ident",
:association_foreign_key => "per_ident",
:conditions => "OBJ_NAME='#{text}'"
end
At least that way all the common stuff stays DRY and you can easily add more relationships.
I'm trying setup a generic sort of web of related objects. Let say I have 4 models.
Book
Movie
Tag
Category
I would like to able to do:
book = Book.find(1)
book.relations << Tag.find(2)
book.relations << Category.find(3)
book.relations #=> [Tag#2, Category#3]
movie = Movie.find(4)
movie.relations << book
movie.relations << Tag.find(5)
movie.relations #=> [Book#1, Tag#5]
Basically I want to be able to take any 2 objects of any model class (or model class that I allow) and declare that they are related.
Obviously I don't want to create a huge mess of join tables. This seems like it's not quite a has many through association, and not quite a polymorphic association.
Is this something that Rails can support via it's association declarations or should I be rolling my own logic here?
Support for polymorphism has improved dramatically since the early days. You should be able to achieve this in Rails 2.3 by using a single join table for all your models -- a Relation model.
class Relation
belongs_to :owner, :polymorphic => true
belongs_to :child_item, :polymorphic => true
end
class Book
has_many :pwned_relations, :as => :owner, :class_name => 'Relation'
has_many :pwning_relations, :as => :child_item, :class_name => 'Relation'
# and so on for each type of relation
has_many :pwned_movies, :through => :pwned_relations,
:source => :child_item, :source_type => 'Movie'
has_many :pwning_movies, :through => :pwning_relations,
:source => :owner, :source_type => 'Movie'
end
A drawback of this kind of data structure is that you are forced to create two different roles for what may be an equal pairing. If I want to see all the related movies for my Book, I have to add the sets together:
( pwned_movies + pwning_movies ).uniq
A common example of this problem is the "friend" relationship in social networking apps.
One solution used by Insoshi, among others, is to register an after_create callback on the join model ( Relation, in this case ), which creates the inverse relationship. An after_destroy callback would be similarly necessary, but in this way at the cost of some additional DB storage you can be confident that you will get all your related movies in a single DB query.
class Relation
after_create do
unless Relation.first :conditions =>
[ 'owner_id = ? and owner_type = ? and child_item_id = ? and child_item_type = ?', child_item_id, child_item_type, owner_id, owner_type ]
Relation.create :owner => child_item, :child_item => owner
end
end
end
I have come up with a bit of solution. I'm not sure it's the best however. It seems you cannot have a polymorphic has_many through.
So, I fake it a bit. But it means giving up the association proxy magic that I love so much, and that makes me sad. In a basic state, here is how it works.
book = Book.find(1)
book.add_related(Tag.find(2))
book.add_related(Category.find(3))
book.related #=> [Tag#2, Category#3]
book.related(:tags) #=> [Tag#2]
I wrapped it up in a reusable module, that can be added to any model class with a single has_relations class method.
http://gist.github.com/123966
I really hope I don;t have to completely re-implement the association proxy to work with this though.
I think the only way to do it exactly as you described is the join tables. It's not so bad though, just 6, and you can pretty much set-and-forget them.
depending on how closesly related your movies/books db tables are
what if you declared
class Items < ActiveRecord::Base
has_many :tags
has_many :categories
has_and_belongs_to_many :related_items,
:class => "Items",
:join_table => :related_items,
:foreign_key => "item_id",
:associated_foreign_key => "related_item_id"
end
class Books < Items
class Movies < Items
make sure you put type in your items table