How can I find all cities with childs? - ruby-on-rails

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)

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 use joins using associations in 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

sorting collection by ids from an array

My Thread model has many Posts. Let's say I want to reorder posts by an array containg ids I wish to see my posts sorted by.
thread.posts.collect {|x| x.id} # => [1,2,3]
order = [2,3,1]
posts = thread.posts.sort_by {|x| order.index x.id}
posts.collect {|x| x.id} # => [2,3,1]
thread.update_attributes(:posts => posts) # => true
thread.posts.collect {|x| x.id} # => [1,2,3]
What am I doing wrong? Is sorting by id always preserved in collections and can I somehow disable it?
You should always assume the order of results retrieved from your database as being more or less "random", unless you specifically ask it to sort them. This means that you can not rely on your database to magically store the order of posts associated with a thread (in fact, the code sample you posted would probably not query the database at all, because there is nothing to update).
The easiest way to achieve what you want is to add an order field to your Post model like this:
class AddOrderToPost < ActiveRecord::Migration
def up
change_table :posts do |t|
t.integer :order, :default => 0
end
Post.update_all ["order = ?", 0]
end
def down
remove_column :posts, :order
end
end
In your Thread model:
class Thread < ActiveRecord::Base
# ...
has_many :posts, :order => 'order ASC'
# ...
end
Afterwards you will be able to reorder the posts like this:
thread.posts.zip([2,3,1]) { |p,i| p.order = i }
If you want, you can also use a plugin like acts_as_list which provides this and other useful functionality.

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