Recreating this custom query using has_many - ruby-on-rails

I'm hoping this will be an easy one :) I've been stuffing around for hours playing with the has_many options trying to emulate this:
has_many :pages, :finder_sql => %q(SELECT * FROM `pages` LEFT OUTER JOIN `component_instances` ON `component_instances`.instance_id = `pages`.id AND `component_instances`.instance_type = 'Page' WHERE `component_instances`.parent_id = #{id})
It's basically a polymorphic join so there is the component_instances table that acts as a central structure and has different types of things hanging off of it. It's a nested set (not that that matters in this case).
The problem seems to be that has_many doesn't allow me to manipulate the join conditions. And I can't nullify the foreign key join condition that's automatically made.
The above code works but I want to use scopes on the results, and that's not possible with a custom query.
Any help would be greatly appreciated :)
Cheers,
Brendon

You can do this with the :through option.
has_many :pages, :through => :component_instances, :source => :parent, :source_type => 'Page'

Thanks for the lead Michael, in the end this worked:
has_one :page_set, :foreign_key => :parent_id
belongs_to :instance, :polymorphic => true, :dependent => :destroy
has_many :pages, :through => :page_set, :source => :instance, :source_type => 'Page', :extend => LearningCaveFilterExtension
but it's a little bit sketchy as the :page_set method actually returns something completely wrong. Ideally it should return self but I needed to put :parent_id as the foreign key so that the SQL generated from the has_many pages declaration was correct (using :id would be the correct way but then that screws up the :pages method. :) My mind hasn't quite gotten around what's going on here, but at least it works and scoping works too :)
Thanks for the help, and if you have any explanations as to why that works, please let me know :)

Lol, sleeping on it gave me the answer:
class PageSet < ActiveRecord::Base
unloadable
set_table_name "component_instances"
has_many :children, :foreign_key => :parent_id, :class_name => 'PageSet'
belongs_to :instance, :polymorphic => true, :dependent => :destroy
has_many :pages, :through => :children, :source => :instance, :source_type => 'Page'
end
The entities with the link through parent_id are rightly children, I was just referring to them in the wrong way, but AR didn't raise any errors :)

Related

Self-referencing models in Rails 3

I have an Entity model and I want to display connections between the Entities. ie, Entity 1 is connected to Entity 2.
My thinking, right now, is to create a join model between the two called Connection and have it work like a traditional rails join table. Except have the columns be entity_one_id and entity_two_id, then establish a many-to-many relationship between Entity and Connection.
This seems like a really not-elegant way to do this. I was wondering if anyone had any better ideas? Maybe something more rails-esque that I'm just not seeing?
That's the most-common way to do it. If an entity is only ever connected to one other model, you could use a linked-list, tree-like structure.
Check out Ryan Bates' Railscast on self-joining models. It deals with a social-network-like system, but it still has principles you'll need and provides a great starting point
You could use this implementation:
class User < ActiveRecord::Base
has_many :friends, :through => :friendships, :conditions => "status = 'accepted'"
has_many :requested_friends, :through => :friendships, :source => :friend, :conditions => "status = 'requested'", :order => :created_at
has_many :pending_friends, :through => :friendships, :source => :friend, :conditions => "status = 'pending'", :order => :created_at
has_many :friendships, :dependent => :destroy
end
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User"
end

Rails, has_many polymorphic association to one model

