Join the associated object into the belongs_to object - ruby-on-rails

I have an Event model which has many DateEntry because an event can have multiple datetimes.
I have a view where I see all the events with date entries for a specific day. For which I've made this class method in models/event.rb:
def self.by_date(date)
includes(:date_entries)
.where(date_entries: { begins_at: date.beginning_of_day..date.end_of_day })
end
And it works fine.
Now on the view I loop over the events I grabbed with that query:
#events.each do |event|
# whatever
I am looking for a way to use the date from the selected DateEntry in the loop. I think I have to use ActiveRecords joins method, but I have tried in many ways and when I am not getting an error the output is still the same.
For clarifying, I want to do in the loop something like event.selected_date_entry_by_the_by_date_method.begins_at or something like that.

First, in your example the date for all events will be the same. You could easily set #date in your controller and then use that in the views.
Second, It seems like you should be doing things the other way around. Instead of finding the Events you should be finding DateEntries and iterating over those.

Your method Event.by_date used includes so it eager loads the date_entries for the events you need. It automatically does a join. In the loop just do
#events.each do |event|
data_entries = event.date_entries
# do something with the dates...
end
Your Event model should have has_many :date_entries.
The date_entries call will return all the dates for that event and won't result in another query.
Run this in the rails console and you will see the queries being run.

You can do it this way, but i suggest to do it as followng, this class method should be a scope or even class method but in DateEntry model:
class DateEntry < ActiveRecord::Base
scope :by_date,->(date){ where(...)}
end
#events.each do |event|
event.date_entries.by_date(date).each do |date_entry|
# .....
end
# .....
end

Related

In Rails, can I filter one-to-many relationships without updating the database

I am using Rails 5 and I want to be able to filter a one-to-many relationship to only send a subset of the child items to the client. The data model is pretty standard, and looks something like this:
class Parent < ApplicationRecord
has_many :children, class_name: 'Child'
end
class Child < ApplicationRecord
belongs_to :parent
end
When the client makes a call, I only want to return some of the Child instances for a Parent.
This is also complicated because the logic about which Child objects should be returned is absurdly complicated, so I am doing it in Ruby instead of the database.
Whenever I execute something like the following, Rails is attempting to update the database to remove the association. I don't want the database to be updated. I just want to filter the results before they are sent to the client.
parent.children = parent.children.reject { |child| child.name.include?('foo') }
Is there a way to accomplish this?
Add an instance method in Parent model
def filtered_children
children.where.not("name like ?", '%foo%')
end
Call filtered_children wherever required, it doesn't make sense to reset the existing association instance variable. The same queries are cached so it doesn't matter if you call them one time or multiple times. But you can always memoize the output of a method to make sure the the method is not evaluated again second time onwards,
def filtered_children
#filtered_children ||= children.where.not("name like ?", '%foo%')
end
Hope that helps!
DB update is happening because filtered records are being assigned back to parent.children. Instead another variable can be used.
filtered_children = parent.children.reject { |child| child.name.include?('foo') }

ActiveRecord Eager Loading after initial query

Let's say I have a simple model association, where a blog Post has many Comments on it.
class Post < ActiveRecord::Base
has_may :comments
end
If I wanted to avoid "N + 1" queries and eager load all the associations beforehand, I could do -
Post.includes(:comments).where(title: "foo")
Which runs two queries. The first one looks up the Post using the where condition and the second looks up all the associated comments at once.
But what if I already have a Post object? Can I "add" an includes to it after the initial result set to run the 2nd bulk query that looks up all the associations?
It seems counter intuitive to do a delayed eager load, but I assume looking up all the associations at once would still save me from having to look them up individually as I loop through them.
e.g.
p = Post.where(title: "foo")
# Will the below work?
p.includes(:comments)
p.comments.each do |comment|
puts comment.text
end
Let's break down your code.
posts = Post.where(title: 'foo')
That searches for all posts with a title of foo. posts is an ActiveRecord::Relation object and you can chain more ActiveRecord commands like select, limit, includes, etc.
So doing posts.includes(:comments) is valid and should eager load the comments. Just don't forget to assign the result to posts again (or another variable).
What will not work is posts.comments because comments is a method that works on an instance of Post. Changing to the following code will work
posts = Post.where(title: 'foo')
posts = posts.includes(:comments)
posts.each do |post|
post.comments.each do |comment|
puts comment.text
end
end

Rails "includes" Method and Avoiding N+1 Query

I don't understand the Rails includes method as well as I'd like, and I ran into an issue that I'm hoping to clarify. I have a Board model that has_many :members and has_many :lists (and a list has_many :cards). In the following boards controller, the show method looks as follows:
def show
#board = Board.includes(:members, lists: :cards).find(params[:id])
...
end
Why is the includes method needed here? Why couldn't we just use #board = Board.find(params[:id]) and then access the members and lists via #board.members and #board.lists? I guess I'm not really seeing why we would need to prefetch. It'd be awesome if someone could detail why this is more effective in terms of SQL queries. Thanks!
Per the Rails docs:
Eager loading is the mechanism for loading the associated records of
the objects returned by Model.find using as few queries as possible.
When you simply load a record and later query its different relationships, you have to run a query each time. Take this example, also from the Rails docs:
clients = Client.limit(10)
clients.each do |client|
puts client.address.postcode
end
This code executes a database call 11 times to do something pretty trivial, because it has to do each lookup, one at a time.
Compare the above code to this example:
clients = Client.includes(:address).limit(10)
clients.each do |client|
puts client.address.postcode
end
This code executes a database call 2 times, because all of the necessary associations are included at the onset.
Here's a link to the pertinent section of the Rails docs.
Extra
A point to note as of recent: if you do a more complex query with an associated model like so:
Board.includes(:members, lists: :cards).where('members.color = ?', 'foo').references(:members)
You need to make sure to include the appended references(:used_eager_loaded_class) to complete the query.

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

Group_by on a belongs_to assocciation

I would like to use the group_by method, but instead of using a column in the database:
#posts.group_by(&:date)
I want to have something like:
#posts.group_by(&:author)
Where post belongs_to author.
I'm just not sure of the syntax?
You can write in your model:
def author_name
self.author.name
end
and then
#posts.group_by(&:author_name)
In this instance presumably you could actually just do
#posts.group_by(&:author_id)
More generally, you can pass a block to group_by, so if you need to group on some arbitrary logic:
#posts.group_by { |post|
... some code returning a value to group this post by ...
}
As a hopefully related aside, last time I looked, the implementation of group_by preserves the relative order of the data fed to it, which can be useful to remember as it allows you to sort the data in the 'flat' list of posts, and then when you group them the order is maintained within the groupings, rather than dealing with sorting withing the nested data output by group_by.

Resources