Rails - Increasing performance of repeated if statement - ruby-on-rails

I'm using the public_activity gem and in the output, I'm checking if the trackable owner is the same as the current user:
= a.owner == current_user ? 'You' : a.owner.name
did this activity
I get a bunch of cache calls in the log:
User Load (1.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Rendered public_activity/post/_create.html.haml (1.4ms)
Rendered public_activity/_snippet.html.haml (11.4ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Rendered public_activity/post/_create.html.haml (13.9ms)
Rendered public_activity/_snippet.html.haml (18.9ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Rendered public_activity/comment/_comment.html.haml (0.9ms)
Rendered public_activity/_snippet.html.haml (12.1ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Rendered public_activity/comment/_comment.html.haml (2.7ms)
Rendered public_activity/_snippet.html.haml (56.3ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Rendered public_activity/comment/_comment.html.haml (0.6ms)
Rendered public_activity/_snippet.html.haml (4.5ms)
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Rendered public_activity/content/_comment.html.haml (2.1ms)
Rendered public_activity/_snippet.html.haml (9.5ms)
Is there any way to eager load the conditional?

#jverban is correct that you can compare the record IDs to avoid needless record loading. To answer your question about eager loading though, yes you can eager load using the includes method in the ActiveRecord query chain. For example:
Activity.includes(:owner).latest
That will tell Rails you intend to reference the owner relation and so they should be loaded as well.
I highly recommend adding the bullet gem to your project (only in development and test environments) to detect N+1 queries and warn you when you've got an N+1 query situation like this happening.

You shouldn't need to load the user record, just compare id attributes
= a.owner_id == current_user.id ? 'You' : a.owner.name
The cache calls will likely still happen if multiple activity owners are not the current user (to get the owner name).

Related

Solidus Paypal checkout fails in sandbox for non-PayPal payment variants

I have built a webshop in Ruby on Rails using Solidus.
When I try to checkout in the PayPal sandbox with credit card or SEPA with a German address, it complains that the shop does not deliver there.
It works fine when I use PayPal payment with an account, also to a German address in Berlin...
This is the error in the ruby console:
Started GET "/solidus_paypal_commerce_platform/shipping_rates?order_id=R977809399&order_token=[FILTERED]&address%5Bcity%5D=Berlin&address%5Bstate%5D=&address%5Bcountry_code%5D=DE&address%5Bpostal_code%5D=10117" for 127.0.0.1 at 2022-09-29 20:29:49 +0200
Processing by SolidusPaypalCommercePlatform::ShippingRatesController#simulate_shipping_rates as */*
Parameters: {"order_id"=>"R977809399", "order_token"=>"[FILTERED]", "address"=>{"city"=>"Berlin", "state"=>"", "country_code"=>"DE", "postal_code"=>"10117"}}
Spree::User Load (0.1ms) SELECT "spree_users".* FROM "spree_users" WHERE "spree_users"."deleted_at" IS NULL AND "spree_users"."spree_api_key" = ? LIMIT ? [["spree_api_key", "undefined"], ["LIMIT", 1]]
Spree::Order Load (0.1ms) SELECT "spree_orders".* FROM "spree_orders" WHERE "spree_orders"."number" = ? LIMIT ? [["number", "R977809399"], ["LIMIT", 1]]
Spree::User Load (0.1ms) SELECT "spree_users".* FROM "spree_users" WHERE "spree_users"."deleted_at" IS NULL AND "spree_users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
CACHE Spree::Order Load (0.0ms) SELECT "spree_orders".* FROM "spree_orders" WHERE "spree_orders"."number" = ? LIMIT ? [["number", "R977809399"], ["LIMIT", 1]]
CACHE Spree::User Load (0.0ms) SELECT "spree_users".* FROM "spree_users" WHERE "spree_users"."deleted_at" IS NULL AND "spree_users"."id" = ? LIMIT ? [["id", 3], ["LIMIT", 1]]
TRANSACTION (0.0ms) begin transaction
Spree::Country Load (0.1ms) SELECT "spree_countries".* FROM "spree_countries" WHERE "spree_countries"."iso" = ? LIMIT ? [["iso", "DE"], ["LIMIT", 1]]
Spree::State Load (0.1ms) SELECT "spree_states".* FROM "spree_states" WHERE "spree_states"."country_id" = ? AND "spree_states"."abbr" = ? ORDER BY "spree_states"."name" ASC LIMIT ? [["country_id", 57], ["abbr", ""], ["LIMIT", 1]]
Spree::State Load (0.1ms) SELECT "spree_states".* FROM "spree_states" WHERE "spree_states"."country_id" = ? AND "spree_states"."name" = ? ORDER BY "spree_states"."name" ASC LIMIT ? [["country_id", 57], ["name", ""], ["LIMIT", 1]]
Spree::Store Load (0.0ms) SELECT "spree_stores".* FROM "spree_stores" WHERE "spree_stores"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Spree::Order Exists? (0.1ms) SELECT 1 AS one FROM "spree_orders" WHERE "spree_orders"."number" = ? AND "spree_orders"."id" != ? LIMIT ? [["number", "R977809399"], ["id", 10], ["LIMIT", 1]]
Spree::Adjustment Load (0.0ms) SELECT "spree_adjustments".* FROM "spree_adjustments" WHERE "spree_adjustments"."order_id" = ? AND "spree_adjustments"."source_type" = ? [["order_id", 10], ["source_type", "Spree::PromotionAction"]]
Spree::LineItem Load (0.0ms) SELECT "spree_line_items".* FROM "spree_line_items" WHERE "spree_line_items"."order_id" = ? ORDER BY "spree_line_items"."created_at" ASC, "spree_line_items"."id" ASC [["order_id", 10]]
Spree::Product Load (0.0ms) SELECT "spree_products".* FROM "spree_products" INNER JOIN "spree_variants" ON "spree_products"."id" = "spree_variants"."product_id" WHERE "spree_variants"."id" = ? LIMIT ? [["id", 45], ["LIMIT", 1]]
TRANSACTION (0.0ms) rollback transaction
Completed 422 Unprocessable Entity in 35ms (Views: 0.2ms | ActiveRecord: 0.8ms | Allocations: 31326)
Would be grateful for any pointers where to adjust that,
as I cannot make head or tail of it.
I don't know why Solidus is erroring internally, but I can tell you that if the rendered PayPal button has an onShippingChange callback yet that callback does not respond with success or times out (due to some error, generally), the PayPal checkout will give that message since you're indicating the address needs to be checked during payment approval yet are not responding that it's OK when queried.
Another possible flow design -- likely not used/offered by Solidus -- is to not specify onShippingChange and to check the address after approval (but before final order capture) and provide a mechanism to patch it when it's not a valid address you'll accept.

Rails: ActiveRecord perform pointless query multiple times over the same model

In my app I have overwritten current_user devise method a bit. The idea is that if certain cookie is present method check the organization by the id inside that cookie and returns owner of this organization instead of regular user:
def current_user
user = warden.authenticate(scope: :user)
return nil if user.nil?
if user.admin? && cookies.key?('mock_admin_login')
organization = Organization.includes(:creator).find(cookies.encrypted[:mock_admin_login])
return organization.creator
end
user
end
Everything works correct but when I take a look at my console I noticed that Organization query is performed multiple times:
CACHE Organization Load (0.5ms) SELECT "organizations".* FROM
"organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 9],
["LIMIT", 1]] ↳
app/controllers/concerns/current_methods_overwritten.rb:11:in
current_user' CACHE User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.9ms) SELECT
"organizations".* FROM "organizations" WHERE "organizations"."id" = $1
LIMIT $2 [["id", 9], ["LIMIT", 1]] ↳
app/controllers/concerns/current_methods_overwritten.rb:11:in
current_user' CACHE User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.7ms) SELECT
"organizations".* FROM "organizations" WHERE "organizations"."id" = $1
LIMIT $2 [["id", 9], ["LIMIT", 1]] ↳
app/controllers/concerns/current_methods_overwritten.rb:11:in
current_user' CACHE User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.3ms) SELECT
"organizations".* FROM "organizations" WHERE "organizations"."id" = $1
LIMIT $2 [["id", 9], ["LIMIT", 1]] ↳
app/controllers/concerns/current_methods_overwritten.rb:11:in
current_user' CACHE User Load (0.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.4ms) SELECT
"organizations".* FROM "organizations" WHERE "organizations"."id" = $1
LIMIT $2 [["id", 9], ["LIMIT", 1]] ↳
app/controllers/concerns/current_methods_overwritten.rb:11:in
current_user' CACHE User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (2.0ms) SELECT
"organizations".* FROM "organizations" WHERE "organizations"."id" = $1
LIMIT $2 [["id", 9], ["LIMIT", 1]] ↳
app/controllers/concerns/current_methods_overwritten.rb:11:in
current_user' CACHE User Load (4.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (0.4ms) SELECT
"organizations".* FROM "organizations" WHERE "organizations"."id" = $1
LIMIT $2 [["id", 9], ["LIMIT", 1]] ↳
app/controllers/concerns/current_methods_overwritten.rb:11:in
current_user' CACHE User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (42.8ms) SELECT
"organizations".* FROM "organizations" WHERE "organizations"."id" = $1
LIMIT $2 [["id", 9], ["LIMIT", 1]] ↳
app/controllers/concerns/current_methods_overwritten.rb:11:in
current_user' CACHE User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user' CACHE Organization Load (4.5ms) SELECT
"organizations".* FROM "organizations" WHERE "organizations"."id" = $1
LIMIT $2 [["id", 9], ["LIMIT", 1]] ↳
app/controllers/concerns/current_methods_overwritten.rb:11:in
current_user' CACHE User Load (1.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 [["id", 10]] ↳ app/controllers/concerns/current_methods_overwritten.rb:11:in current_user'
Although It might seem like a not big deal but server spends additional 30-40ms to perform this action every time when current_user method is called. Why this query is called so many times instead of one and how can I fix it?
You need to memoize the result so that its not reevaluated every time you call current_user.
If you look at the helper that devise generates you can see that it does just that:
def current_#{mapping}
#current_#{mapping} ||= warden.authenticate(scope: :#{mapping})
end
If you want to fix your existing method you want to make sure to memoize the DB calls:
def current_user
#current_user ||= warden.authenticate(scope: :#{mapping})
if #current_user&.admin? && cookies.key?('mock_admin_login')
#current_org || = Organization.includes(:creator)
.find(cookies.encrypted[:mock_admin_login])
#current_user = #current_org.creator
end
#current_user
end
But you really should implement this as a custom Warden strategy instead.

How to use devise_invitable properly with activeadmin ? (issues to send emails)

I am building an app in rails with an admin interface using the activeadmin gem. I have a User model, an Organization Model and an Account Model (polymorphic). A same user can have different types of accounts (Trainer, Team or Athlete) and an organization has many accounts (they are members of this organization).
Only the admin can create organizations and send invitations to join them. I have an OrganizationInvitation model in active admin and I am trying to use devise_invitable.
ActiveAdmin.register OrganizationInvitation do
permit_params :organization_id, :email, :status
form do |f|
f.semantic_errors
f.inputs do
f.input :organization, collection: Organization.all.map { |organization| [organization.name, organization.id] }
f.input :email
end
f.actions
end
controller do
def create
#organization_invitation = OrganizationInvitation.new(organization_invitation_params)
#email = organization_invitation_params[:email]
#user = User.invite!(email: #email)
if #organization_invitation.save
redirect_to admin_organization_invitations_path, notice: 'Invitation successfully sent'
else
redirect_to admin_organization_invitations_path, alert: 'Something went wrong'
end
end
private
def organization_invitation_params
params.require(:organization_invitation).permit(
:organization_id,
:email
)
end
end
end
There will be different small issues to handle the different types of accounts, the case in which a user already has an account etc but for now I am just trying to send an email to invite a new user to an organization. When I test it, the user gets created in the database but apparently no email gets sent. I did the basic setup for devise_invitable and I added this line in my development config file but it still does not work.
config.action_mailer.perform_deliveries = true
I never used these 2 gems before and feel I am doing sth wrong but do not know what. Thank you in advance!
Edit: Here are the logs as well
Started GET "/admin/organization_invitations" for 127.0.0.1 at 2019-03-21 16:10:44 +0000
Processing by Admin::OrganizationInvitationsController#index as HTML
AdminUser Load (3.3ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = $1 ORDER BY "admin_users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Rendering /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activeadmin-1.4.3/app/views/active_admin/resource/index.html.arb
(1.1ms) SELECT COUNT(*) FROM (SELECT 1 AS one FROM "organization_invitations" LIMIT $1 OFFSET $2) subquery_for_count [["LIMIT", 30], ["OFFSET", 0]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE (0.0ms) SELECT COUNT(*) FROM (SELECT 1 AS one FROM "organization_invitations" LIMIT $1 OFFSET $2) subquery_for_count [["LIMIT", 30], ["OFFSET", 0]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
(0.4ms) SELECT COUNT(*) FROM "organization_invitations"
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE (0.0ms) SELECT COUNT(*) FROM (SELECT 1 AS one FROM "organization_invitations" LIMIT $1 OFFSET $2) subquery_for_count [["LIMIT", 30], ["OFFSET", 0]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
OrganizationInvitation Load (0.8ms) SELECT "organization_invitations".* FROM "organization_invitations" ORDER BY "organization_invitations"."id" desc LIMIT $1 OFFSET $2 [["LIMIT", 30], ["OFFSET", 0]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Organization Load (1.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Organization Load (1.8ms) SELECT "organizations".* FROM "organizations"
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Rendered /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activeadmin-1.4.3/app/views/active_admin/resource/index.html.arb (194.2ms)
Completed 200 OK in 244ms (Views: 224.8ms | ActiveRecord: 13.5ms)
Started GET "/admin/organization_invitations/new" for 127.0.0.1 at 2019-03-21 16:10:46 +0000
Processing by Admin::OrganizationInvitationsController#new as HTML
AdminUser Load (0.5ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = $1 ORDER BY "admin_users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Rendering /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activeadmin-1.4.3/app/views/active_admin/resource/new.html.arb
Organization Load (0.3ms) SELECT "organizations".* FROM "organizations"
↳ app/admin/organization_invitations.rb:8
Rendered /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activeadmin-1.4.3/app/views/active_admin/resource/new.html.arb (105.3ms)
Completed 200 OK in 169ms (Views: 161.7ms | ActiveRecord: 0.8ms)
Started POST "/admin/organization_invitations" for 127.0.0.1 at 2019-03-21 16:10:51 +0000
Processing by Admin::OrganizationInvitationsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"XkxCRz+/BsvXAmyKUZk4UZhjtE1AcrhOnCxheNQGPQVP+j6tf+rZYVqqiU+CxtYR57pPShgjJdknG9PmiNtGOA==", "organization_invitation"=>{"organization_id"=>"1", "email"=>"myaddress#gmail.com"}, "commit"=>"Create Organization invitation"}
AdminUser Load (2.0ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = $1 ORDER BY "admin_users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
User Load (1.4ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["email", "antoinecquellier#gmail.com"], ["LIMIT", 1]]
↳ app/admin/organization_invitations.rb:18
User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."invitation_token" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["invitation_token", "c94d3b1e55b5cf39eba111085ec94eb1dcccca321308b87017a7f08b1634a3df"], ["LIMIT", 1]]
↳ app/admin/organization_invitations.rb:18
(0.3ms) BEGIN
↳ app/admin/organization_invitations.rb:18
User Update (1.6ms) UPDATE "users" SET "invitation_token" = $1, "invitation_created_at" = $2, "invitation_sent_at" = $3, "updated_at" = $4 WHERE "users"."id" = $5 [["invitation_token", "c94d3b1e55b5cf39eba111085ec94eb1dcccca321308b87017a7f08b1634a3df"], ["invitation_created_at", "2019-03-21 16:10:51.702522"], ["invitation_sent_at", "2019-03-21 16:10:51.702522"], ["updated_at", "2019-03-21 16:10:51.704500"], ["id", 1]]
↳ app/admin/organization_invitations.rb:18
(4.7ms) COMMIT
↳ app/admin/organization_invitations.rb:18
Rendering /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/devise_invitable-2.0.0/app/views/devise/mailer/invitation_instructions.html.erb
Rendered /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/devise_invitable-2.0.0/app/views/devise/mailer/invitation_instructions.html.erb (2.4ms)
Rendering /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/devise_invitable-2.0.0/app/views/devise/mailer/invitation_instructions.text.erb
Rendered /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/devise_invitable-2.0.0/app/views/devise/mailer/invitation_instructions.text.erb (5.2ms)
Devise::Mailer#invitation_instructions: processed outbound mail in 291.5ms
Sent mail to antoinecquellier#gmail.com (10.4ms)
Date: Thu, 21 Mar 2019 16:10:52 +0000
From: please-change-me-at-config-initializers-devise#example.com
Reply-To: please-change-me-at-config-initializers-devise#example.com
To: antoinecquellier#gmail.com
Message-ID: <5c93b78c2ad5_9da3fc49695b254502d0#MBP-de-Antoine.mail>
Subject: Invitation instructions
Mime-Version: 1.0
Content-Type: multipart/alternative;
boundary="--==_mimepart_5c93b78c751_9da3fc49695b25450147";
charset=UTF-8
Content-Transfer-Encoding: 7bit
----==_mimepart_5c93b78c751_9da3fc49695b25450147
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
Hello antoinecquellier#gmail.com
Someone has invited you to http://localhost:3000/, you can accept it through the link below.
http://localhost:3000/users/invitation/accept?invitation_token=LtbgEbDe4ac63rs7oeC3
If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password.
----==_mimepart_5c93b78c751_9da3fc49695b25450147
Content-Type: text/html;
charset=UTF-8
Content-Transfer-Encoding: 7bit
<p>Hello antoinecquellier#gmail.com</p>
<p>Someone has invited you to http://localhost:3000/, you can accept it through the link below.</p>
<p>Accept invitation</p>
<p>If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password.</p>
----==_mimepart_5c93b78c751_9da3fc49695b25450147--
(0.2ms) BEGIN
↳ app/admin/organization_invitations.rb:20
Organization Load (0.4ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/admin/organization_invitations.rb:20
OrganizationInvitation Create (1.5ms) INSERT INTO "organization_invitations" ("organization_id", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["organization_id", 1], ["email", "antoinecquellier#gmail.com"], ["created_at", "2019-03-21 16:10:52.023105"], ["updated_at", "2019-03-21 16:10:52.023105"]]
↳ app/admin/organization_invitations.rb:20
(1.6ms) COMMIT
↳ app/admin/organization_invitations.rb:20
Redirected to http://localhost:3000/admin/organization_invitations
Completed 302 Found in 345ms (ActiveRecord: 15.0ms)
Started GET "/admin/organization_invitations" for 127.0.0.1 at 2019-03-21 16:10:52 +0000
Processing by Admin::OrganizationInvitationsController#index as HTML
AdminUser Load (0.5ms) SELECT "admin_users".* FROM "admin_users" WHERE "admin_users"."id" = $1 ORDER BY "admin_users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Rendering /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activeadmin-1.4.3/app/views/active_admin/resource/index.html.arb
(0.6ms) SELECT COUNT(*) FROM (SELECT 1 AS one FROM "organization_invitations" LIMIT $1 OFFSET $2) subquery_for_count [["LIMIT", 30], ["OFFSET", 0]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE (0.0ms) SELECT COUNT(*) FROM (SELECT 1 AS one FROM "organization_invitations" LIMIT $1 OFFSET $2) subquery_for_count [["LIMIT", 30], ["OFFSET", 0]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
(0.5ms) SELECT COUNT(*) FROM "organization_invitations"
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE (0.0ms) SELECT COUNT(*) FROM (SELECT 1 AS one FROM "organization_invitations" LIMIT $1 OFFSET $2) subquery_for_count [["LIMIT", 30], ["OFFSET", 0]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
OrganizationInvitation Load (0.5ms) SELECT "organization_invitations".* FROM "organization_invitations" ORDER BY "organization_invitations"."id" desc LIMIT $1 OFFSET $2 [["LIMIT", 30], ["OFFSET", 0]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Organization Load (0.5ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.1ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.1ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
CACHE Organization Load (0.0ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Organization Load (0.6ms) SELECT "organizations".* FROM "organizations"
↳ /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.2.1/lib/active_record/log_subscriber.rb:98
Rendered /Users/antoinequellier/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activeadmin-1.4.3/app/views/active_admin/resource/index.html.arb (204.8ms)
Completed 200 OK in 240ms (Views: 233.9ms | ActiveRecord: 3.6ms)
Regarding the mailer config, I'm not sure what you're refering to. Should not this be handled in the DeviseInvitable config?
Config in development.rb
config.action_mailer.default_url_options = { host: 'http://localhost:3000' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtp.gmail.com",
port: "587",
domain: "gmail.com",
user_name: "myaddress#gmail.com",
password: "password",
authentication: "plain",
enable_starttls_auto: true
}
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_deliveries = true
config.action_mailer.perform_caching = false

Remove N+1 queries for impressionist gem

In my Rails app I have the N+1 problem where I'm making extra call(s) to the database to get associated data over and over again, specially when logging impressions per model.
For example:
Started GET "/" for 127.0.0.1 at 2018-12-02 16:21:05 -0500
Processing by JobsController#index as HTML
Job Load (4.4ms) SELECT "jobs".* FROM "jobs" WHERE "jobs"."published_at" IS NOT NULL ORDER BY "jobs"."published_at" DESC LIMIT $1 OFFSET $2 [["LIMIT", 30], ["OFFSET", 0]]
↳ app/controllers/jobs_controller.rb:22
User Load (1.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 13], ["LIMIT", 1]]
↳ app/controllers/jobs_controller.rb:24
Impression Exists (1.3ms) SELECT 1 AS one FROM "impressions" WHERE "impressions"."impressionable_id" = $1 AND "impressions"."impressionable_type" = $2 AND "impressions"."session_hash" = $3 LIMIT $4 [["impressionable_id", 705], ["impressionable_type", "Job"], ["session_hash", "d80d52dd401011a626d600167140e49f"], ["LIMIT", 1]]
↳ app/controllers/jobs_controller.rb:24
Impression Exists (0.6ms) SELECT 1 AS one FROM "impressions" WHERE "impressions"."impressionable_id" = $1 AND "impressions"."impressionable_type" = $2 AND "impressions"."session_hash" = $3 LIMIT $4 [["impressionable_id", 704], ["impressionable_type", "Job"], ["session_hash", "d80d52dd401011a626d600167140e49f"], ["LIMIT", 1]]
↳ app/controllers/jobs_controller.rb:24
Impression Exists (0.4ms) SELECT 1 AS one FROM "impressions" WHERE "impressions"."impressionable_id" = $1 AND "impressions"."impressionable_type" = $2 AND "impressions"."session_hash" = $3 LIMIT $4 [["impressionable_id", 703], ["impressionable_type", "Job"], ["session_hash", "d80d52dd401011a626d600167140e49f"], ["LIMIT", 1]]
↳ app/controllers/jobs_controller.rb:24
I'm using the impressionist gem and using the impressionist method directly, taking all of the Jobs on the page and logging an impression. The problem is because I'm only recording unique impressions, for records that already have an impression, these additional calls are redundant. I tried to use #jobs.includes(:impressions).each to preload the associated data hoping Rails was smart enough to figure out which records existed, but Rails still outputs numerous Impression Exists queries.
#jobs.each{|job| impressionist(job,'', :unique => [:session_hash])}

Does Rails Really Cache Database Query?

Does Rails actually cache the query result? The documentation says that same query will be never executed twice on the same request:
1.7 SQL Caching
The second time the same query is run against the database, it's not actually going to hit the database. The first time the result is returned from the query it is stored in the query cache (in memory) and the second time it's pulled from memory.
I did an experiment to proof that Rails actually cache the query:
def test
data = ""
User.find(1).update(first_name: 'Suwir Suwirr')
data << User.find(1).first_name
data << "\n"
User.find(1).update(first_name: 'Pengguna')
data << User.find(1).first_name
data << "\n"
render plain: data
end
If the result is cached, i would get the same result for each User.find(1). However, the result was Rails does not actually cache the query; i was expecting the update does not reflected on the result since it was "cached":
Suwir Suwirr
Pengguna
But the console says that it was cached: (Please highlight the CACHE word)
Started GET "/diag/test" for 10.0.2.2 at 2017-02-21 10:30:16 +0700
Processing by DiagController#test as HTML
User Load (0.7ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 4], ["LIMIT", 1]]
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.1ms) BEGIN
SQL (0.4ms) UPDATE "users" SET "first_name" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["first_name", "Suwir Suwirr"], ["updated_at", 2017-02-21 03:30:16 UTC], ["id", 1]]
(16.5ms) COMMIT
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.1ms) BEGIN
SQL (0.3ms) UPDATE "users" SET "first_name" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["first_name", "Pengguna"], ["updated_at", 2017-02-21 03:30:16 UTC], ["id", 1]]
(0.9ms) COMMIT
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Rendering text template
Rendered text template (0.0ms)
Completed 200 OK in 380ms (Views: 3.5ms | ActiveRecord: 21.9ms)
So my question, does Rails actually cache the query result? Or, only several query result on some request?
Update: Using Batch #update_all
I made another experiment to "fool" the query logic. Now Rails does not "cache" the query. Why this behaviour can happen?
# Controller
def test
data = ""
User.where(id: 1).update_all(first_name: 'Suwir Suwirr')
data << User.find(1).first_name
data << "\n"
User.where(id: 1).update_all(first_name: 'Pengguna')
data << User.find(1).first_name
data << "\n"
logger.info 'hi'
render plain: data
end
# Console
Started GET "/diag/test" for 10.0.2.2 at 2017-02-21 10:45:43 +0700
Processing by DiagController#test as HTML
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 4], ["LIMIT", 1]]
SQL (13.8ms) UPDATE "users" SET "first_name" = 'Suwir Suwirr' WHERE "users"."id" = $1 [["id", 1]]
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
SQL (2.9ms) UPDATE "users" SET "first_name" = 'Pengguna' WHERE "users"."id" = $1 [["id", 1]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
hi
Rendering text template
Rendered text template (0.0ms)
Completed 200 OK in 28ms (Views: 0.8ms | ActiveRecord: 17.8ms)
# Browser result
Suwir Suwirr
Pengguna
I was stupid.
Yes, Rails does actually cache the query, but update and destroy will invalidate its query cache. update_all is basically iterating each record with update.
I tried the experiment by really "fooling" the ActiveRecord query mechanism. And yes, it works.
# Controller
def test
data = ""
ActiveRecord::Base.connection.execute('UPDATE "users" SET "first_name" = \'Suwir Suwirr\' WHERE "users"."id" = 1')
data << User.find(1).first_name
data << "\n"
ActiveRecord::Base.connection.execute('UPDATE "users" SET "first_name" = \'Pengguna\' WHERE "users"."id" = 1')
data << User.find(1).first_name
data << "\n"
render plain: data
end
# Browser
Suwir Suwirr
Suwir Suwirr

Resources