Has many through associations with conditions - ruby-on-rails

I am trying to add a condition to a has many through association without luck. This is the association in my video model:
has_many :voted_users, :through => :video_votes, :source => :user
I want to only get the voted_users whose video_votes have a value equal to 1 for that video. How would I do this?

I would suggest creating a model method within the video model class
Something like:
def users_with_one_vote
self.voted_users, :conditions => ['value = ?', 1]
end
Then in the controller use video.users_with_one_vote
Then testing is easier too.
Any chance you can change that column name from 'value'. Might give some issues (reserved?).

I'd do this in 2 stages:
First, I'd define the has_many :through relationship between the models without any conditions.
Second, I'd add a 'scope' that defines a where condition.
Specifically, I'd do something like:
class User < ActiveRecord::Base
has_many :video_votes
has_many :votes, :through=>:video_votes
def self.voted_users
self.video_votes.voted
end
end
class VideoVote
def self.voted
where("value = ?", 1)
end
end
class Video
has_many :video_votes
has_many :users, :through=>:video_votes
end
Then you could get the users that have voted using:
VideoVote.voted.collect(&:user).uniq
which I believe would return an array of all users who had voted. This isn't the exact code you'd use -- they're just snippets -- but the idea is the same.

Would
has_many :voted_users, :through => :video_votes, :source => :user, :conditions => ['users.votes = ?', 1]
Do the trick?

I found that defining this method in my model works:
def upvoted_users
self.voted_users.where("value = 1")
end
and then calling #video.upvoted_users does the trick.

The best way to do this without messing with the relations is by crafting a more complex query. Relations is not the best thing to use for this particular problem. Please understand that relations is more a "way of data definition" then a way of "bussiness rules definition".
Bussiness logic or bussiness rules must be defined on a more specifically layer.
My suggestion for your problem is to create a method to search for users who voted on your video only once. something like:
class Video < ActiveRecord::Base
def voted_once()
User.joins(:video_votes).where("video_votes.value == 1 AND video_votes.video_id == ?", this.id)
end
Rails is magical for many things, but complex queries still have to be done in a "SQL" way of thinking. Don't let the illusional object oriented metaphor blind you

As long as we are throwing around ideas, how about using association extensions.
class VideoVote
scope :upvotes, where(:value => 1)
end
class Video
has_many :voted_users, :through => :video_votes, :source => :user do
def upvoted
scoped & VideoVote.upvotes
end
end
end
Then you feel good about making a call with absolutely no arguments AND you technically didn't add another method to your Video model (it's on the association, right?)
#video.voted_users.upvoted

Related

Iterating Over Rails Model Classes with Polymorphic Associations

In an application there are many models with polymorphic associations defined like:
has_many :mentions, :as => :mentionable, :dependent => :destroy
In a library class all mentionable models are collected for later iterating.
mentionables = Model1.all + Model2.all + Model3.all ...
This works but it is just static which is not desirable for a library code. Bottom statement would be much more intuitive hovever it won't work.
mentionables = Mentionable.all
Is there an API in Rails to iterate over models with Polymorphic relations defined with ':as => ...' directive?
I think there are two questions in here and I'll try my best to answer both of them. First, you'd need a way to list all of the models in your Rails application. There are methods offered in this question, and I prefer this answer (by sj26).
# Really depends on how your classes are loaded, usually environment specific.
Rails.application.eager_load!
ActiveRecord::Base.descendants
After, you need to parse the model associations to determine if they have an :as #option assigned, and pull out the associated class name. This is assuming that your association has been created as such:
class Mentionee < ActiveRecord::Base
has_many :mentions, :as => :mentionable, :dependent => :destroy, :class_name => 'Mentionable'
end
You can do this using the reflect_on_all_associations method (there is probably a more ruby-esque way to write this):
Mentionee.reflect_on_all_associations.select {|a| a.options[:as] == :mentionable }
Which will return the polymorphic class model for Mentionable. To join these up, you could do something as follows (untested!):
Rails.application.eager_load!
mentionables = []
ActiveRecord::Base.descendants.each do |descendent|
mentionables << descendent.reflect_on_all_associations.select{|a| a.options[:as] == :mentionable}
end
mentionables.each do |mentionable|
# Do your work here
end

Modify the behavior of has_many or use scope?

