find_each and finder_sql not playing well with each other? - sql

find_each doesn't seem to be playing nice with an ActiveRecord relation that uses finder_sql. Has anyone dealt with this before? For example:
class User < ActiveRecord::Base
has_many :games, :dependent => :destroy,
:finder_sql =>
proc {"SELECT * FROM games WHERE player_0_id = #{id} OR player_1_id = #{id}"}
end
class Game < ActiveRecord::Base
belongs_to :player_0, :class_name => "User", :foreign_key => "player_0_id", :inverse_of => :games
belongs_to :player_1, :class_name => "User", :foreign_key => "player_1_id", :inverse_of => :games
end
Calling #user.games.each works fine and iterates over all the games we care about. But calling #user.games.find_each results in:
ActiveRecord::StatementInvalid:
SQLite3::SQLException: no such column: games.user_id: SELECT "games".* FROM "games" WHERE "games"."user_id" = 2 AND ("games"."id" >= 0) ORDER BY "games"."id" ASC LIMIT 1000

Have a look at http://compositekeys.rubyforge.org/ to solve the given problem.

Related

Need to bring back information on intermediate model with terminal model on Rails has_many through

I have the following models:
school.rb
class School < ActiveRecord::Base
has_many :offers
has_many :players, :through => :offers
has_many :teammembers, :class_name => 'Offer', :conditions => ["on_roster = \'t\'"]
has_many :teamplayers, :through => :teammembers, :source => :player
player.rb
class Player < ActiveRecord::Base
has_many :offers, :dependent => :destroy
scope :sr, lambda {
where("(players.year = ? and players.redshirt = 't') or (players.year = ? and players.redshirt = 'f')", (Time.now + 8.months - 5.years).year, (Time.now + 8.months - 4.years).year)
}
offer.rb
class Offer < ActiveRecord::Base
belongs_to :school
belongs_to :player
validates_uniqueness_of :school_id, :scope => [:player_id]
Then in my schools_controller I have
schools_controller.rb
def show
#school = School.find(params[:id], :include => [{:offers => :player}])
#seniors = #school.teamplayers.sr
My issue is that I need to display a field from the offers model that is associated to the relationship between the School and the Player. Currently I'm performing an additional query in the view to get this field.
schools/show.haml.html
- #srpg.each do |player|
= Offer.find_by_school_id_and_player_id(#school.id, player.id).status
Obviously this is causing a N+1 issue, but I can't seem to figure out a way to bring back both the offer.status and the player.* in the same record.
Work with the offers instead:
# in model
class Offer
belongs_to :player
scope :of_seniors, includes(:player).where('player.kind = "senior"')
end
# in controller
#seniors_offers = #school.offers.of_seniors
# in view
- for offer in #seniors_offers
= offer.player.name
= offer.status
- end

Help with a complex ActiveRecord query that has too many joins

I have the following models
class User < ActiveRecord::Base
has_many :occupations, :dependent => :destroy
has_many :submitted_jobs, :class_name => 'Job', :foreign_key => 'customer_id'
has_many :assigned_jobs, :class_name => 'Job', :foreign_key => 'employee_id'
end
class Job < ActiveRecord::Base
belongs_to :customer, :class_name => 'User', :foreign_key => 'customer_id'
belongs_to :employee, :class_name => 'User', :foreign_key => 'employee_id'
belongs_to :field
end
class Occupation < ActiveRecord::Base
belongs_to :user
belongs_to :field
belongs_to :expertise
end
along with Field (just name and id) and Expertise (name and integer rank).
I need to create a filter that works like the following pseudocode
select * from jobs where employee_id == current_user_id
or employee_id == 0
and current_user has occupation where occupation.field == job.field
and if job.customer has occupation where occupation.field == job.field
current_user.occupations must include an occupation where field == job.field
and expertise.rank > job.customer.occupation.expertise.rank
You can see how I quickly exhaust my knowledge of SQL with a query this complex.
How would you do it? The proper SQL would be great, but if a Rails person can point me towards the correct way to do it with ActiveRecord methods, that's great too. Or maybe I'm not structuring my models very well; I'm open to all kinds of suggestions.
Thanks!
I might have missed something and did not look into refactoring the models but heres something that might help you to a complete solution or how to reformulate your query
The code is not tested or syntax checked
#jobs = Job.
joins(:employee,:occupation).
includes(:customer => {:occupations => :expertise}).
where(:employee_id => current_user.id).
where("occupations.field_id = jobs.field_id").all
user_occupations = current_user.occupations.include(:expertise)
user_occupations_by_field_id = user_occupations.inject({}) do |hash,oc|
hash[oc.field_id] = oc
hash
end
#jobs.reject! do |j|
common_occupations = j.customer.occupations.select do |oc|
if c = user_occupations_by_field_id[oc.field_id]
!user_occupations.select do |eoc|
c.field_id == eoc.field_id && c.expertise.rank > oc.expertise.rank
end.empty?
else
true
end
end
end

Rails has_many find associated items

So, got 2 models:
class Match < ActiveRecord::Base
has_many :rounds
has_many :participations
has_many :players, :through => :participations
belongs_to :clan_1, :class_name => "Clan", :foreign_key => "clan_1_id"
belongs_to :clan_2, :class_name => "Clan", :foreign_key => "clan_2_id"
belongs_to :winner, :class_name => "Clan", :foreign_key => "winner_id"
belongs_to :league
belongs_to :tournament
validates :clan_1_id, :presence => true
validates :clan_2_id, :presence => true
scope :by_league, lambda { |league| where("league_id == ?",league.id) }
scope :by_tournament, lambda { |tournament| where("tournament_id == ?",tournament.id) }
scope :played, where("played is not NULL")
scope :not_played, where("played is NULL")
end
class Clan < ActiveRecord::Base
has_many :players
has_many :rounds_won, :class_name => "Round", :foreign_key => "winner_id"
has_many :rounds_blue, :class_name => "Round", :foreign_key => "clan_blue_id"
has_many :rounds_purple, :class_name => "Round", :foreign_key => "clan_purple_id"
has_many :matches_won, :class_name => "Match", :foreign_key => "winner_id"
has_and_belongs_to_many :leagues
has_and_belongs_to_many :tournaments
def matches
Match.where("clan_1_id = ? OR clan_2_id = ?",self.id, self.id)
end
def matches_lost
matches.where("winner_id != ?", self.id)
end
def matches_drawn
matches.played.where("winner_id is NULL")
end
end
and I want to fetch all clans, which taken part in match.
You're over thinking it. Rails makes it very easy for you to do this.
class Comment < ActiveRecord::Base
belongs_to :post
end
class Post < ActiveRecord::Base
has_many :comments
end
#post.comments
If you have a column in your comment table with "modelName"_id (eg post_id) rails with automatically hook up the foreign key.
All you have to do is call #model1.model2 assuming #model1 is an instance of the model1 object.
If you want to hook up the query yourself you could use the where() method.
#comments = Comment.where(:post_id => some_id)
If you alter your associations a little bit you can utilize includes() in scopes:
class Match < ActiveRecord::Base
belongs_to :clan_1, :class_name => "Clan"
belongs_to :clan_2, :class_name => "Clan"
end
class Clan < ActiveRecord::Base
has_many :one_matches, :class_name => 'Match', :foreign_key => :clan_1_id
has_many :two_matches, :class_name => 'Match', :foreign_key => :clan_2_id
end
Then you can add this scope:
class Clan < ActiveRecord::Base
scope :participated_in_match, includes(:one_matches, :two_matches).where("matches.id IS NOT NULL")
end
This isn't tested so please let me know if you get unexpected results.
Quite simply:
model_two_object = Model_2.first # For clarity only, change to suit your needs
model_two_object.models_1

How to write an AREL statement on a self-referencing table

I have written quite a few AREL statements, but I'm tying myself in knots over this one. Here is my situation:
class Product < AR::Base
has_many :parents, :class_name => "ProductLink", :foreign_key => :to_product_id
has_many :children, :class_name => "ProductLink", :foreign_key => :from_product_id
# has an attribute called "identifier"
end
class ProductLink < AR::Base
belongs_to :parent, :class_name => "Product", :foreign_key => :from_product_id
belongs_to :child, :class_name => "Product", :foreign_key => :to_product_id
end
I want to retrieve all of the Products that have a child product with an identifier that matches some value.
I have twisted myself into a pretzel with this, seems easy, but I have been looking at it for too long now. I appreciate any help!
Got it!
brand.products.joins(:children => :child).where(:children => { :child => { :searchable_identifier.matches => "2136" } } )
That works great. See the hashed joins? That's what was throwing me off.

multiple joins using activerecord in rails

I'm building a small twitter style microblogging service where users can follow other users and get a feed of their messages
I have the following models:
class Follow < ActiveRecord::Base
belongs_to :follower, :class_name => "User"
belongs_to :followee, :class_name => "User"
end
class User < ActiveRecord::Base
has_many :follows, :foreign_key => 'follower_id',
:class_name => 'Follow'
has_many :followers, :through => :follows
has_many :followed, :foreign_key => 'followee_id',
:class_name => 'Follow'
has_many :followees, :through => :followed
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
To get a feed for the current user, I want to perform the following SQL query:
SELECT * FROM follows JOIN users JOIN messages WHERE follows.follower_id = current_user.id AND follows.followee_id = users.id AND users.id = messages.user_id;
What is the correct ActiveRecord way of doing this?
Not sure what you're looking for, but here is my suggestion:
I assume that you have other purposes for that Follow class, otherwise I don't see the purpose of it.
The "correct way" (i.e. my completely subjective way) to do it would actually be something like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :followers, :foreign_key => 'followed_id',
:class_name => 'User', :association_foreign_key => 'follower_id',
:include => [:messages]
has_and_belongs_to_many :follows, :foreign_key => 'follower_id',
:class_name => 'User', :association_foreign_key => 'followed_id'
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
Then create the following table:
create_table :users_users, :id => false do |t|
t.integer :followed_id
t.integer :follower_id
end
And you're set:
followed = User.find :first
follower = User.find :last
followed.followers << follower
followed.followers.first.messages
followed.followers.first.followers.first.messages # etc...
But from what I make it, you want to show all the messages from all the followers at the same time.
This should be possible to achieve by adding
has_and_belongs_to_many :followed_messages, :foreign_key => 'follower_id',
:class_name => 'Message', :association_foreign_key => 'followed_id'
to the User class, but I don't know how correct that way would be. Or it might be possible to achieve with association extensions but there I can't really give any examples.
Update:
By changing the :class_name, it will associate it with the Message.id, didn't think about that so it will not be correct in this way.
So the only "nice" option is to go through the User class like in the first example.
The only other options I can see is either the association extensions (which I can't give you an example for) or perhaps using a finder statement.
has_many :followed_messages, :class_name => 'Message',
:finder_sql => 'select * from messages where user_id in(select followed_id from users_users where follower_id = #{id})'
You probably have to customize that sql statement to get everything to work, but at least you should get the picture :)
Keijro's arrangement would work better, though if you need the Follow table, then you can execute the SQL query you specified as follows:
Follow.all(:joins => { :messages, :users }, :conditions => { "follows.follower_id" => current_user.id, "follows.followee_id" => "users.id", "users.id" => "messages.user_id"} )