new to rails and trying to work out the most 'railsy' way to implement the following.
Given below:
class Team
has_many :team_memberships
has_many :players, through: :team_memberships
end
class TeamMembership
acts_as_list scope: :team_id
belongs_to :team
belongs_to :player
end
class Player
has_many :team_memberships
has_many :teams, through: team_memberships
end
In my TeamMembership model I have a additional columns position:integer, captain:boolean and wicket_keeper:boolean (Can you tell what I am working on yet.. :))
Given the following:
t = Team.first
p = Player.first(11)
t.players << p
This populates the TeamMembership model with my player objects nicely.
If I try:
t.players[4][:captain] = true
it fails with - ActiveModel::MissingAttributeError: can't write unknown attributecaptain'`
However, I can set it like this:
t.team_memberships[4][:captain] = true
But this just feels wrong.
Given you should see what I am trying to achieve, is this:
A) The best way to approach this task - if so, is there a way to get at and set the captain attribute directly through the player (if that makes sense)
or
B) Is there a better, more 'railsy' approach that I am missing.
I believe what you have t.team_memberships[4][:captain] = true is correct. I say this because you state in your question that your TeamMembership model has position:integer, captain:boolean, wicket_keeper:boolean. So from my understanding you've populated the TeamMembership model. However with the following: t.players[4][:captain] = true you are trying to access the captain attribute. Which is part of the TeamMembership model, and so what I'm trying to say is that your trying to access the attribute captain which an attribute of the instance TeamMembership. Further to this I believe this error is coming from the has_many :team_memberships which you have in your Player class, this refers to the associated model. I think its looking for player_id in the TeamMembership table. See the following links they may help clarify what you are trying to do:
API - Ruby on Rails - belongs_to
Guide Ruby on Rails - Belongs_to_association-reference have a look at section 4.1.2.6: foreign_key
Related
For example I have model Match and model Player. What I want is for Player to be able to participate in many matches.
So it should look smth like this:
#match_first = Match.first
#match_last = Match.last
#match_first.players
#player1, player3, player4
#match_last.players
#player1, player4, player5
Both matches can have same players simultanously.
In your Match model:
has_many: :players, through: :appearances
In your Player model:
has_many: :matches, through: :appearances
class Appearance < ActiveRecord::Base
belongs_to :match
belongs_to :player
You could have extra attributes on appearance like 'goals_scored'. The through model doesn't have to be called Appearance... it could be Attendance or MatchPlayer or PlayerMatch, but you can see that the last two names are constraining, because out of this association you can also see all the matches a player appeared in.
Rails provides two ways to accomplish the many-to-many relationship. The first, which #zoot's answer describes, is has_many :through. This is a great option if the relationship is a Model in its own right (i.e., needs additional attributes or validations).
If the relationship between the two entities is direct such that you don't really need a third model, you can use the has_and_belongs_to_many association.
In your Match model:
has_and_belongs_to_many :players
In your Player model:
has_and_belongs_to_many :matches
When you use has_and_belongs_to_many you do need to create a join table that holds the relationship. The migration for that (assuming a relatively recent version of Rails) might look like:
class CreateJoinTableMatchesPlayers < ActiveRecord::Migration
def change
create_join_table :matches_players, :users do |t|
t.index [:match_id, :player_id], unique: true
end
end
end
There's a section in the Active Record Associations guide that talks about how to choose between the two approaches.
I have a few models...
class Game < ActiveRecord::Base
belongs_to :manager, class_name: 'User'
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :game
belongs_to :voter, class_name: 'User'
end
class User < ActiveRecord::Base
has_many :games, dependent: :destroy
has_many :votes, dependent: :destroy
end
In my controller, I have the following code...
user = User.find(params[:userId])
games = Game.includes(:manager, :votes)
I would like to add an attribute/method voted_on_by_user to game that takes a user_id parameter and returns true/false. I'm relatively new to Rails and Ruby in general so I haven't been able to come up with a clean way of accomplishing this. Ideally I'd like to avoid the N+1 queries problem of just adding something like this on my Game model...
def voted_on_by_user(user)
votes.where(voter: user).exists?
end
but I'm not savvy enough with Ruby/Rails to figure out a way to do it with just one database roundtrip. Any suggestions?
Some things I've tried/researched
Specifying conditions on Eager Loaded Associations
I'm not sure how to specify this or give the includes a different name like voted_on_by_user. This doesn't give me what I want...
Game.includes(:manager, :votes).includes(:votes).where(votes: {voter: user})
Getting clever with joins. So maybe something like...
Game.includes(:manager, :votes).joins("as voted_on_by_user LEFT OUTER JOIN votes ON votes.voter_id = #{userId}")
Since you are already includeing votes, you can just count votes using non-db operations: game.votes.select{|vote| vote.user_id == user_id}.present? does not perform any additional queries if votes is preloaded.
If you necessarily want to put the field in the query, you might try to do a LEFT JOIN and a GROUP BY in a very similar vein to your second idea (though you omitted game_id from the joins):
Game.includes(:manager, :votes).joins("LEFT OUTER JOIN votes ON votes.voter_id = #{userId} AND votes.game_id = games.id").group("games.id").select("games.*, count(votes.id) > 0 as voted_on_by_user")
I have 3 simple models:
class User < ActiveRecord::Base
has_many :subscriptions
end
class Product < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
I can do a_subscription.product = a_product and AR knows I mean product_id and everything works fine.
But If i do:
Subscription.where :product => a_product
It throws an error at me Unknown column 'subscriptions.product' - It knows in the first case that I mean product_id but it doesn't in the latter. I am just wondering if this is how it is suppose to be or am I missing something? I can get it to work by saying
Subscription.where :product_id => a_product
by do I have to specify _id?
Yes, right now you can't pass association to the where method. But you'll be able to do it in Rails 4. Here is a commit with this feature.
I don't think there's an elegant way around that (as of now, see #nash 's answer). However, if you have an instance of a_product and it has has_many on subscriptions, why not just turn it around and say:
subscriptions = a_product.subscriptions
I'm using a :has_many, :through association to link two models, User and Place
It looks like this -
In User:
has_many :user_places
has_many :places, :through=>:user_places
In Place:
has_many :user_places
has_many :users, :through=>:user_places
In User_Place
belongs_to :user
belongs_to :place
belongs_to :place_status
On that last one note the place_status.
I want to write a find that returns all places associated to a user with a particular place_status_id.
Place_Status_id is on the join model, user_place.
So basically I want
User.places.where(:place_status_id=>1)
(in rails 3)
but i get an error with that because place_status_id isnt on the place model.
Any ideas? Thanks all.
I believe you can do your find this way
#user.places.joins(:user_places).where(:user_places => {:place_status_id => 1})
I've never used Rails 3, so I'm sorry if there's any errors.
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