I would like to have one model (event) that has multiple polymorphic HABTM associations to the same user model.
It should work something like this:
Event < ActiveRecord::Base
has_many :admin_users, :through => :event_users, :source => :userable, :source_type => 'User'
has_many :participating_users, :through => :event_users, :source => :userable, :source_type => 'User'
has_many :paid_users, :through => :event_users, :source => :userable, :source_type => 'User'
has_many :done_users, :through => :event_users, :source => :userable, :source_type => 'User'end
class EventUser < ActiveRecord::Base
belongs_to :userable, :polymorphic => true
belongs_to :event
end
User < ActiveRecord::Bas
has_many :event_users, :as => :userable
has_many :events, :through => :event_users
end
This almost works!! The problem is though that userable_type gets the type "User" for all diffrent associations. Is it possible to solve this?
I'm afraid your associations look totally wrong. First of all, if you have a 'has_many' on one side of a association, you have to have a 'belongs_to' on the other side. Secondly, I'm guessing done_user, admin_user etc inherit from User. Am i correct ?
And how different are participating_user, admin_user etc different from each other? Do you really need classes for each of these, can you make do with named scopes?
I would suggest you simplify your data model.
Right now your modeling looks fuzzy. Please do elaborate.
EDIT
Honestly, I think your modeling is over complicated. If I were you, given your descriptions of *_users, I would just have named scopes to retrieve that type of an user. So in effect
class Event < ActiveRecord::Base
has_many :event_users
has_many :users, :through => :event_users
end
class User < ActiveRecord::Base
has_many :event_users
has_many :events, :through => :event_users
end
So now, write named scopes in your User model to retrieve *_user.
Here's an excellent screencast on named_scopes.

Right way to force uniqueness on a join model? (has_many :through)