I have a class that looks something like this:
class User < ActiveRecord:Base
has_many :users_companies
has_many :companies, :through => :users_companies
end
For plain users, I'd like user.companies to refer to the standard association method, but when a user is an admin, I want User.all (i.e., admins have access to all companies). The simplest way I can think of to implement this (and what I've always done in the past) is use a scope on the Company class, such as:
scope :accessible_by, lambda { |user| ... }
The only problem is that this just doesn't feel right. Instead of writing a controller action that includes:
#companies = Company.accessible_by(current_user)
I'd feel more comfortable writing
#companies = current_user.companies
Is there a good way to override the User#companies method to accommodate this kind of behavior? Or, should I be happy with using a scope on Company?
I'm wrestling with a similar problem. The only acceptable solution I can devise is an association extension, which overrides the query for admin users and passes normal users' queries, unmolested.
# this works for me in rails 3.1
class User < ActiveRecord:Base
has_many :users_companies
has_many :companies, :through => :users_companies do
def visible
if proxy_association.owner.admin?
UsersCompany.scoped
else
self
end
end
end
end
User.where(:admin => true).first.companies.visible == UsersCompany.all
I'm fairly new to Rails, but this is an interesting question so I figured I'd toss in my two cents. It seems that you should be able to extend your association in User with a companies method that checks self.is_admin? (or similar) and returns what you need. See http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many#461-User-a-block-to-extend-your-associations
Nice question. I was wondering if something like the following is an option you would consider
class User < ActiveRecord:Base
has_many :users_companies
has_many :companies, :through => :users_companies
def viewable_companies
admin? ? Company.all : self.companies
end
end
I know the naming is horrible but, you know, naming things is serious stuff :)

Rails ActiveRecord Double Associations

I have the following two models, User..
class User < ActiveRecord::Base
has_and_belongs_to_many :sites
end
.. and Site:
class Site< ActiveRecord::Base
has_and_belongs_to_many :users
end
Up to this point its fine. It works and it's pretty simple.
Now I want to introduce "primary user" to the Site. I add "primary_user_id" to the Site, and trying to add a second association:
class Site< ActiveRecord::Base
has_and_belongs_to_many :user
# my new association that doesn't work...
has_one :primary_user, :class_name => "User", :conditions => ['id = ?', '{self.primary_user_id}']
end
It doesn't like it... Now I know that I can fake this by just adding a method "primary_user" to the site and this will work, but my question is whether it is possible to user ActiveRecord associations and how?
has_and_belongs_to_many is tricky and most people have moved away from it and use has_many through => model.
btw - 'Up to this point its fine. It works and it's pretty simple.' is how all things start off. How they perform when you 'really' start to use them is what counts and for that reason you'll probably find has_many through easier to work with.
These links will help:
http://paulbarry.com/articles/2007/10/24/has_many-through-checkboxes
http://thoughtsincomputation.com/posts/checkboxes-with-has_many-through
http://my.opera.com/durrantm/blog/2011/07/24/rails-simple-form-with-has-many-through-hmt-relationship
https://github.com/romanvbabenko/nested_has_many_through (nesting gem).

Rails query using HMTH and multiple join models

I am using Rails 3 and wanted to get the classes a student has access to based upon the model below
class Student
has_many :students_levels
has_many :levels, :through => :students_levels
end
class Class
has_many :classes_levels
has_many :levels, :through => :classes_levels
end
class Level
has_many :students_levels
has_many :classes_levels
end
class StudentsLevel
belongs_to :students
belongs_to :levels
end
class ClassesLevel
belongs_to :classes
belongs_to :levels
end
I came up with the query below but didn't think it seemed like the best Rails way to do things and wanted to get additional suggestions. Thx
Class.where(:id => (ClassesLevel.where(:level_id => Student.find(1).levels)))
I want to add this as an instance method to Student and was thinking there would be a better way doing something with has many through.
I quite did not understand the whole logic behind your class structure. Why you are not connecting students directly into a class? And how a class can have many levels. I mean if you have Math1 and Math2, those are different classes, right? Or do you have Math1,2,3?
Well, anyway, here's the solution if you want to use current assosiations, I hope it suites your needs:
Class Student
...
def available_classes
Class.find(:all,
:include => {:levels => {:students_levels => :student}},
:conditions => ["students.id = ?", self.id])
end
And sorry, this is still in Rails 2.x format...

Rails: Many-to-many Polymorphic Associations: Getting specific Type of Records

I have the following classes:
class Region < ActiveRecord::Base
has_many :geographical_relations, :as => :contained
has_many :geographical_units, :as => :container, :class_name => "GeographicalRelation"
end
class GeographicalRelation < ActiveRecord::Base
belongs_to :container, :polymorphic => true
belongs_to :contained, :polymorphic => true
end
class Country < ActiveRecord::Base
has_many :geographical_relations, :as => :contained
end
And I want to be able to, from a Country record, get all the Regions in which it is contained:
c = Country.find(1)
c.regions #=> should return all the regions in which c is contained
For now I've created the following method:
def regions
self.geographical_relations.where(:container_type => "Region").map{|relation| relation.container}
end
But I wonder if there's any way to set an "has_many" association to do this for me.
Cheers.
EDIT:
after trying the alternatives suggested in the comments I only got nice ActiveRecord errors.
The has_many_polymorphs gem seems to be a good way to do this, but for Rails 3 it is not 'officially' supported so for my case it is not a good option.
So I will work with methods like the one described above, putting them in modules and including the respective modules inside each "container"/"contained" models. This seems to work ok. =) Only change I made, to avoid N+1 Queries was adding 'includes':
def regions
self.geographical_relations.includes(:container).where(:container_type => "Region").map{|relation| relation.container}
end
Hopefully this works nicely and fast... =P =)
Anyway if anyone has an answer to solve this I'll look forward to see it! =)
Thanks all!

Resources