I want to display a list of all projects, where a project
has one or more tasks
AND
has one or more clients OR has flag 'can_have_clients = 0'
AND
current_user has assignment on client
My current query is working, but does not look like the right way:
Project.where('id IN (SELECT DISTINCT project_id FROM tasks)')
.where('id IN (SELECT DISTINCT project_id FROM clients WHERE id IN (
SELECT DISTINCT resource_id FROM assignments WHERE resource_type="Client" AND user_id=?))
OR can_have_clients = 0', current_user)
Is it possible to split up more (specially the last where/OR) and does this look like the way to go with rails?
# model
class Project < ActiveRecord::Base
has_many :tasks
has_many :clients
...
class Task < ActiveRecord::Base
belongs_to :project
...
class Client < ActiveRecord::Base
has_many :assignments, :as => :resource
...
Try this:
Project.joins(:tasks).joins( :clients => :assignments).where(
:projects => { :can_have_clients => 0},
:assignments => { :resource_type => "Client", :user_id => current_user}
).select("DISTINCT project.*")
If you want to eager load tasks and clients and assignments:
Project.include(:tasks).include( :clients => :assignments).
where("tasks.id IS NOT NULL AND clients.id IS NOT NULL AND
assignments.id IS NOT NULL").
where(
:projects => { :can_have_clients => 0},
:assignments => { :resource_type => "Client", :user_id => current_user}
)
I think you can use named_scope here to optimize your query in rails format .
And for performance vice you can make the query like :
Project.where('exists (SELECT 1 FROM tasks where tasks.project_id=project.id)')
.where('exists (SELECT 1 FROM clients WHERE exists (
SELECT 1 FROM assignments WHERE assignments.resource_id=clients.id AND resource_type="Client" AND user_id=?))
OR can_have_clients = 0', current_user)
because in is too costly than exists , check it.
Related
my model is
class Job < ActiveRecord::Base
belongs_to :client
end
class Client < ActiveRecord::Base
has_many :jobs
end
in controller i want get client with sorted jobs.
If i do (without ordering)
#client = Client.find(params[:id], :include => {:jobs => :status})
It is all ok. But if i add ordering:
#client = Client.find(params[:id], :include => {:jobs => :status}, :order => 'job.level DESC')
// :order is dynamicly set (not in this example) - i know about :order in has_many.
the result is only 3 rows (for every job.level one). I logged the sql query and executed it and result is ok, but in app i have only these 3 rows.
What is the right way to sort jobs? Thank you
You could try specifying the order directly in the model instead of the controller
class Job < ActiveRecord::Base
belongs_to :client, :order => 'level DESC'
end
OR
class Client < ActiveRecord::Base
has_many :jobs, :order => 'level DESC'
end
Also, what data type is the level in the Jobs model? If you could post your logs for the sql queries that would be helpful as well. Also, have you tried
:order => 'jobs.level'
instead of the singular job.level used in the code you posted?
This is not what i originally wanted, but it works, so this is answer
#jobs = #client.jobs.find(:all, :order => sort_column + " " + sort_direction)
I have these models simplified:
class Game::Champ < ActiveRecord::Base
has_one :contract, :class_name => "Game::ChampTeamContract", :dependent => :destroy
has_one :team, :through => :contract
# Attributes: :avg => integer
end
#
class Game::Team < ActiveRecord::Base
has_many :contracts, :class_name => "Game::ChampTeamContract", :dependent => :destroy
has_many :champs, :through => :contracts
end
#
class Game::ChampTeamContract < ActiveRecord::Base
belongs_to :champ
belongs_to :team
# Attributes: :expired => bool, :under_negotiation => bool
end
#
So what I want to do here is to find all Game::Champs that have no Game::ChampTeamContract whatsoever OR has, but (is not :under_negociation OR is :expired ), sorted by Champ.avg ASC
I am kinda stuck at using two queries, concating the result and sorting it. I wish there were a better way to to it more "Railish"
UPDATE: Just added a constraint about :expired
Try something like:
Game::Champs.
joins("left outer join game_champ_team_contracts on game_champ_team_contracts.champ_id = game_champs.id").
where("game_champ_team_contracts.id is null or (game_champ_team_contracts.state != ? or game_champ_team_contracts.state = ?)", :under_negotiation, :expired).
order("game_champs.avg ASC")
This is a fairly nasty line if left as-is, so if you use this, it needs tidying up. Use scopes or methods to split it up as much as possible!
I just tested with a super simple query:
#bars1 = Bar.where(:something => 1)
#bars2 = Bar.where(:something => 2)
#bars = #bars1 + #bars2
Not sure if it's right, but it works...
I am putting together a messaging system for a rails app I am working on.
I am building it in a similar fashion to facebook's system, so messages are grouped into threads, etc.
My related models are:
MsgThread - main container of a thread
Message - each message/reply in thread
Recipience - ties to user to define which users should subscribe to this thread
Read - determines whether or not a user has read a specific message
My relationships look like
class User < ActiveRecord::Base
#stuff...
has_many :msg_threads, :foreign_key => 'originator_id' #threads the user has started
has_many :recipiences
has_many :subscribed_threads, :through => :recipiences, :source => :msg_thread #threads the user is subscribed to
end
class MsgThread < ActiveRecord::Base
has_many :messages
has_many :recipiences
belongs_to :originator, :class_name => "User", :foreign_key => "originator_id"
end
class Recipience < ActiveRecord::Base
belongs_to :user
belongs_to :msg_thread
end
class Message < ActiveRecord::Base
belongs_to :msg_thread
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
end
class Read < ActiveRecord::Base
belongs_to :user
belongs_to :message
end
I'd like to create a new selector in the user sort of like:
has_many :updated_threads, :through => :recipiencies, :source => :msg_thread, :conditions => {THREAD CONTAINS MESSAGES WHICH ARE UNREAD (have no 'read' models tying a user to a message)}
I was thinking of either writing a long condition with multiple joins, or possibly writing giving the model an updated_threads method to return this, but I'd like to see if there is an easier way first. Am I able to pass some kind of nested hash into the conditions instead of a string?
Any ideas? Also, if there is something fundamentally wrong with my structure for this functionality let me know! Thanks!!
UPDATE:
While I would still appreciate input on better possibilities if they exist, this is what I have gotten working now:
class User < ActiveRecord::Base
# stuff...
def updated_threads
MsgThread.find_by_sql("
SELECT msg_threads.* FROM msg_threads
INNER JOIN messages ON messages.msg_thread_id = msg_threads.id
INNER JOIN recipiences ON recipiences.msg_thread_id = msg_threads.id
WHERE (SELECT COUNT(*) FROM `reads` WHERE reads.message_id = messages.id AND reads.user_id = #{self.id}) = 0
AND (SELECT COUNT(*) FROM recipiences WHERE recipiences.user_id = #{self.id} AND recipiences.msg_thread_id = msg_threads.id) > 0
")
end
end
Seems to be working fine!
Also to check if a specific thread (and message) are read:
class Message < ActiveRecord::Base
# stuff...
def read?(user_id)
Read.exists?(:user_id => user_id, :message_id => self.id)
end
end
class MsgThread < ActiveRecord::Base
# stuff...
def updated?(user_id)
updated = false
self.messages.each { |m| updated = true if !m.read?(user_id) }
updated
end
end
Any suggestions to improve this?
Add a named_scope to the MsgThread model:
class MsgThread < ActiveRecord::Base
named_scope :unread_threads, lambda { |user|
{
:include => [{:messages=>[:reads]}, recipiencies],
:conditions => ["recipiences.user_id = ? AND reads.message_id IS NULL",
user.id],
:group => "msg_threads.id"
}}
end
Note: Rails uses LEFT OUTER JOIN for :include. Hence the IS NULL check works.
Now you can do the following:
MsgThread.unread_threads(current_user)
Second part can be written as:
class Message
has_many :reads
def read?(usr)
reads.exists?(:user_id => usr.id)
end
end
class MsgThread < ActiveRecord::Base
def updated?(usr)
messages.first(:joins => :reads,
:conditions => ["reads.user_id = ? ", usr.id]
) != nil
end
end
You might want to take a look at Arel, which can help with complex SQL queries. I believe (don't quote me) this is already baked into Rails3.
I want to find a ordered list of runners by their results.
models
class Race < ActiveRecord::Base
has_many :runners, :dependent => :destroy
end
class Runner < ActiveRecord::Base
belongs_to :race
has_one :result, :dependent => :destroy
end
class Result < ActiveRecord::Base
belongs_to :runner
end
trying to use something like this
ordered_runners = race.runners.all(:include => :result, :order => 'results.position ASC')
position is their finishing position ie [1,2,3,4....]
but if a result is missing (nil) then the runner is not included. Is there a way to do this and return all runners?
cheers
Runners without Results are not included because :include only brings in the data minimizing the number of queries to avoid N+1 hits to the db. You want to do an outer :join to include all runners no matter if they have a result or not.
ordered_runners = race.runners.all(:joins => "left outer join results on runners.id = results.runner_id", :order => 'results.position ASC')
Check this code based on your migration column/table names and your database.
This should return the runners with a null result:
race.runners.all(:include => :result, :conditions => "results IS NULL", :order => 'results.position ASC')
I have two models, user and group. I also have a joining table groups_users.
I have an association in the group model:
has_many :groups_users
has_many :users, :through=> :groups_users
I would like to add pending_users which would be the same as the users association but contain some conditions. I wish to set it up as an association so that all the conditions are handled in the sql call. I know there's a way to have multiple accessors for the same model, even if the name is not related to what the table names actually are. Is it class_name?
Any help would be appreciated, thanks
Use named_scopes, they're your friend
Have you tried using a named_scope on the Group model?
Because everything is actually a proxy until you actually need the data,
you'll end up with a single query anyway if you do this:
class User < ActiveRecord::Base
named_scope :pending, :conditions => { :status => 'pending' }
and then:
a_group.users.pending
Confirmation
I ran the following code with an existing app of mine:
Feature.find(6).comments.published
It results in this query (ignoring the first query to get feature 6):
SELECT *
FROM `comments`
WHERE (`comments`.feature_id = 6)
AND ((`comments`.`status` = 'published') AND (`comments`.feature_id = 6))
ORDER BY created_at
And here's the relevant model code:
class Feature < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :feature
named_scope :published, :conditions => { :status => 'published' }
This should be pretty close - more on has_many.
has_many :pending_users,
:through => :groups_users,
:source => :users,
:conditions => {:pending => true}
:pending is probably called something else - however you determine your pending users. As a side note - usually when you see a user/group model the association is called membership.
In the User model:
named_scope :pending, :include => :groups_users, :conditions => ["group_users.pending = ?", true]
That's if you have a bool column named "pending" in the join table group_users.
Edit:
Btw, with this you can do stuff like:
Group.find(id).users.pending(:conditions => ["insert_sql_where_clause", arguments])