I have a parent/child relationship via our users table, with models as such:
class User < ActiveRecord::Base
# Parents relationship
has_many :children_parents, :class_name => "ParentsChild", :foreign_key => "child_id", :dependent => :destroy
has_many :parents, :through => :children_parents
# Children relatiopnship
has_many :parents_children, :class_name => "ParentsChild", :foreign_key => "parent_id", :dependent => :destroy
has_many :children, :through => :parents_children
...
end
And in parents_child.rb:
class ParentsChild < ActiveRecord::Base
belongs_to :parent, :class_name => "User"
belongs_to :child, :class_name => "User"
end
Right now, it is possible in our "add children" form (just using vanilla nested attributes) to add the same user as a child multiple times for parents. I am not sure what the 'right' way to go about forcing uniqueness in the ParentsChild relationship, although I am leaning towards a unique index on (parent_id, child_id) at the database layer (using a migration of course).
I am sure I could also enforce uniqueness constraints in the UsersController::update method, but would prefer to avoid changing that code (right now it doesn't reference parents/children at all, thanks to nested attributes in the form/model) if possible. I am most concerned with making sure we use the "proper" solution. What is the 'right' or 'rails' way to do this?
Using has_many :through, you can specify :uniq as an option, like this:
has_many :parents, :through => :children_parents, :uniq => true

Is there a way to break out of the default_scope when using has_many?

I have a tree-like model where in all situations but one, I want to scope the results to only return the roots.
class Licence < ActiveRecord::Base
default_scope :conditions => { :parent_licence_id, nil }
belongs_to :parent_licence, :class_name => 'Licence'
has_many :nested_licences, :class_name => 'Licence',
:foreign_key => 'parent_licence_id', :dependent => :destroy
end
class User < ActiveRecord::Base
has_many :licences
end
Using default_scope seemed like an awesome idea because the various models which have associations to Licence (there are about 4 of them) and also any code using find(), would not need to do anything special. The reason it isn't an awesome idea is that the default scope also applies to the has_many, which results in never finding the children. But the has_many is the only place which needs to bust out of the scope, so as far as "default" behaviour goes, I think this default_scope is quite reasonable.
So is there some good way to get around this specific problem?
Here is one which I am not too fond of because it uses SQL for a nearly trivial query:
has_many :nested_licences, :class_name => 'Licence', :dependent => :destroy,
:finder_sql => 'SELECT l.* FROM licences l WHERE l.parent_licence_id = #{id}',
:counter_sql => 'SELECT COUNT(l.*) FROM licences l WHERE l.parent_licence_id = #{id}'
Alternatively is there some way to apply a named scope to an association from the model? e.g. something along the lines of this nonsense-code:
class Licence < ActiveRecord::Base
named_scope :roots, :conditions => { :parent_licence_id, nil }
belongs_to :parent_licence, :class_name => 'Licence'
has_many :nested_licences, :class_name => 'Licence',
:foreign_key => 'parent_licence_id', :dependent => :destroy
end
class User < ActiveRecord::Base
has_many :licences, :scope => :roots # a :scope option doesn't really exist
end
I know I can also do this:
class Licence < ActiveRecord::Base
named_scope :roots, :conditions => { :parent_licence_id, nil }
belongs_to :parent_licence, :class_name => 'Licence'
has_many :nested_licences, :class_name => 'Licence',
:foreign_key => 'parent_licence_id', :dependent => :destroy
end
class User < ActiveRecord::Base
has_many :licences, :conditions => { :parent_licence_id, nil }
end
But that really isn't very DRY. Actually having to do every single query through Licence.roots.find() instead of Licence.find() isn't very DRY either, to be honest. It's just asking for a bug to occur where the scope isn't used.
Try using Licence.unscoped.find()
btw -
The documentation for ActiveRecord::Base.unscoped says that chaining unscoped with a named scope method has no effect.
It is recommended to use the block form of unscoped because chaining unscoped with a named scope does not work. If "sent" (below) is a named_scope the following two statements are the same.
Message.unscoped.sent
Message.sent
fyi rails 2 also has with_exclusive_scope which can be helpful.
Can't you make use of the :conditions option on the association? Something like this:
has_many :nested_licences, :class_name => 'Licence',
:dependent => :destroy, :conditions => "parent_licence_id = #{id}"

has_many :through issue with new records

I'm modeling "featuring" based on my plan in this question and have hit a bit of a stumbling block.
I'm defining a Song's primary and featured artists like this:
has_many :primary_artists, :through => :performances, :source => :artist,
:conditions => "performances.role = 'primary'"
has_many :featured_artists, :through => :performances, :source => :artist,
:conditions => "performances.role = 'featured'"
This works fine, except when I'm creating a new song, and I give it a primary_artist via new_song.performances.build(:artist => some_artist, :role => 'primary'), new_song.primary_artists doesn't work (since the performance I created isn't yet saved in the database).
What's the best approach here? I'm thinking of going with something like:
has_many :artists, :through => :performances
def primary_artists
performances.find_all{|p| p.role == 'primary'}.map(&:artist)
end
I think you're overcomplicating it. Just because things have similarities doesn't mean you should put them all in the same box.
class Song < ActiveRecord::Base
has_one :artist # This is your 'primary' artist
has_and_belongs_to_many :featured_artists, :source => :artist # And here you make a featured_artists_songs table for the simple HABTM join
validates_presence_of :artist
end
Poof, no more confusion. You still have to add song.artist before you can save, but that's what you wanted. Right?
There's not much that you can do about the association not being recognized until you save. Arguably, it doesn't really exist until you save, validations pass, and the relevant transaction(s) are completed.
Regarding your question of cleaning up your primary_artist method, you could model it something like this.
class Song < ActiveRecord::Base
has_many :performances
has_many :artists, :through => :performances
has_one :primary_artist, :through => :performances, :conditions => ["performances.roll = ?", "primary"], :source => :artist
end
It's unclear if you want one or many primary artists, but you can easily switch that has_one to has_many as needed.
You've nailed the source of your problem with build vs. create.
As for finding the primary artist of a song. I would add a named_scope on artist to select only featured/primary artists.
class Artist < ActiveRecord::Base
...
named\_scope :primary, :joins => :performances, :conditions => "performances.role = primary"
named\_scope :featured, :joins => :performances, :conditions => "performances.role = featured"
end
To get the primary artist for a song, you would do #song.artists.primary or if you prefer your primary_artists method in song.
def primary_artists
artists.primary
end
However, after looking at your initial question, I think your database layout is insufficient. It's workable but not clear, I've posted my suggestions there, where it belongs.
These named scopes I would also work under my proposed scheme as well.

Resources