Associated tables: Join or Include with Rails - ruby-on-rails

I am feeling a bit slow when it comes to rails and the Active Record associations... I have two tables.
Table = Rings
Table = Variations with foreign_key => "ring_id".
class Ring < ActiveRecord::Base
has_many :variations
end
class Variation < ActiveRecord::Base
belongs_to :ring
end
So in my "index/list" view i want to display all the rings, and both the variations, and i was thinking it would be possible to do this through one SQL query... however, i have tried the join and the include methods and i think i am just not understanding how they work properly.
So my question is, how would i write a query in my controller, that would pull my "title" and "value" column values from the "variations" and combine them into one simple object for easy looping? Or do i have to loop through all rings and look up the variation values during the loop?
thanks

In your controller:
#rings = Ring.includes(:variations).all
In index.html.erb:
#rings.each do |ring|
...
ring.variations.each do |variation|
...
end
end
The includes portion of the query will prevent Rails from repeatedly querying the database as you loop through and render your rings and variations in the view.

You need to use the includes method: Ring.inclues(:variations). Then the variation will be loaded along with the rings in a single SQL query.
For more info: http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations

Related

Query model data with relationship count and performance

I have problems with query performance with Postgresql and Rails counting related models while retrieving data.
class MasterModel
# few fields, like name, description and such
has_and_belongs_to_many :business_models, class: 'BusinessModel'
end
class BusinessModel
# Lots of important information, many fields
has_and_belongs_to_many :master_models, class: 'MasterModel'
end
The use case in question being that business_model can be related to any amount of master_model so typically you should have a small amount of master_model, a great amount of business_model and a even bigger amount of many to many relationships.
When showing master_model index page, you can visualize its information and a delete button only enabled when there are no relationships, hence the reason why its important to count the relationship in its representation.
So I tried some ways to achieve this:
Includes relationship is incredibly slow in ActiveRecord but not in query time. At least it has no N+1.
MasterModel.includes(:business_models).limit(50).offset(0).each do |master|
master.business_models.size
end
No includes. We have N+1 but is incredibly fast as long as model pagination is reasonable.
MasterModel.limit(50).offset(0).each do |master|
master.business_models.size
end
Given that I only need to know if relationships exists or not I tried a select with exists. Single query and fast.
MasterModel.select(
:id,
:name,
:description.
'NOT EXISTS (
SELECT many.master_id
FROM many
WHERE many.master_id = master.id
) AS removable'
).limit(50).offset(0).each do |master|
master.business_models.removable
end
In the end, I chose the 3rd choice but I am not totally convinced. What would be the Rails way? Am I doing something wrong in the other cases?
If you would used has_many through association you would be able to use counter_cache but HABTM doesn't support counter_cache so that you can implement your own counter_cache
First of all you need to add new integer column to the master_models table called business_models_count
add_column :master_models, :business_models_count, :integer, default: 0
And add next code to your model MasterModel
class MasterModel
has_and_belongs_to_many :business_models, class: 'BusinessModel', before_add: :inc_business_models_count, before_remove: :dec_business_models_count
private
def inc_business_models_count(*)
self.increment!(:business_models_count)
end
def dec_business_models_count(*)
self.decrement!(:business_models_count)
end
end
And write some rake task which goes through MasterModel records and update counter for existing records.
It can be done like this:
MasterModel.find_each do |master|
master.increment!(:business_models_count, master.business_models.size)
end
And after that you will be able to get business_models_count of each MasterModel instance without N+1
MasterModel.limit(50).offset(0).each do |master|
master.business_models_count
end

Rails has_and_belongs_to_many query for all records

