How to use joins using associations in Ruby on Rails - ruby-on-rails

I am trying to create a Redmine plugin built on Ruby on Rails. I have the following query which fetches the sum of time_entries of all the issues during an invoice.
SELECT SUM( t.hours )
FROM time_entries t
JOIN invoices i ON t.project_id = i.project_id
WHERE t.project_id = <current project id----#project.id>
AND i.id = <billing invoice id>
AND t.updated_on > 'i.created_at'
AND t.updated_on < 'i.due_date'
How can I store this query data inside invoices table column called time_spent or retrieve the results in the invoices view which lists all invoices along with the above query by invoice ID
The associations in model I created goes like this
class Invoice < ActiveRecord::Base
set_table_name "invoices"
set_primary_key "invoice_id"
belongs_to :project
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
has_many :time_entries, :class_name => "TimeEntry", :foreign_key => "project_id"
In the Controller I am calling the model as
#invoices = Invoice.find(:all, :joins=>" INNER JOIN time_entries ON time_entries.project_id = invoices.project_id",:conditions => ["invoices.project_id = ? AND updated_on > ? AND updated_on < ?", #project.id, invoices.created_at, invoices.due_date])
I know the controller instance variable is totally messed up.
Somewhere I doing mistake. Can some one please help me with this.

I'm assuming Rails 3+ and MySQL, this should give you your each item in your #invoices collection an accessor named "time_spent" which will have the sum you are looking for. It will not persist this info in the db, but it retrieves it for your view:
Invoice.where("invoices.project_id = ? AND invoices.updated_on > ? AND invoices.updated_on < ?", #project.id, invoices.created_at, invoices.due_date).select("SELECT invoices.*, SUM( time_entries.hours ) as time_spent").joins(:time_entries).group("invoices.id")
I'm guessing at < Rails 3 below:
Invoice.find(:all,
:conditions => ["invoices.project_id = ? AND updated_on > ? AND updated_on < ?", #project.id, invoices.created_at, invoices.due_date],
:select=>"SELECT invoices.*, SUM( time_entries.hours ) as time_spent",
:joins=>[:time_entries],
:group=>"invoices.id")
(hoping I typed this correctly. If not, the gist is to use the "select" and "group" methods to get your result.)

Got it working with the following code.
Invoice.find(:all, :select => 'invoices.*, SUM( time_entries.hours ) as time_spent_on_issues', :joins=>" JOIN time_entries ON time_entries.project_id = invoices.project_id", :conditions => ["invoices.project_id = ? AND time_entries.updated_on > invoices.created_at AND time_entries.updated_on < invoices.due_date", #project.id])
BTW "miked" code helped a lot

Related

ActiveRecord find with joins and associations?

I have a model TwitterUser that has_one website as shown in the model below:
class TwitterUser < ActiveRecord::Base
has_one :website, :foreign_key => :id, :primary_key => :website_id
end
I'm trying to run a query that will join TwitterUser with Website and get all TwitterUsers' with a website that has an updated_at date > a certain date, limited to 10 rows.
I thought this would give me what I wanted, but apparently it's not. What's wrong with it?
TwitterUser.includes().find(:all, :limit => 10, :conditions => ["websites.updated_at >= '2013-05-12 05:31:53.68059'"], :joins => :website)
In my database, my twitter_users table consist of a website_id field.
My websites table has an id field.
This should work.
TwitterUser.joins(:website).where("websites.updated_at >= '2013-05-12 05:31:53.68059'").limit(10)

How to full join two rail models?

I'm trying to join the results of 'SupplierShippingItem' and 'MtlSystemItem' but I keep getting an error:
Association named 'mtl_system_items' was not found; perhaps you misspelled it?
My association is done like this:
SupplierShippingItem.joins(:mtl_system_items).where('supplier_shipping_items.inventory_item_id = mtl_system_items.inventory_item_id SEGMENT1 ILIKE ? OR DESCRIPTION ILIKE ? ', "%#{params[:term]}%", "%#{params[:term]}%")
SupplierShippingItem
class SupplierShippingItem < ActiveRecord::Base
attr_accessible :inventory_item_id, :received_qty, :shipped_qty, :supplier_shipping_list_id, :supplier_planning_schedule_id, :po_number
belongs_to :mtl_system_item, :foreign_key => :inventory_item_id
end
*MtlSystemItem *
class MtlSystemItem < ActiveRecord::Base
attr_accessible :inventory_item_id, :segment1, :description, :primary_uom_code, :inventory_item_status_code, :item_type
has_many :supplier_shipping_items, :foreign_key => :inventory_item_id
end
What I'm trying to achieve is to fetch the items in MtlSystemItem but only if they are found in SupplierShippingItem. I have thousands of items in MtlSystemItem so I want to filter them out a bit. I'll also include a date restriction later on, but I'm blocked by the error.
as the error says, the association is not found. You used mtl_system_items instead of mtl_system_item (singular) which is the association you declared.
Remember that for joins and includes, you need to use the association name. For where, use the table name
SupplierShippingItem.joins(:mtl_system_item)
.where('supplier_shipping_items.inventory_item_id = mtl_system_items.inventory_item_id SEGMENT1 ILIKE ? OR DESCRIPTION ILIKE ? ', "%#{params[:term]}%", "%#{params[:term]}%")
SupplierShippingItem.joins(:mtl_system_items)
should be
SupplierShippingItem.joins(:mtl_system_item)
on your example above:
class SupplierShippingItem < ActiveRecord::Base
belongs_to :mtl_system_item
end
class MtlSystemItem < ActiveRecord::Base
has_many :supplier_shipping_items
end
you can try this ActiveRecord joins:
SupplierShippingItem.find(:all, :joins => :mtl_system_item])
and you can add conditions for this query like this:
SupplierShippingItem.find(:all, :joins => :mtl_system_item, :conditions => ["supplier_shipping_items.id = ?", 1]])

How can I find all cities with childs?

I have a City model and a Business model.
Business belogs_to :city
Now I want to define a view with a list of only those cities who have a child (in this case a business). Empty cities (means cities which still have no business added) should not be considered. If possible the list should be sorted in a way that the city with most businesses is on the top.
I found a solution like this:
#cities = City.find :all,
:joins => "INNER JOIN businesses ON businesses.city_id = cities.id",
:select => "cities.*, count(businesses.id) businesses_count",
:group => "businesses.city_id HAVING businesses_count > 0",
:order => "businesses_count desc"
This works fine (sorting is not yet done), but as far as I understood this will not work with Rails 3.1 and 3.2 (I use 3.0 now). See http://m.onkey.org/active-record-query-interface
Can anybody let me know how to define my #cities in a way that is ok for Rails 3.1 and 3.2?
Thank you!
#KandadaBoggu:
Great, I very much like your answer 2), thanks!!
Just a comment:
I migrated with
rails generate migration add_businesses_count_to_cities businesses_count:integer
Then I needed to edit the migration:
class AddBusinessesCountToCities < ActiveRecord::Migration
def self.up
add_column :cities, :businesses_count, :integer, :default => 0
City.reset_column_information
City.all.each do |c|
c.update_attribute :businesses_count, c.businesses.count
end
end
def self.down
remove_column :cities, :businesses_count
end
end
This is important to set a default value of 0 and then update the cities with the current number of businesses.
I also added the counter_cache to the child (business) like:
belongs_to :city, :counter_cache => true
This way it works great.
1) Without sorting:
City.joins(:businesses).select("DISTINCT cities.*")
2) Using counter_cache
Add an integer column called business_count to cities table.
class City < ActiveRecord::Base
has_many :businesses, :counter_cache => :business_count
end
Now you can select the cities as follows:
City.where("business_count > 0").order(:business_count)
3) Using group by
City.joins("
( SELECT a.id, COUNT(*) as business_count
FROM cities a, businesses b
WHERE a.id = b.city_id
GROUP BY a.id
) c ON cities.id = c.id ").
select("cities.*, c.business_count AS business_count").
order(:business_count)

rails complex search with ancestry

i have following model setup
class Category < ActiveRecord::Base
has_ancestry :cache_depth => true, :depth_cache_column => :depth
has_many :watches, :dependent => :destroy
has_many :products, :through => :watches
end
class Watch < ActiveRecord::Base
belongs_to :category
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :watch, :counter_cache => true
belongs_to :category
end
I need to find products through categories name. Category have 2 levels deep(tree structure). 1 - level is a make, 2 - serie. For now im build this type of search query with the help of meta_search gem
#products = (Product.search :watch_category_name_contains => params[:search]).all.paginate(:page => params[:page])
This works and return all products with serie_name. But watch table always contain only category_id of 2 level category(serie), and im need to be able to search products through makes(1 level category). How can i build this type of query? Thanks!
Well, i see some upvotes coming on my old question, so i will answer. Im finish with raw sql for makes and series queries. Here it is:
def self.makes_with_products
find_by_sql "
SELECT makes.* FROM categories makes
WHERE ancestry IS NULL AND makes.id IN (
SELECT series.ancestry FROM products p
INNER JOIN watches w ON w.id = p.watch_id
INNER JOIN categories series ON series.id = w.category_id
WHERE series.ancestry = makes.id AND p.active
)
"
end
def series_with_products
find_by_sql "
SELECT series.* FROM categories series
WHERE series.ancestry = '#{id}'
AND (
SELECT COUNT(*) FROM products p
INNER JOIN watches w ON w.id = p.watch_id
WHERE w.category_id = series.id AND p.active
) > 0
"
end
Hope this help someone.

Rails Association Question

I have three models: User, RaceWeek, Race.
Current associations:
class User < ActiveRecord::Base
has_many :race_weeks
end
class RaceWeek < ActiveRecord::Base
belongs_to :user
has_many :races
end
class Race < ActiveRecord::Base
belongs_to :race_week
end
So the user_id is a foreign key in RaceWeek and race_week_id is a foreign key in Race.
fastest_time is an attribute of the Race model.
QUESTION: What's the optimal way to retrieve a list of users who have the top X fastest race times?
You can do it like this:
users = User.all(:limit => X, :joins => {:race_weeks => :races}, :order => "reces.fastest_time DESC").uniq
If you have correctly specified has_many :through association, then you could even do it like this:
users = User.all(:limit => X, :joins => :races, :order => "reces.fastest_time DESC").uniq
In this solution, you get what you want with one query, but two joins. And this uniq method is not very good unless you would use small X.
Something like:
races = Race.all(:order => "fastest_time desc", :limit => X, :include => {:race_week => :user})
users = races.map{|race| race.race_week.user}.uniq
Note: didn't test this.
Given your current model the following should work.
race_weeks = RaceWeek.find_by_sql(["SELECT user_id FROM race_weeks JOIN races ON races.race_week_id = race_weeks.id ORDER BY races.fastest_time desc LIMIT ?", X)
users = User.find(race_weeks.collect(&:user_id).uniq)
I know that it requires two look ups but the second lookup should be very fast since you are only looking up X records by their primary key.

Resources