I have a show with many episodes, and episodes have many people through contributions. (see diagram below)
I added has_many :people, through: :contributions to Show so I can do: game_of_thrones.people.count, and even:
has_many :actors, -> { joins(:roles).merge(Role.actors).distinct },
through: :contributions, source: :person
-
game_of_thrones.actors.count
But what I would like to do is create a list of actors on game_of_thrones or breaking_bad, etc, ordered by the number of appearances on the show.
example:
Jon Snow: 9 episodes
Arya Stark: 8 episodes
Joffrey Lannister: 5 episodes
Tyrion Lannister: 2 episodes
My question is a two parter.
How do I return a list of top actors on a show ordered by their contribution count?
Should I be doing this through ActiveRecord or SQL queries. What would be a good reference to understand this better so I can stop bothering stack overflow?
class Show < ActiveRecord::Base
has_many :episodes, inverse_of: :show
has_many :contributions, through: :episodes
has_many :people, through: :contributions
has_many :actors, -> { joins(:roles).merge(Role.actors) },
through: :contributions, source: :person
end
class Episode < ActiveRecord::Base
belongs_to :show, inverse_of: :episodes
has_many :contributions, inverse_of: :episode
has_many :roles, through: :contributions
has_many :people, through: :contributions
end
class Contribution < ActiveRecord::Base
belongs_to :episode, inverse_of: :contributions
belongs_to :person, inverse_of: :contributions
belongs_to :role, inverse_of: :contributions
end
class Person < ActiveRecord::Base
has_many :contributions, inverse_of: :person
has_many :episodes, through: :contributions
has_many :roles, through: :contributions
scope :actors, -> { joins(:roles).merge(Role.actors) }
end
class Role < ActiveRecord::Base
has_many :contributions, inverse_of: :role
has_many :people, through: :contributions
has_many :episodes, through: :contributions
scope :actors, -> { where(name: 'Actor') }
end
failed queries: game_of_thrones.people.joins(:contributions).distinct.joins(:episodes).where("episodes.show_id = 1").order("count(episodes.show_id) desc")
game_of_thrones.guests.joins(:contributions).order("count(contribuions.id) desc")
This is how I solved it:
def top_contributors
self.people.select("people.id, count(contributions.id) AS contribution_count").
joins(:contributions).
joins(:episodes).
group("people.id").
order("contribution_count DESC").
limit(5)
end
Though I think this is counting all of a person's contributions regarless of shows.
Related
I have the following models:
class Account
has_many :credits, class_name: "Charge", inverse_of: :dst, foreign_key: :dst_id
has_many :debits, class_name: "Charge", inverse_of: :src, foreign_key: :src_id
has_many :occurrences, through: [:credits, :debits] # not sure about this
end
class Charge
belongs_to :src, class_name: "Account", foreign_key: :src_id, inverse_of: :debits
belongs_to :dst, class_name: "Account", foreign_key: :dst_id, inverse_of: :credits
has_many :occurrences
end
class Occurrences
belongs_to: :charge
end
This situation is similar to the classic Twitter follower/followed example.
What I'm trying to model is a charge that takes money from one account and deposits it into another.
How can I access from Account all occurrences through debits and credits?
I used the Railstutorials to create followers and followed_users http://ruby.railstutorial.org/chapters/following-users#top
On the page where I want to show a specific persons' followers/followed_users, I'd like to show them based on when the relationship was created.
#users = #user.followers.order("created_at DESC")
Something like this ^^ just shows when the user was created, not when the relationship was created. How can I run this query efficiently to get the proper ordering?
def following
#users = #user.followed_users
end
def followers
#users = #user.followers
end
-User Model-
has_many :relationships, foreign_key: "follower_id", :dependent => :destroy
has_many :followed_users, through: :relationships, source: :followed
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
- Relationship Model -
belongs_to :follower, class_name: "User", touch: true
belongs_to :followed, class_name: "User", touch: true
validates :follower_id, presence: true
validates :followed_id, presence: true
Since your user has there two relationships, you can easily access that table with the direction you want.
has_many :relationships, foreign_key: "follower_id", dependent: :destroy
has_many :reverse_relationships, foreign_key: "followed_id"
First Answer (when they're a follower)
You have to use the relationships table because that record gets created when you get a new follower, thus you do this:
#user.relationships.order("created_at DESC").collect { |r| User.find(r.followed) }
Second Answer (when they're followed)
#user.reverse_relationships.order("created_at DESC").collect { |r| User.find(r.follower) }
I have a nested model like so:
class Games::Player < ActiveRecord::Base
attr_accessible :user_id
belongs_to :user
has_many :games_extras_achievements_players, :class_name => 'Games::Extras::AchievementsPlayer'
has_many :games_extras_achievements, :class_name => 'Games::Extras::Achievement',:through=>:games_extras_achievements_players
validates :user_id,uniqueness: true
end
class Games::Extras::Achievement < ActiveRecord::Base
has_many :games_extras_achievements_players, :class_name => 'Games::Extras::AchievementsPlayer'
has_many :games_players, through: :games_extras_achievements_players, class_name: 'Games::Player'
end
class Games::Extras::AchievementsPlayer < ActiveRecord::Base
attr_accessible :games_extras_achievement_id, :games_player_id
belongs_to :games_extras_achievement, :class_name => 'Games::Extras::Achievement'
belongs_to :games_player, :class_name => 'Games::Player'
end
Objects on the join class work as expected.
However trying to get player -> achievement or vice versa gives an error:
> p.games_extras_achievements
Games::Extras::Achievement Load (0.3ms) SELECT "games_extras_achievements".* FROM "games_extras_achievements" INNER JOIN "games_extras_achievements_players" ON "games_extras_achievements"."id" = "games_extras_achievements_players"."games_extras_achievement_id" WHERE "games_extras_achievements_players"."player_id" = 1
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column games_extras_achievements_players.player_id does not exist
LINE 1: ...ents_players"."games_extras_achievement_id" WHERE "games_ext...
If I change the migration to use player_id like it is trying to find, I get an error stating that games_player_id does not exist
I seem to have fixed it.
I needed to use the non-namespaced column names and add a foreign key constraint.
class Games::Player < ActiveRecord::Base
attr_accessible :user_id
belongs_to :user
has_many :games_extras_achievements_players, :class_name => 'Games::Extras::AchievementsPlayer'
has_many :games_extras_achievements, :class_name => 'Games::Extras::Achievement',:through=>:games_extras_achievements_players
validates :user_id,uniqueness: true
end
class Games::Extras::Achievement < ActiveRecord::Base
has_many :games_extras_achievements_players, :class_name => 'Games::Extras::AchievementsPlayer'
has_many :games_players, through: :games_extras_achievements_players, class_name: 'Games::Player'
end
class Games::Extras::AchievementsPlayer < ActiveRecord::Base
attr_accessible :achievement_id, :player_id
belongs_to :games_extras_achievement, class_name:'Games::Extras::Achievement',foreign_key: :achievement_id
belongs_to :games_player, class_name: 'Games::Player',foreign_key: :player_id
end
Hopefully this will save someone some aggravation.
Is it possible to make a has_one relationship work like this?
I would like to be able able to load records like this:
#person = Person.find(1) => {Person id: 1, favorite_house_id: 10}
#person.favorite_house => {House id: 10....)
class Person < ActiveRecord::Base
has_many :houses, through: :person_houses
has_one :favorite_house, through: :person_houses
end
class PersonHouse < ActiveRecord::Base
belongs_to :house
belongs_to :person
end
class House < ActiveRecord::Base
has_many :people, through: :person_houses
end
Replace the has_one relation of Person by:
belongs_to :favorite_house, :class_name => "House"
Do not forget to create a column favorite_house_id in the table of Person.
Warning:Total Rails Newb (TRN). This should be a pretty basic question so I'm hoping someone can spare a couple mins to help shed some light.
Let's say I have the following models: User, Group, and Member
A user can have many groups (let's say friends, family, etc)
A group can have many members, namely other users.
How would I structure this?
Initially I tried this:
class User < ActiveRecord::Base
has_many :groups
has_many :groups, :through => :members
end
class Groups < ActiveRecord::Base
has_many :users, :through => :members
belongs_to :user
end
class Member < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
However this gave me an error in User so I changed
has_many :groups, :through => :members
to
has_many :memberships, :through => :members, :source => :groups
Still getting an error about missing association when I try to do
group = Group.new
group.user.new
It will be useful: http://railscasts.com/episodes/47-two-many-to-many
class User < ActiveRecord::Base
has_many :members
has_many :groups, :through => :members
has_many :groups_as_owner, :class_name => "Group"
end
class Groups < ActiveRecord::Base
has_many :members
has_many :users, :through => :members
belongs_to :owner, :class_name => "User", :foreign_key => :user_id
end
class Member < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
basically has_many-through associations are n:m associations (join-tables) that (shall) have more attributes than just the id's of the joined record ids...
so you have a table Groups (with an id), a table Users (with an id) and a table Members (no id, but user_id and group_id)
basically, what you did is nearly correct, just think about how you access a group from a user or vice versa....
a user would first look up its member information and through that member information get access to the group information ... and vice versa for a group
so you first set up
has_many :members
and then call
has_many :groups, :through => :members
all you need is
class User < ActiveRecord::Base
has_many :members
has_many :groups, :through => :members
end
class Groups < ActiveRecord::Base
has_many :members
has_many :users, :through => :members
end
class Member < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
and you have another bug in your code above
you might want to use
user = group.users.new
instead of
user = group.user.new
Try this structure:
class User < ActiveRecord::Base
has_many :members
has_many :groups, :through => :members
end
class Groups < ActiveRecord::Base
has_many :members
has_many :users, :through => :members
end
class Member < ActiveRecord::Base
belongs_to :group
belongs_to :user
end
Also take a look at has_and_belongs_to_many, if you don't need to do with class Member then you should use has_and_belongs_to_many. In this case don't forget to create joining table in the database