1

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?

Justin Meltzer
  • 12,518
  • 30
  • 110
  • 177

6 Answers6

1

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?).

Ryan Bigg
  • 102,687
  • 22
  • 224
  • 252
Michael Durrant
  • 84,444
  • 83
  • 284
  • 429
  • Would this work, considering `value` is a field in the video_votes table, not the user table... – Justin Meltzer Oct 04 '11 at 04:18
  • I don't think value is reserved... value represents whether it is an upvote or a downvote – Justin Meltzer Oct 04 '11 at 04:21
  • This won't work (I think). Don't you need to have some sort of query there like `self.voted_users.all(:conditions => "video_votes.value = '1'")` ? – Ryan Bigg Oct 04 '11 at 04:32
  • for the .all, maybe. But I believe just using the plural model name may work as rails uses the convention of plural for all. I think I have conditions right though? – Michael Durrant Oct 04 '11 at 12:54
1

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.

Michael Durrant
  • 84,444
  • 83
  • 284
  • 429
Kevin Bedell
  • 12,704
  • 9
  • 71
  • 108
0

Would

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

Do the trick?

joho
  • 62
  • 4
0

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.

Justin Meltzer
  • 12,518
  • 30
  • 110
  • 177
0

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

Gabriel Mazetto
  • 1,050
  • 1
  • 11
  • 22
0

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
Wizard of Ogz
  • 12,145
  • 2
  • 38
  • 43