Hi: I'm struggling with a rails query. Finally, I want to make a JSON response with the following contents: A listing of all countries which have at least one company, associated through "Address" and a count of how of how many companies reside in a country.
Here is my simple model structure:
class Country < ActiveRecord::Base
has_many :addresses
end
class Address < ActiveRecord::Base
belongs_to :country
belongs_to :company
end
class Company < ActiveRecord::Base
has_one :address
end
What's the most elegant way to solve this with Rails?
You need to overload the to_json method :
class Country < ActiveRecord::Base
has_many :addresses
def to_json
# format your country as json with all the elements you need
end
end
Then in your controller do something like that:
countries = Country.find(:all).select{|c| c.companies.size > 0 }
json = countries.collect{|c| c.to_json }
You'll have to use has_many throught to get the companies from a country. Documentation here: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Sorry I don't have time for a more precise solution, but hopefully these pointers will help you. If you have specific problems, feel free to either comment or open a new question.
You might want to add a counter cache to Address
belongs_to :country, :counter_cache => :companies_count
It stores the number of companies in the country model, which saves you from the N+1 query issue.
Alternatively you could avoid it also by issuing a single query like this:
Country.find(:all, :select => 'countries.*, count(addresses.id) as companies_count', :joins => 'LEFT JOIN addresses ON addresses.country_id = countries.id', :group => 'countries.id')
This way all of the returned countries will have field companies_count containing the number of companies for that country.
Related
I am currently making a website that runs on Ruby on Rails. I am facing some issues while I was trying to join two tables, Rates and Locations, that I have with two different attributes name.
Rates: id rater_id rateable_id (and a few more attributes in this table)
Locations: id title body user_id (and a few more attributes in this table)
Here is the query that I am trying to do in SQL.
SELECT *
FROM rates, locations
WHERE rates.rater_id = locations.user_id AND rates.rateable_id = locations.id
I have read the official active record documents that provided by rubyonrails.org. I have tried doing these, but it does not work. Here is the code that I am trying to implant in app\controllers\users_controller.rb
#join_rating = Rate.joins(:locations).where("rates.rateable_id = locations.id AND rates.rater_id = locations.id")
#all_rating = #all_rating.where(rater_id: #user)
#count_all_rating = #all_rating.count
#join_rating, is trying to join the attributes with different names.
#all_rating, is trying to filter which location to show using the user ID
#join_rating, is trying to calculate the total numbers of locations that are rated by the user
Assume that everything is setup correctly and the only error is in the query that I am trying to do, how should I rewrite the statement so that I am able to show the locations that the user has rated using #all_rating.
Thank you!
A few points:
When in ActiveRecord you're starting a statement with the Rate class, it means the result is going to be a collection of Rate objects. So if you're trying to show locations, you should start with a Location class.
#locations_user_rated = Location.joins('INNER JOIN rates ON
rates.rateable_id = locations.id').where('rates.rater_id' => #user)
And if your ActiveRecord associations are well defined, you could simply do:
#locations_user_rated = Location.joins(:rates).where('rates.rater_id' => #user)
"Well defined" simply means you'll need to do something like the following. Note that I am not sure I understand your model relationships correctly. I assume below that every location has multiple rates, and that the reason your Rate model has the field called rateable_id instead of a location_id is because you want :rateable to be polymorphic. This means you probably also have a rateable_type field in rates table.
class Location < ActiveRecord::Base
has_many :rates, as: :rateable
end
class Rate < ActiveRecord::Base
belongs_to :rateable, polymorphic: true
end
If this polymorphism is not the case, things should actually be simpler, and I highly recommend that you follow Rails's conventions and simply name the relationship field location_id on your Rate model instead of rateable_id. Then you can do:
class Location < ActiveRecord::Base
has_many :rates
end
class Rate < ActiveRecord::Base
belongs_to :location
end
If still you are not convinced about the field name, you can customize things and do:
class Location < ActiveRecord::Base
has_many :rates, foreign_key: :rateable_id
end
class Rate < ActiveRecord::Base
belongs_to :location, foreign_key: :rateable_id
end
You can find more about how to customize associations here, and here.
I highly recommend taking advantage of ActiveRecord's has_many, belongs_to, and has_many through: functionality.
If you set up a model for each of these tables, with the correct relationships:
class User < ActiveRecord::Base
has_many :ratings, foreign_key: :rater_id
has_many :rated_locations, through: ratings, class_name: Location.name, source: :rater
end
class Rating < ActiveRecord::Base
belongs_to :rater, class_name: User.name
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :ratings
end
Then to access the locaitons that a user has rated, you just call
user.rated_locations
I have a sql-Statement and I'd like to "convert" it into rails (activerecord) method calls.
This is my query
'SELECT * FROM clients WHERE company_id IN (SELECT company_id FROM companies_projects WHERE project_id= ? )
companies_projects is a join table for an n:n relation of companies and projects
clients belong to companies (1:n)
project is an external resource and has no has_many companies, so I can't go from that direction
I want to get all clients that belong to companies that belong to one project, so I can list them in the index-page
My models
class Client < ActiveRecord::Base
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :companies_projects
has_many :clients
has_many :projects, :through => :companies_projects
end
I checked the statement in rails console and it works.
I have two problems impelementing this query.
1. find_by_sql
I tried this method
Client.find_by_sql('SELECT * FROM clients WHERE company_id IN (SELECT company_id FROM companies_projects WHERE project_id= ? )',project.id)
But it throws an InvalidStatement Exception, MySQL Syntax Error near "?"
I also tried to put the sql and bindings into an array [sql,bind1], that works but I get an array and need an ActiveRecordRelation
2. where
I'm new to rails and can't figure out a valid method chain for such a query.
Could someone point me in the right direction?
I would prefer using ActiveRecord methods for the query, but I just don't know which methods to use for the nested selects.
You should have following associations between your models:
class Client < ActiveRecord::Base
belongs_to :company
end
class Company < ActiveRecord::Base
has_and_belongs_to_many :projects
has_many :clients
end
class Project < ActiveRecord::Base
has_and_belongs_to_many :companies
has_many :clients, through: :companies
end
Then it is simply:
project.clients
Client.where(company_id: CompanyProject.where(project_id: project.id).pluck(:id))
Or you can use JOIN
Client.joins(:company_project).where('companies_projects.project_id = ?', project.id)
But the best solution was proposed by #arup-rakshit
Considering that you have an intermediate model CompanyProject, this can be achieved with following query:
Client.where(:company_id => CompanyProject.where(:project_id => project_id).map(&:company_id) )
[Edit: made company_id a symbol)
In my Rails 4 app, I have the following models:
class Person < ActiveRecord::Base
has_many :addresses
end
class Address < ActiveRecord::Base
belongs_to :person
belongs_to :city
end
class City < ActiveRecord::Base
has_many :addresses
end
I'm using the :includes function to return query result into one variable:
Address.includes(:person, :city).where("person_id = 1")
It works as expected, except that I do not want the query to return every single column.
Here's what I've tried:
use select and specify table name and column names explicitly, e.g. "city.name", but Rails generates a big query with outer joins, that can be very costly, especially when there are lots of concurrent requests, so prefer a better solution.
don't want to hard code complete and raw SQL statements, because of maintenance issue later on
create a new "dummy" belongs_to relationship like in Address: belongs_to :city_select_columns, -> { select('name') }, :class => 'City', but that doesn't work (actually I'm not sure if that select is even supported, only came across documentation about where so far).
maybe define scope in City and Person? but I'm not sure how it should be defined or if it'd make sense to do it this way
Suggestions? Thanks
Have you tried this?
class Person < ActiveRecord::Base
has_many :addresses
has_many :cities, :through => :addresses
end
class Address < ActiveRecord::Base
belongs_to :person
belongs_to :city
end
class City < ActiveRecord::Base
has_many :addresses
end
Then:
Person.find(1).cities.pluck(:name)
Looks like this generates an INNER JOIN but with indexes it shouldn't be too costly?
Did you try select?
Address.select(<output_columns>).includes(:person, :city).where("person_id = 1")
Could not find a good query method using Rails' API, I ended up writing a raw inner join SQL, then call ActiveRecord::Base.connection.execute to run it.
I have the following table structure
manufacturers --> products ---> available_sizes_products <-- sizes
and the following models
class Manufacturer < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
has_many :sizes, :through => :available_sizes_products
has_many :available_sizes_products
end
class AvailableProductSize < ActiveRecord::Base
belongs_to :sizes
belongs_to :products
end
class Size < ActiveRecord::Base
has_many :products, :through => :available_sizes_products
has_many :available_sizes_products
end
I need to get a unique list of manufacturers, that have products in size "XL" or "L" for example.I'm getting lost in the chaining of joins etc.
class Manufacturer < ActiveRecord::Base
def self.with_sizes(sizes=[])
#sizes = Sizes.find(sizes)
...
end
end
Can someone help me with that ? Trying to do the Rails 4 way rather than drop down to SQL, since I need the query to run on several DBS
Thanks
First of all you have to use single form of noun in belongs_to expression.
And for the query try this one:
Manufacturer.includes(:products).where(products: (size: "XL"))
I use "includes" to avoid N+1 query. Otherwise it will send two queries: one for Manufacturers and one for products. Write back, if this one doesn't fit your need.
EDIT
BTW, if you want to use exactly joining, write joins instead of includes.
Everything is here:
http://guides.rubyonrails.org/active_record_querying.html#joining-tables
and here:
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
After going through the docs for joins
this is what worked :
Manufacturer.joins(products: :sizes).where(sizes: {id:ids}).distinct
This Rails way returns the model correctly.
I have a pretty complicated set of models and I'm trying to figure out how to set it all up so that rails will generate chained queries (though I'm not sure it is possible). So my question ends up being: how do I chain these together OR how do I properly paginate this type of function?
First, the high overview.
It is a "social network" and I have users who can be students or teachers. Students and teachers can both follow each other. Both can also do things on the site that result in activity_items. A students can belong to a teacher. Therefore, if a student is following a teacher, they should also see the activity_items of the students under that teacher.
And that is where it gets complicated. I am trying to figure out how to display an activity feed.
Now the models:
model ActivityItem < ActiveRecord::Base
belongs_to :profile, :polymorphic => :true
attr_accessor :posted_by_user
end
model Following < ActiveRecord::Base
belongs_to :profile, :polymorphic => true
belongs_to :following, :polymorphic => true
end
model Student < ActiveRecord::Base
has_many :following_relationships, :as => :profile, :class_name => "Following"
has_many :follower_relationships, :as => :following, :class_name => "Following"
# getting the actual student/teacher model for each relationship
def following
self.following_relationships.reset
following_relationships.collect(&:following)
end
def activity_feed
all = following
all.inject([]) do |result,profile|
result | profile.activity_items
end
end
end
model Teacher < ActiveRecord::Base
[same as student, really it is in an Extension]
end
So the method I'm most concerned about is activity_feed. Without running the large set of queries every time, I'm not sure how to paginate the data. I have a few more methods that go through each activity_item and set posted_by_user to true or false to let me know if it is an activity_item created by someone they are following directly or indirectly (through the teacher).
How can I modify this so that I'm able to either
(A) get a single (or two or three) queries so that I can do a pagination method on it.
(B) how would I properly cache / paginate a large data set like this?