Table Join in rails 2 - ruby-on-rails

I am working in rails 2, I have one table users and other table lms_users. In lms_users table, id from users table is coming as foreign key. I want to join two tables in such a way, so i get only those users, whose entry is not in lms_users.

class User
named_scope :not_in_lms_users, {
:conditions => [ "users.id NOT IN (SELECT user_id FROM lms_users)" ]
}
end
or
class User
named_scope :not_in_lms_users, {
:conditions => "lms_users.user_id IS NULL",
:joins => "LEFT OUTER JOIN lms_users ON lms_users.user_id = users.id"
}
end
Just check, but I think the second one is more efficient. This should work :
User.not_in_lms_users

Related

How to make a join between 2 tables in Rails that returns the data of both tables

I'm trying to join two tables: Rooms and Room_Types (which have a relationship already). The thing is, I'm trying to do something like:
room = Room.all :conditions => ['rooms.id = ?', #room_id],
:joins => :room_type
room.to_json
..and this JSON is being sent to my view.
However, the JSON is only showing the fields of the Room table and is not including the Room_Type fields, and I need both tables' fields in this JSON. How can I accomplish this?
:joins only performs a JOIN. As in SQL, this does not add the JOINed table's columns to the results. If you want to do that you should use :include instead:
rooms = Room.all :conditions => [ 'rooms.id = ?', #room_id ],
:include => :room_type
rooms.to_json
Or, in Rails 3 parlance:
rooms = Room.where(:id => #room_id).include(:room_type).all
rooms.to_json
try
Room.joins(:room_type).where(:id => #room_id).select('room_types.foo as foo, room_types.bar as bar')

Simple ActiveRecord Question

I have a database model set up such that a post has many votes, a user has many votes and a post belongs to both a user and a post. I'm using will paginate and I'm trying to create a filter such that the user can sort a post by either the date or the number of votes a post has. The date option is simple and looks like this:
#posts = Post.paginate :order => "date DESC"
However, I can't quite figure how to do the ordering for the votes. If this were SQL, I would simply use GROUP BY on the votes user_id column, along with the count function and then I would join the result with the posts table.
What's the correct way to do with with ActiveRecord?
1) Use the counter cache mechanism to store the vote count in Post model.
# add a column called votes_count
class Post
has_many :votes
end
class Vote
belongs_to :post, :counter_cache => true
end
Now you can sort the Post model by vote count as follows:
Post.order(:votes_count)
2) Use group by.
Post.select("posts.*, COUNT(votes.post_id) votes_count").
join(:votes).group("votes.post_id").order(:votes_count)
If you want to include the posts without votes in the result-set then:
Post.select("posts.*, COUNT(votes.post_id) votes_count").
join("LEFT OUTER JOIN votes ON votes.post_id=posts.id").
group("votes.post_id").order(:votes_count)
I prefer approach 1 as it is efficient and the cost of vote count calculation is front loaded (i.e. during vote casting).
Just do all the normal SQL stuff as part of the query with options.
#posts = Post.paginate :order => "date DESC", :join => " inner join votes on post.id..." , :group => " votes.user_id"
http://apidock.com/rails/ActiveRecord/Base/find/class
So I don't know much about your models, but you seem to know somethings about SQL so
named scopes: you basically just put the query into a class method:
named_scope :index , :order => 'date DESC', :join => .....
but they can take parameters
named_scope :blah, {|param| #base query on param }
for you, esp if you are more familiar with SQL you can write your own query,
#posts = Post.find_by_sql( <<-SQL )
SELECT posts.*
....
SQL

Rails HABTM query where condition is based on association attribute

I have User and Album models with HABTM relationship
class Album < ActiveRecord::Base
has_and_belongs_to_many :users
class User < ActiveRecord::Base
has_and_belongs_to_many(:albums)
I'd like to find all the albums that are stored in the database but not associated with a particular user.
So far my code is like this:
Album.all(:order => "albums.created_at DESC", :include => "users", :limit => limit, :conditions => ["users.id != ? AND users.id IS NOT NULL", current_user.id])
but for some reason this is not working. It's returning albums that are associated with current_user.
here take a look at this ouptput from the console.
Check the users id i first fetch.
Then i fetch albums which should not have the users id
I then find one of the listed albums and ask it to return the associated users
one of those associated users is the one from above and shouldnt be there.
Can anyone help with the above?
I usually try to stay away sub-selects but I can't seem to get it to work any other way:
class Album < ActiveRecord::Base
scope :without_user, lambda{|u| where("#{quoted_table_name}.id NOT IN (SELECT `albums_users`.album_id FROM `albums_users` WHERE `albums_users`.user_id = ?)", u.id) }
end
user = User.find(30)
Album.without_user(user)
Assuming you created table albums_users to hold relationship data:
Album.includes(:users).
where(["albums_users.user_id IS NULL OR albums_users.user_id != ?", user_id])
I think it will generate SQL along the lines of
SELECT *
FROM albums LEFT OUTER JOIN albums_users ON albums.id = albums_users.album_id
WHERE albums_users.album_id IS NULL OR albums_users.album_id != #{user_id}
Try:
:conditions => ["users.id <> ? AND users.id IS NOT NULL", current_user.id]
A non-sql solution would be:
Album.all.reject{|album| user.albums.include?(album)}
Obviously, if you have tens of thousands of rows in your database, you might not want to do this.
Might do something like this, too:
Album.where(["id NOT IN (?)", user.albums_ids])
But if your users have a lot (say hundreds) of albums, you shouldn't do this either.
Just throwing in easy solutions if you're out for one of those.

Rails scalar query

I need to display a UI element (e.g. a star or checkmark) for employees that are 'favorites' of the current user (another employee).
The Employee model has the following relationship defined to support this:
has_and_belongs_to_many :favorites, :class_name => "Employee", :join_table => "favorites",
:association_foreign_key => "favorite_id", :foreign_key => "employee_id"
The favorites has two fields: employee_id, favorite_id.
If I were to write SQL, the following query would give me the results that I want:
SELECT id, account,
IF(
(
SELECT favorite_id
FROM favorites
WHERE favorite_id=p.id
AND employee_id = ?
) IS NULL, FALSE, TRUE) isFavorite
FROM employees
Where the '?' would be replaced by the session[:user_id].
How do I represent the isFavorite scalar query in Rails?
Another approach would use a query like this:
SELECT id, account, IF(favorite_id IS NULL, FALSE, TRUE) isFavorite
FROM employees e
LEFT OUTER JOIN favorites f ON e.id=f.favorite_id
AND employee_id = ?
Again, the '?' is replaced by the session[:user_id] value.
I've had some success writing this in Rails:
ee=Employee.find(:all, :joins=>"LEFT OUTER JOIN favorites ON employees.id=favorites.favorite_id AND favorites.employee_id=1", :select=>"employees.*,favorites.favorite_id")
Unfortunately, when I try to make this query 'dynamic' by replacing the '1' with a '?', I get errors.
ee=Employee.find(:all, :joins=>["LEFT OUTER JOIN favorites ON employees.id=favorites.favorite_id AND favorites.employee_id=?",1], :select=>"employees.*,favorites.favorite_id")
Obviously, I have the syntax wrong, but can :joins expressions be 'dynamic'? Is this a case for a Lambda expression?
I do hope to add other filters to this query and use it with will_paginate and acts_as_taggable_on, if that makes a difference.
edit
errors from trying to make :joins dynamic:
ActiveRecord::ConfigurationError: Association named 'LEFT OUTER JOIN favorites ON employees.id=favorites.favorite_id AND favorites.employee_id=?' was not found; perhaps you misspelled it?
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1906:in `build'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1911:in `build'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1910:in `each'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1910:in `build'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/associations.rb:1830:in `initialize'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1789:in `new'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1789:in `add_joins!'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1686:in `construct_finder_sql'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1548:in `find_every'
from /Users/craibuc/.gem/ruby/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:615:in `find'
Try this:
ee=Employee.all(
:select=>"employees.*,favorites.favorite_id",
:joins=>"LEFT OUTER JOIN favorites AS favorites
ON employees.id=favorites.favorite_id AND
favorites.employee_id = #{session[:user_id]}")
Or to be exact:
joins = Employee.send(:sanitize_sql_array,
["LEFT OUTER JOIN favorites AS favorites
ON employees.id=favorites.favorite_id AND
favorites.employee_id = ? ", session[:user_id]
])
ee=Employee.find(:all,
:select=>"employees.*,favorites.favorite_id",
:joins=> joins )
Second approach addresses the SQL injection issues.
Edit 1
To test these calls in irb do the following:
Simulate the session object by creating hash:
>> session = {:user_id => "1" }
session = {:user_id => "1" }
=> {:user_id=>"1"}
Execute the finder:
>> ee=Employee.find(:all,
:select=>"employees.*,favorites.favorite_id",
:joins=>"LEFT OUTER JOIN favorites AS favorites
ON employees.id=favorites.favorite_id AND
favorites.employee_id = #{session[:user_id]}")
I imagine both ways are possible, but normally, I'd stick the condition in the WHERE clause (:conditions):
ee = Employee.find(:all,
:select => 'employees.*, favorites.favorite_id',
:conditions => ['favorites.employee_id = ?', 1],
:joins => 'LEFT OUTER JOIN favorites ON employees.id = favorites.favorite_id'
)
:joins expects either a raw string or a symbol (association name), or an array of associations. So you can't have dynamic conditions there.
See parameters section here.

Find all objects with no associated has_many objects

In my online store, an order is ready to ship if it in the "authorized" state and doesn't already have any associated shipments. Right now I'm doing this:
class Order < ActiveRecord::Base
has_many :shipments, :dependent => :destroy
def self.ready_to_ship
unshipped_orders = Array.new
Order.all(:conditions => 'state = "authorized"', :include => :shipments).each do |o|
unshipped_orders << o if o.shipments.empty?
end
unshipped_orders
end
end
Is there a better way?
In Rails 3 using AREL
Order.includes('shipments').where(['orders.state = ?', 'authorized']).where('shipments.id IS NULL')
You can also query on the association using the normal find syntax:
Order.find(:all, :include => "shipments", :conditions => ["orders.state = ? AND shipments.id IS NULL", "authorized"])
One option is to put a shipment_count on Order, where it will be automatically updated with the number of shipments you attach to it. Then you just
Order.all(:conditions => [:state => "authorized", :shipment_count => 0])
Alternatively, you can get your hands dirty with some SQL:
Order.find_by_sql("SELECT * FROM
(SELECT orders.*, count(shipments) AS shipment_count FROM orders
LEFT JOIN shipments ON orders.id = shipments.order_id
WHERE orders.status = 'authorized' GROUP BY orders.id)
AS order WHERE shipment_count = 0")
Test that prior to using it, as SQL isn't exactly my bag, but I think it's close to right. I got it to work for similar arrangements of objects on my production DB, which is MySQL.
Note that if you don't have an index on orders.status I'd strongly advise it!
What the query does: the subquery grabs all the order counts for all orders which are in authorized status. The outer query filters that list down to only the ones which have shipment counts equal to zero.
There's probably another way you could do it, a little counterintuitively:
"SELECT DISTINCT orders.* FROM orders
LEFT JOIN shipments ON orders.id = shipments.order_id
WHERE orders.status = 'authorized' AND shipments.id IS NULL"
Grab all orders which are authorized and don't have an entry in the shipments table ;)
This is going to work just fine if you're using Rails 6.1 or newer:
Order.where(state: 'authorized').where.missing(:shipments)

Resources