Rails: possible to use fallback for duplicate polymorphic associations? - ruby-on-rails

Is there a way to define a polymorphic association that pulls from another poly assoc, but also then reverts to a backup poly assoc if the first one is empty? Maybe like this?
class Reservation < ActiveRecord::Base
has_many :trip_itinerary_entries, :through => :trips, :source => :itinerary_entries
has_many :template_itinerary_entries, :through => :templates, :source => :itinerary_entries
has_many :entries, :from => :trip_itinerary_entries, :backup => :template_itinerary_entries
Thanks!

I would set up the two polymorphic associations (in my model and the required columns in my DB) and then create a method for getting both of the items (you don't want to override them method otherwise you would't be able to write new relationships).
def get_entries
if entries.any?
entries
else
template_itinerary_entries
end
end
This way you can still use entries when you need to add items.
reserve = Reservation.new
reserve.entries << Entry.create()
But you could use the get_entries method to get either of the items.
reserve.get_entries
=> [template_itinerary_entries] # if there are no entries.

Related

A dynamic condition for an `has_many : through` Record Association

I am using Ruby on Rails 3.0.7 and I would like to set an has_many : through dynamic condition.
In my model file I have:
class Article < ActiveRecord::Base
has_many :article_category_relationships,
:class_name => 'Article::Categories::ArticleRelationship',
:foreign_key => 'article_id',
:autosave => true,
:dependent => :destroy
# Here should be the dynamic condition statement (read below for more
# information about this)
has_many :article_categories,
:through => :article_category_relationships,
:source => :article_category,
:dependent => :destroy
end
In the related Article::Categories::ArticleRelationship database table I have also a created_by_user_id column (other columns are article_id and category_id) which represents the id of the user who created the relationship.
So, in order to retrieve article categories related to a user, in the above Record Association code I would like to filter :article_category_relationships by passing a dynamic condition which depends by that user id value. Otherwise, if I pass no id value, a default value should permit to retrieve all article categories by using the #article.article_categories code.
Is it possible? If so, how can I code that in the Record Association statement?
I believe a scope in your category model might be what you're looking for — something like this:
scope :by_user, lambda {|user|
unless user.nil?
where('article_category_relationships.created_by_user_id IS ?', user.id).
join(:article_category_relationships)
end
}
This allows you to call #article.article_categories.by_user(#user).

ActiveRecord Association without Foreign Key

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.

Rails polymorphic associations, two assoc types in one class

Consider a class:
class Link < ActiveRecord::Base
has_many :link_votes, :as => :vote_subject, :class_name => 'Vote'
has_many :spam_votes, :as => :vote_subject, :class_name => 'Vote'
end
The problem is, when I'm adding a new vote with #link.link_votes << Vote.new the vote_subject_type is 'Link', while I wish it could be 'link_votes' or something like that. Is this an AR limitation or is there a way to workaround this thing?
I've actually found one related answer, but I'm not quite sure about what it says: Polymorphic Association with multiple associations on the same model
Sounds like you want to use Single Table Inheritance - this will allow you to have two different types of Votes. This will add a 'type' column to the votes table that you will then access as a LinkVote or SpamVote
class SpamVote << Vote
...
end
Along those lines.
class Link < ActiveRecord::Base
has_many :link_votes, :as => :vote_subject
has_many :spam_votes, :as => :vote_subject
end
In the votes table you'd see columns like:
id, type, vote_subject_type, vote_subject_id, etc.
Do more research on STI and I bet you'll find your answer.

rails polymorphic association (legacy database)

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.

Rails polymorphic many to many association

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

Resources