Given the following 2 models
class PropertyApplication
has_and_belongs_to_many :applicant_profiles
end
class ApplicantProfile
has_and_belongs_to_many :property_applications
end
I have a query that lists all property_applications and gets the collection of applicant_profiles for each property_application.
The query is as follows and it is very inefficient.
applications = PropertyApplication.includes(:applicant_profile).all.select |property_application| do
property_application.applicant_profile_ids.include?(#current_users_applicant_profile_id)
do
assume #current_users_applicant_profile_id is already defined.
How can I perform one (or few) queries to achieve this?
I want to achieve something like this
PropertyApplication.includes(:applicant_profile).where('property_application.applicant_profiles IN (#current_users_applicant_profile))

Find multiple database objects by attribute in Rails?

I have a Track table and a Section table. A track has many sections. Sections are connected to their respective task by the Section's :track_id, which corresponds to the Track's :id attribute.
<% #track = Track.find(params[:id]) %>
<% #sections = Section.find_by_track_id(#track.id) %>
In the code above I'm trying to find multiple sections that share the same :track_id attribute, but find_by_track_id() only returns the first. What's the best way to get all of them?
Thanks!
If your tracks and sections are related in this way, then the best way to relate them is by using the methods that come automatically from Rails' associations.
in this case, I expect in your model files, you have the following:
class Track < ActiveRecord::Base
has_many :sections
end
class Section < ActiveRecord::Base
belongs_to :track
end
Then you can get the sections for a track like this:
#track = Track.find(params[:id])
#sections = #track.sections
You're looking for where, which finds all records where a specific set of conditions are met.
#sections = Section.where(track_id: #track.id)
This is unrelated to your question, but you should set #sections and #track in your controller. As it seems like you're new to Rails, I'd highly recommend reading through the Rails Guides. They will help you immensely on your journey.
EDIT: I was solving for the general question of "Find multiple database objects by attribute in Rails?", which is how to find multiple database objects in the general case. #TarynEast's method is the way to go to find all of the sections for a track, or more generally, all of the objects that belong to the desired object. For the specific case you're asking for above, go with #TarynEast's solution.
Association
To extend Taryn East's answer, you need to look into ActiveRecord Associations.
In your model, if you have the following has_many relationship:
#app/models/track.rb
Class Track < ActiveRecord::Base
has_many :sections
end
#app/models/section.rb
Class Section < ActiveRecord::Base
belongs_to :track
end
This will set up a relational database association between your tracks and sections datatables.
--
Associative Data
The magic of Rails comes into play here
When you call the "parent" object, you'll be able to locate it using its primary key (typically the ID). The magic happens when Rails automatically uses this primary_key as a foreign_key of the child model - allowing you to call all its data as an append to the parent object:
#track = Track.find params[:id] #-> find single Track by primary key
#sections = #track.sections #-> automagically finds sections using the track primary key
This means if you call the following, it will work exactly how you want:
#sections.each do |section|
section.name
end
Where
Finally, if you wanted to look up more than one record at a time, you should identify which ActiveRecord method you should use:
find is to locate a single record by id
finy_by key: "value" is to locate a single record by your defined key/column
where is to return multiple items using your own conditions
So to answer your base line question, you'll want to use where:
#sections = Section.where track_id: params[:id]
This is not the right answer, but it should help you
<% #sections=#track.sections%>
Use find when you are looking for one specific element identified by it's id.
Model.find is using the primary key column. Therefore there is always exactly one or no result.

rails, how to combine two ActiveRecord query results

I have following association
class Location < ActiveRecord::Base
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :location
end
Suppose I have some instances of Location, I what to query all items belong to those locations. Currently I managed to get the result as an array
items =[]
Location.near(latitude,longitude,distance).find_each do |location|
location.items.find_each do |item|
items << item
end
end
However, is there any way I can get the results as ActiveRecord::Relation. Because I want to further query the results by using "where" with ActiveRecord::Relation.
P.S. The "near" method is from geocoder gem, it returns a ActiveRecord::Relation.
---------------------Edit----------------------------
Thank you for replies I nearly find the solution
locations = Location.near(latitude,longitude,distance)
Item.where(location_id: locations.pluck(:id))
Is it the right way to do it? to me it is a bit unintuitive.
----------------------Edit again ---------------------------
Just a small comment: I say it is unintuitive because I am switching from DataMapper. If it is Datamapper, it would be quite simple, like
Location.near(blabla).items
It is very simply to make queries through associations. Compared to Datamapper, can not understand why ActiveRecord association is so useless?
Edit to use one query with mapping...
What billy said above, but another option that might be faster:
locations = Location.near(1, 2, 3)
items = Item.where(:location_id => locations.map(&:ids)

Rails: Sum of values in all Transactions that belong_to an Activity

Live site: http://iatidata.heroku.com
Github: https://github.com/markbrough/IATI-Data
Based on aid information released through the IATI Registry: iatiregistry.org
I'm a bit of a Rails n00b so sorry if this is a really stupid question.
There are two key Models in this app:
Activity - which contains details
such as recipient country, funding
organisation
Transaction - which contains details such as how much money (value) was committed or disbursed (transaction_type), when, to whom, etc.
All Transactions nest under an Activity. Each Activity has multiple Transactions. They are connected together by activity_id. has_many :transactions and belongs_to :activity are defined in the Activity and Transaction Models respectively.
So: all of this works great when I'm trying to get details of transactions for a single activity - either when looking at a single activity (activity->show) or looping through activities on the all activities page (activity->index). I just call
#activities.each do |activity|
activity.transactions.each do |transaction|
transaction.value # do something like display it
end
end
But what I now really want to do is to get the sum of all transactions for all activities (subject to :conditions for the activity).
What's the best way to do this? I guess I could do something like:
#totalvalue = 0
#activities.each do |activity|
activity.transactions.each do |transaction|
#totalvalue = #totalvalue + transaction.value
end
end
... but that doesn't seem very clean and making the server do unnecessary work. I figure it might be something to do with the model...?! sum() is another option maybe?
This has partly come about because I want to show the total amount going to each country for the nice bubbles on the front page :)
Thanks very much for any help!
Update:
Thanks for all the responses! So, this works now:
#thiscountry_activities.each do |a|
#thiscountry_value = #thiscountry_value + a.transactions.sum(:value)
end
But this doesn't work:
#thiscountry_value = #thiscountry_activities.transactions.sum(:value)
It gives this error:
undefined method `transactions' for #<Array:0xb5670038>
Looks like I have some sort of association problem. This is how the models are set up:
class Transaction < ActiveRecord::Base
belongs_to :activity
end
class Activity < ActiveRecord::Base
has_and_belongs_to_many :policy_markers
has_and_belongs_to_many :sectors
has_many :transactions
end
I think this is probably quite a simple problem, but I can't work out what's going on. The two models are connected together via id (in Activity) and activity_id (in Transactions).
Thanks again!
Use Active Record's awesome sum method, available for classes:
Transaction.sum(:value)
Or, like you want, associations:
activity.transactions.sum(:value)
Let the database do the work:
#total_value = Transaction.sum(:value)
This gives the total for all transactions. If you have some activities already loaded, you can filter them this way:
#total_value = Transaction.where(:activity_id => #activities.map(&:id)).sum(:value)
You can do it with one query:
#total_value = Transaction.joins(:activity).where("activities.name" => 'foo').sum(:value)
My code was getting pretty messy summing up virtual attributes. So I wrote this little method to do it for me. You just pass in a collection and a method name as a string or symbol and you get back a total. I hope someone finds this useful.
def vsum collection, v_attr # Totals the virtual attributes of a collection
total = 0
collection.each { |collect| total += collect.method(v_attr).call }
return total
end
# Example use
total_credits = vsum(Account.transactions, :credit)
Of course you don't need this if :credit is a table column. You are better off using the built in ActiveRecord method above. In my case i have a :quantity column that when positive is a :credit and negative is a :debit. Since :debit and :credit are not table columns they can't be summed using ActiveRecord.
As I understood, you would like to have the sum of all values of the transaction table. You can use SQL for that. I think it will be faster than doing it the Ruby way.
select sum(value) as transaction_value_sum from transaction;
You could do
#total_value = activity.transactions.sum(:value)
http://ar.rubyonrails.org/classes/ActiveRecord/Calculations/ClassMethods.html

Resources