So I've read a lot about the rails includes method but I'm still a bit confused about what's the best situation to use it.
I have a situation where I have a user record and then this user is related to multiple models like client, player, game, team_player, team, server and server_center.
I need to display specific attributes from the related models in a view. I only need around 1-2 attributes from a specific model and I don't use the others.
I already added delegates for example to get the server.name from player I can use server_name but in this situation do I include all of the tables from which I need the attributes or is there something else I do because I only need a couple of attributes from the model.
My query is as follows at the moment:
#user_profile = User
.includes({:client => [:player, :team_player => [:team]]},
:game,
{:server_center => :server})
.where(game_id: #master.admin.games)
Includes ensures that all of the specified associations are loaded using the minimum possible number of queries.
Let say we have 2 models named User and Profile :
class User < ActiveRecord::Base
has_one :profile
end
class Profile < ActiveRecord::Base
belongs_to :user
end
If we are iterating through each of the users and display the name of each user were name field resides in Profile model which has a association with User model, we would normally have to retrieve the name with a separate database query each time. However, when using the includes method, it has already eagerly loaded the associated person table, so this block only required a single query.
without includes:
users = User.all
users.each do |user|
puts user.profile.name # need extra database query for each time we call name
end
with includes
# 1st query to get all users 2nd to get all profiles and loads to the memory
users = User.includes(:profile).all
users.each do |user|
puts user.profile.name # no extra query needed instead it loads from memory.
end
Eager Loading is used to prevent N+1 query problems. basically it does left outer join and this plays an important role in speeding up request response or optimizing the queries. eg: if we are having huge amount users and if we want to iterate through those users and their corresponding profile. no of time which we will be hitting database will be equals to number of users. but if we are using includes it will keep all profile into memory later when we iterate through the users it will fetch from this memory instead of querying.
Eager loading may not always be the best the cure for our N+1 queries for eg: if you are dealing with some complex queries preferably looks for some caching solutions like Russian Doll caching etc.. still both method has his own pros & cons end of the day it's up to you to determine the best approach.
one useful gem which helps to detect N+1 query is bullet
Related
I have a simple rails application for a competition with these models: User, Team, and Trip. The associations are: team has_many users, and user has_many trips. Trips has a distance_miles field.
I'm am working on a simple view to display a table of teams with their stats that have multiple aggregations.
My initial approach was the following but results in N+1 queries.
#teams.each do |team|
#team.user.joins(:trips).count()
#team.user.joins(:trips).sum(:distance_miles)
end
The following works, but seems ugly as I want to add pagination, sorting and filtering eventually.
#teams = Team.left_joins(users: :trips)
.select('teams.id, teams.name, SUM(trips.distance_miles) as num_miles, COUNT(trips.id) as num_trips')
.group(:id)
I've been reading preload and includes but cant seem to get it to also get multiple aggregations. The following gets me part way there, but it is now missing fields from Team and still need the other aggregation:
#teams = Team.includes(users: :trips).group(:id).sum('trips.distance_miles')
Is there a "rails way" that I'm missing?
ActiveRecord::Calculations which provides .sum, .count is only really useful in very simple cases as it always creates a separate database query.
You're on the right track with .select but it could be cleaned up by extracting the code into the model:
class Team
def self.with_trips
trips = Trip.arel_table
self.left_joins(users: :trips)
.select(
self.arel.projections, # adds the existing select clause
trips[:distance_miles].sum.as(:num_miles),
trips[:id].count.as(:distance_miles)
)
.group(:id)
end
end
Using .eager_load instead of .left_joins will result in a PG::GroupingError on Postgres as the results would be ambiguous. Instead you need use a window function.
I would like to create some sample data for a user when they register so that they do not have to start with a blank canvas.
I am presently defining lots of static data in classes which I then iterate over, however setting up relationships is cumbersome and I think that this is quite brittle.
I think that having some demo fixtures (separate from my test set) would be a good way to do this, but as records are tied to an account I can't see how I can insert this data and attach it to the account when loading the fixtures.
This might not even be the best approach, but if there is a better way then please let me know.
Every RAILS application has seeds.rb present in db/ folder.So as the name says,it is used to seed your db by default records that you may want.
So this is how i am using my seeds.rb.Creating many records,constants and then to put those records in your db,just run rake db:seed assuming you have db ready.See HERE for more infor
my seeds.rb
###seed eventype table with default values
["birthday","farewell","party"].each do |f|
EventType.find_or_create_by({name:f.upcase})
end
###create users with random names
##after creating users,create associated fields
%w[mike john paul allen harry betty].each_with_index do |name,index|
#user = User.find_or_create_by({email:"user_#{name}#gmail.com"})
##user has_one address
#user.create_address({:address_2=>"street 1,near berry lane"})
##user has_many pictures
#user.pictures.create!({:title=>"Title of the picture",:picture => File.new("#{Rails.root}/public/images/test.jpg")})
end
You can even use (0..6).each do loop to create n records as you wish in db.
for example:-
(0..100).each do
###create user/pictures etc
end
However you must be careful to follow all validations and create valid record else this wont work.
for example,if in user model,you are expecting username as well,then in seeds.rb,you must pass the username so that it passes the validation easily.
=========================================================================
IF you dont want to use db seed,you can use a callbacks on: :create(i dont prefer observers).Simple example can be :
##in user.rb
after_commit :create_default_account, on: :create
def create_default_account
##assuming user has_one :account
self.build_account({:name=>"Default account"})
end
HOPE IT HELPS
Some of my classes :
class User
embeds_many :notifications
field :first_name
field :last_name
def name{ "#{first_name} #{last_name}" }
class Notification
embedded_in :user
belongs_to :sender, class_name: "User", inverse_of: nil
Now in my views, I implemented a small mailbox system for notifications. However, it's currently hitting N+1 times the database :
<% current_user.notifications.sort{...}.each do |notif|%>
...
<%= notif.sender.name if notif.sender %>
The problem here is the notif.sender.name which causes N hits on the database. Can I somehow preload/eager load this ? Something like current_user.notifications.includes(:sender) (but which would work :D)
I currently only need the sender name.
I think you're half out of luck here. Mongoid has an error message like:
Eager loading in Mongoid only supports providing arguments to M.includes that are the names of relations on the M model, and only supports one level of eager loading. (ie, eager loading associations not on the M but one step away via another relation is not allowed).
Note the last parenthesized sentence in particular:
eager loading associations not on the M but one step away via another relation is not allowed
Embedding is a relation but you want to apply includes to the embedded relation and that's one step too far for Mongoid.
The fine manual does say that:
This will work for embedded relations that reference another collection via belongs_to as well.
but that means that you'd call includes on the embedded relation rather than what the models are embedded in. In your case, that means that you could eager load the senders for each set of embedded Notifications:
current_user.notifications.includes(:sender).sort { ... }
That still leaves you with the N+1 problem that eager loading is supposed to get around but your N will be smaller.
If that's still too heavy then you could denormalize the name into each embedded document (i.e. copy it rather than referencing it through the sender). Of course, you'd need to maintain the copies if people were allowed to change their names.
It's not perfect, but this article presents a possible solution.
You can load all the senders and use set_relation to avoid them to be loaded every time.
def notifications_with_senders
sender_ids = notifications.map(:sender_id)
senders = User.in(id: sender_ids).index_by(&:id)
notifications.each do |notification|
notification.set_relation(:sender, senders[notification.sender_id])
end
end
Would be great to have that as a Relation method (like includes of Rails Active Record)
I'm creating a customized "Buy Now" page that is a combination of User, address, Sale, SaleLine, and Payment models. To initiate the payment process, I need to specifie the Sale ID in a callback. So my #new method looks something like this...
# new
def bitcoin
require 'cgi'
#payment_to_tender = get_cost
#sale = Sale.create
begin
payment = create_new_payment_wallet(ENV["BITCOIN"], "http://www.example.com/a/b", #sale.id)
end
end
So the key line in there is the middle where a new Sale record is created. This page doesn't require any kind of login or anything (because it's the signup page technically). Will this be a problem? I think anytime, even a bot navigates to the page, it will spawn yet another Sale record. Will that eventually catch up with me? Should I run a script nightly that deletes all orphan Sale records that are older than a day, or should I try a different algo?
Rails can handle as many models as required
Models are just .rb files which are opened when you call ActiveRecord, they're not applications or anything super-resource intensive
However, what they represent is much more than just opening a file. The question you're really asking is "is my schema set up correctly?", which is a different ballgame
ActiveRecord Assocations
Rails is "object orientated", which means everything you do has to work around an object. This is typically an ActiveRecord Object, which is made up of a database query & associated data
One of the biggest problems with Rails apps is an inefficient use of the ActiveRecord Association structure. ActiveRecord Associations work by defining "relations" in your models, allowing you to call one piece of data & automatically have its related data attached in the object
The problem for most people is ActiveRecord Assocations pick up data they don't need, causing unnecessary expensive database calls. This is where the problems arise, and is what you're trying to address
Creating Independent Records
If you want to create a record with another, you can use the after_create method, like this:
#app/models/bitcoin.rb
Class BitCoin < ActiveRecord::Base
after_create :create_sale
end
This will actually create a sale record for you, if it's related correctly
dream
I'd like to keep record of when a user changes their address.
This way, when an order is placed, it will always be able to reference the user address that was used at the time of order placement.
possible schema
users (
id
username
email
...
)
user_addresses (
id
label
line_1
line_2
city
state
zip
...
)
user_addresses_map (
user_id
user_address_id
start_time
end_time
)
orders (
id
user_id
user_address_id
order_status_id
...
created_at
updated_at
)
in sql, this might look something like: [sql]
select ua.*
from orders o
join users u
on u.id = o.user_id
join user_addressses_map uam
on uam.user_id = u.id
and uam.user_address_id = o.user_address_id
join user_addresses ua
on ua.id = uam.user_address_id
and uam.start_time < o.created_at
and (uam.end_time >= o.created_at or uam.end_time is null)
;
edit: The Solution
#KandadaBoggu posted a great solution. The Vestal Versions plugin is a great solution.
snippet below taken from http://github.com/laserlemon/vestal_versions
Finally, DRY ActiveRecord versioning!
acts_as_versioned by technoweenie was a great start, but it failed to keep up with ActiveRecord’s introduction of dirty objects in version 2.1. Additionally, each versioned model needs its own versions table that duplicates most of the original table’s columns. The versions table is then populated with records that often duplicate most of the original record’s attributes. All in all, not very DRY.
vestal_versions requires only one versions table (polymorphically associated with its parent models) and no changes whatsoever to existing tables. But it goes one step DRYer by storing a serialized hash of only the models’ changes. Think modern version control systems. By traversing the record of changes, the models can be reverted to any point in time.
And that’s just what vestal_versions does. Not only can a model be reverted to a previous version number but also to a date or time!
Use the Vestal versions plugin for this:
Refer to this screen cast for more details.
class Address < ActiveRecord::Base
belongs_to :user
versioned
end
class Order < ActiveRecord::Base
belongs_to :user
def address
#address ||= (user.address.revert_to(updated_at) and user.address)
end
end
Thought I'd add an updated answer. Seems the paper_trail gem has become the most popular one for versioning in Rails. It supports Rails 4 as well.
https://github.com/airblade/paper_trail
From their readme:
To setup and install:
gem 'paper_trail', '~> 3.0.6'
bundle exec rails generate paper_trail:install
bundle exec rake db:migrate
Basic Usage:
class Widget < ActiveRecord::Base
has_paper_trail
end
For a specific instance of the Widget class:
v = widget.versions.last
v.event # 'update' (or 'create' or 'destroy')
v.whodunnit # '153' (if the update was via a controller and
# the controller has a current_user method,
# here returning the id of the current user)
v.created_at # when the update occurred
widget = v.reify # the widget as it was before the update;
# would be nil for a create event
I've only played with it but I'm about to start a pretty ambitious site which will require good versioning of certain classes and I've decided to use paper_trail.
===EDIT====
I have implemented the paper_trail gem in production at www.muusical.com and it has worked well using the above. The only change is that I am using gem 'paper_trail', '~> 4.0.0.rc' in my Gemfile.
From a data architecture point of view, I suggest that to solve your stated problem of
...when an order is placed, it will
always be able to reference the user
address that was used at the time of
order placement.
... you simply copy the person's address into an Order model. The items would be in OrderItem model. I would reformulate the issue as "An order happens at a point in time. The OrderHeader includes all of the relevant data at that point in time."
Is it non-normal?
No, because the OrderHeader represents a point in time, not ongoing "truth".
The above is a standard way of handling order header data and removes a lot of complexity from your schema as opposed to tracking all changes in a model.
--Stick with a solution that solves the real problem, not possible problems--does anyone need a history of the user's changes? Or do you just need the order headers to reflect the reality of the order itself?
Added: And note that you need to know which address was eventually used to ship the order/invoice to. You do not want to look at an old order and see the user's current address, you want to see the address that the order used when the order was shipped. See my comment below for more on this.
Remember that, ultimately, the purpose of the system is to model the real world. In the real world, once the order is printed out and sent with the ordered goods, the order's ship-to isn't changing any further. If you're sending soft goods or services then you need to extrapolate from the easier example.
Order systems are an excellent case where it is very important to understand the business needs and realities--don't just talk with the business managers, also talk with the front-line sales people, order clerks, accounts receivable clerks, shipping dept folks, etc.
You're looking for the acts_as_audited plugin. It provides an audits table and model to be used in place of your map.
To set it up run the migration and add the following to your user address model.
class UserAddress < ActiveRecord::Base
belongs_to :user
acts_as_audited
end
Once you've set it up, all you need to do is define an address method on order. Something like this:
class Order < ActiveRecord::Base
belongs_to :user
attr_reader :address
def address
#address ||= user.user_address.revision_at(updated_at)
end
end
And you can access the users' address at the time of order completion with #order.address
revision_at is a method added to an audited model by acts_as_audited. It takes a timestamp and reconstructs the model as it was in that point of time. I believe it pieces the revision together from the audits up on that specific model before the given time. So it doesn't matter if updated_at on the order matches a time exactly.
I think this would be as simple as:
Users:
id
name
address_id
UserAddresses:
id
user_id
street
country
previous_address_id
Orders
id
user_id #to get the users name
user_address_id #to get the users address
Then when a user changes their address, you do a sort of "logical delete" on the old data by creating a new UserAddress, and setting the "previous_address_id" field to be the pointer to the old data. This removes the need for your map table, and creates a sort of linked list. In this way, whenever an order is placed, you associate it to a particular UserAddress which is guaranteed never to change.
Another benefit to doing this is that it allows you to following the changes of a users address, sort of like a rudimentary logger.