I have a rails application where I have following models.
BookingHotel
has_many :hotel_picture_partners, through: :hotel_pictures
has_many :booking_hotel_partner_details, dependent: :destroy
BookingHotelPartnerDetail
belongs_to :booking_hotel
HotelPicturePartner
belongs_to :hotel_picture, dependent: :destroy
I have a query as follows
#booking_hotel_partner_details = BookingHotelPartnerDetail.unscoped.select(:id, :booking_hotel_id, :partner_booking_hotel_id).includes(booking_hotel: :hotel_picture_partners)
This puts memory under pressure as it loads all data for included models.
Is there a way I can only load selective fields from booking_hotels & hotel_picture_partners tables ?
Also I want to get an activerecord array as response.
pluck method loads only attributes, without loading whole models. check it: http://apidock.com/rails/ActiveRecord/Calculations/pluck.
Try to rewrite it like this:
BookingHotelPartnerDetail.unscoped.select('booking_hotels.id as bh_id', 'hotel_picture_partners.id as hpp_id').joins(booking_hotel: :hotel_picture_partners)
Your project is probably already pretty far along, but for anyone else wanting this behaviour I've released a patch that filters columns if you have a .select(...) along with .includes(...) or .eager_load(...).
It's now included in a data-related gem I maintain, The Brick.
It works by overriding ActiveRecord::Associations::JoinDependency.apply_column_aliases() like this.
In order to enable this selective behaviour, add the special column name :_brick_eager_load as the first entry in your .select(...), which turns on the filtering of columns while the aliases are being built out. Here's an example based on your code:
#booking_hotel_partner_details =
BookingHotelPartnerDetail.unscoped
.includes(booking_hotel: :hotel_picture_partners)
.select(:_brick_eager_load, :id, :partner_booking_hotel_id, 'booking_hotel.name', 'hotel_picture_partners.notes')
Feel free to add myraid additional columns from any table as strings in the same way as the last two sample items I put into the .select(...).
Because foreign keys are essential to have everything be properly associated, they are automatically added, so you do not need to include :booking_hotel_id in your select list.
Hope it can save you both query time and some RAM!
Related
I know we need to use the following sudo code in case of Rails
Parent.all.each do |parent|
parent.childrens.update_all(:price => parent.price)
end
But I have like 5 Million Parent records and I know this would take a lot of time.
Is there a easy way to do the above through Rails or MySQL the fastest way (in a single query)
Parent.includes(:childrens).find_in_batches.find_in_batches do |group|
sleep(50)
group.each { |parent| parent.childrens.update_all(price: parent.price) }
end
This is the best you can come up with rails atleast..it will avoid n+1 also, since the records are huge, find_in_batches will help you, otherwise there is a possibility that your db/dyno gets locked..
I think you can use ActiveRecord callback functionality to achieve this.
Example code would look like this:
class Parent < ActiveRecord::Base
after_update :denormalize
has_many :children
private
def denormalize
children.update_all(price: price)
end
end
This will ensure that, whenever a parent object is modified the child will also be updated.
I have two models, one belongs to the other. They look like this:
class LittleClass < ActiveRecord::Base
has_many :little_class_sessions
end
and
class LittleClassSession < ActiveRecord::Base
belongs_to :little_class
end
LittleClassSession has a column called little_class_id. I want to get all LittleClassSession but also have the associated LittleClass returned to me in the same hash.
Is there some way to do this that's built into Rails? Or is there a clean way to do this?
And is this something that I build into the LittleClass or LittleClassSession model with scope?
When you query ActiveRecord you will get an array of ActiveRecord:Relation. It is a specific entity which starts your query. You can of course join dependent tables (as in your example with one-to-many relation). But you will still need to go over those dependent relations to build whatever object you need.
Here is a sketch of what I mean (assume we search for all little class sessions with specific little class id):
class_sessions = LittleClassSession.includes(:little_class).where(:little_classes => {:id => 1})
class_sessions.each do |relation|
test_hash = relation.attributes.merge!({:little_class => relation.little_class.attributes});
puts test_hash
end
test_hash will include all the attributes of the little class session as well as attributes of the little class under corresponding key.
I want to delete the tokens I created for a post, when I am deleting the post itself. But if the token does not exist, I don't want Rails to throw an error and stop executing.
Right now, this is how I go about it. I think this code is way too chunky. Is there any neat way to accomplish the same?
DownloadToken.find_by_post_id(post.id).destroy unless DownloadToken.find_by_post_id(#post.id).nil?
This is one way(old syntax)
DownloadToken.find_by_post_id(post.id).try(:destroy)
Newer syntax in rails:
DownloadToken.find_by(id: post.id).try(:destroy)
With Ruby's safe navigation operator:
DownloadToken.find_by(id: post.id)&.destroy
Look at your Post model; post.rb
Ie.
Class Post < ActiveRecord::Base
has_many :download_tokens, dependent: :destroy
end
Now when you delete a Post object, whatever the association; has_many, has_one, it will find the destroy the dependent also. In this case the DownloadToken(s)
DownloadToken.find_by_post_id(post.id)&.destroy
Executes destroy only if the query result is not nil. It's the abbreviated version of:
token = DownloadToken.find_by_post_id(post.id)
token.destroy if token
If you are certain you 'll handle the post deletion with its destroy method, than you can follow Jay's answer and it will work just fine.
If you will use the delete method on post you need some extra functionality to handle the download_tokens.
class DownloadToken < ActiveRecord::Base
def self.remove_post_tokens(the_post_id)
where(post_id: the_post_id).destroy_all
end
end
so your sequence will be:
id = post.id
post.delete #or post.destroy
DownloadToken.remove_post_tokens(id)
That scenario is not purely academic, because the dependent destroy action can be really expensive, eg if you have too many download_tokens, so you would not want it to be in the same transaction as post's destruction.
I need some advice. I'm making an app that is a little complicated to learn. It has a user model. Once a user signs up, I'd like a modal dialog to appear the first time (and only the first time) he/she access certain pages. For example, there's a "vote" page and a "create a poll" page, etc.
What's the best way to implement this type of help system? I thought of adding a column to my user model that can contains a default list of values like "vote,create,share." When the user accesses the page that corresponds to one of those actions, if the name of the action is still in the list, the modal appears and the name of the action is removed from the list.
Does this sound reasonable? I know that in a normalized database, you shouldn't store multiple values in a single field. But it seems crazy to create a table called "actions" and another joining table to relate users to actions. There are only 4 or 5 things the user can do on the site.
Or is there some other way to do this that I'm missing? Thanks in advance for your advice.
You should probably make a UserTrigger model that can be used to "trigger" certain actions like this. It would look something like this in practice:
<% for_trigger(#user, :first_time_help) do %>
...help content...
<% end %>
This method would have a definition approximately like:
def for_trigger(model, name)
if (model.triggers.where(:name => name.to_s).delete_all > 0)
yield
end
end
A softer version would have a should_trigger? and did_trigger! method pair, where one would test and the other would actually remove the trigger record. This single-shot one should be good enough for most cases.
You would have a definition in your User model roughly like:
class User < ActiveRecord::Base
has_many :triggers,
:class_name => 'UserTrigger'
end
class UserTrigger < ActiveRecord::Base
belongs_to :user
end
You will have to pre-populate these trigger records after_create on your User model, or any other models that require it. To re-set the trigger, just re-add the record.
I am working on a rails project and am having some issues with the following join:
#page = Page.find(params[:id], :joins => "LEFT JOIN page_translations ON page_translations.page_id = pages.id")
For some reason its only pulling back everything from the Pages table.
Here is my model for Page
class Page < ActiveRecord::Base
has_many :users_pages
has_many :users, :through => :users_pages
has_many :page_translations
has_many :categories
accepts_nested_attributes_for :page_translations
accepts_nested_attributes_for :categories
end
Here is my model for PageTranslation
class PageTranslation < ActiveRecord::Base
belongs_to :pages
end
Thanks in advance for all of the help!
Edit (#thenduks)
The log runs two separate queries:
Page Load (0.5ms) SELECT `pages`.* FROM `pages` WHERE (`pages`.`id` = 1) LIMIT 1
PageTranslation Load (0.5ms) SELECT `page_translations`.* FROM `page_translations` WHERE (`page_translations`.page_id = 1)
Here is what my controller looks like:
#page = Page.find(params[:id], :include => :page_translations)
I was stumped about this same thing and wasted a few hours trying to figure it out. It turns out that using the joins method of the query interface doesn't initialize the models related to the tables being joined. You can see this by watching the SQL statements in the server console, or by even redirecting ActiveRecord logging to STDOUT in your Rails console.
I was very disappointed by this. It just doesn't seem like how the joins method should work -- it certainly wasn't what I was expecting. I was expecting it to eager load, since it was in the eager load section of the Edge Guides.
Anyway, I couldn't waste any more time trying to figure it out, so what I did instead is use the fancy query interface to simply build my query, used to_sql to get the SQL for my query, and then passed the SQL to select_all, which returns an array of hashes, where each element in the array (each hash) represents a row.
Example:
query = Post.joins("LEFT JOIN categories ON post.category_id = categories.id")
query.select("posts.*, category.category_name")
con = ActiveRecord::Base.connection
results = con.select_all(query.to_sql)
Results:
[{"id": 1, "title": "joins, why have you forsaken me", "category_name": "frustration"},{"id": 2, "title": "pizza", "category_name": "food"}]
To be honest, I would still like to know for certain if it is possible to do it the way we think it should work, or the way it ought to work. Otherwise, I see no reason for having a joins method, other than to help us build the query. So if any Rails gurus out there know how to use joins to populate models related to those tables, PLEASE LET ME (US) KNOW!
Anyway, I hope this helps you move along for now.
UPDATE: So I think I just figured it out. I stumbled across this blog post. As it turns out, when using the joins method of the query interface, Rails will actually add the columns you selected from the joined tables as attribute methods of the model being joined against.
Using the same example above, I can actually access the category_name of each post by simply calling post.category_name. #$%! Unbelievably simple, but no documentation whatsoever on this!
Here it is once again:
query = Post.joins("LEFT JOIN categories ON post.category_id = categories.id")
query.select("posts.*, category.category_name")
posts = query.all
# access a post's category name
puts posts[0].category_name
# this is what I thought I would be able to do
# without Rails making another query to the database
puts posts[0].category.category_name
I hope this helps! :)
How about:
Page.find( params[:id], :include => :page_translations )
Edit:
Ok, so some time recently the behavior of ActiveRecord when it comes to joins/includes seems to have changed. The guides still refer to being able to do this though two associations, as in has_many :orders, :include => :line_items and similar... but as far as including records from a has_many... After consulting with a co-worker we came across some info on the subject. Seems that the single monolithic queries were just getting too complex and ugly and it was causing problems for some of the fancier niceties that ActiveRecord gives you and duplicate rows, that kind of thing.
TL;DR: It doesn't work like that anymore. 2 queries is expected.