Rails eager loading detected when saving with has_many through association - ruby-on-rails

Bullet gem detected an eager loading on create. Consider the following models:
class User < ActiveRecord::Base
has_many :user_banks
has_many :banks, through: :user_banks
end
class Bank < ActiveRecord::Base
belongs_to :country
belongs_to :currency
has_many :user_posts
has_many :users, through: :user_banks
end
class UserBank < ActiveRecord::Base
belongs_to :user
belongs_to :bank
end
In my controller, when creating user:
# GET /user/new
def new
#user = User.new
end
# POST /users
def create
#user = User.new(user_params)
if #user.save
redirect_to user_path(#user), notice: I18n.t('views.action.created')
else
render :new
end
end
private
def user_params
params.fetch(:user, {}).permit(:name, :email, bank_ids: [])
end
I've got an error;
Bullet::Notification::UnoptimizedQueryError:
user: ruby
POST /admin/users
USE eager loading detected
Bank => [:country]
Add to your query: .includes([:country])
Call stack
/src/app/controllers/admin/users_controller.rb:36:in `create'
The Rails code that eager loads has_many through associations is unfortunately a bit buggy.
How do I solve this? to eager load the country and currency of the Bank.
If you will try in Rails console something like this;
[1] pry(main)> User.new(name: 'test', email: 'test#test.com', bank_ids: [1, 2]).save!
Country Load (1.3ms) SELECT `countries`.`id`, `countries`.`created_at`, `countries`.`updated_at`, `countries`.`code` FROM `countries` WHERE `countries`.`id` = 1 LIMIT 1
Currency Load (0.4ms) SELECT `currencies`.* FROM `currencies` WHERE `currencies`.`id` = 1 LIMIT 1
Country Load (1.3ms) SELECT `countries`.`id`, `countries`.`created_at`, `countries`.`updated_at`, `countries`.`code` FROM `countries` WHERE `countries`.`id` = 1 LIMIT 1
Currency Load (0.4ms) SELECT `currencies`.* FROM `currencies` WHERE `currencies`.`id` = 1 LIMIT 1
User Create (0.5ms) INSERT INTO `users` (`name`, `email`, `created_at`, `updated_at`) VALUES ('test', 'test#test.com', '2020-06-25 06:18:50', '2020-06-25 06:18:50')
UserBank Create (1.0ms) INSERT INTO `user_banks` (`user_id`, `bank_id`, `created_at`, `updated_at`) VALUES (1, 1, '2020-06-25 06:18:50', '2020-06-25 06:18:50')
UserBank Create (0.4ms) INSERT INTO `user_banks` (`user_id`, `bank_id`, `created_at`, `updated_at`) VALUES (1, 2, '2020-06-25 06:18:50', '2020-06-25 06:18:50')

Try:
User.new(name: 'test',
email: 'test#test.com',
banks: Bank.eager_load(:country, :currency).find([1, 2])
).save!

Related

Add Existing Tag to Article in Rails 6

I'm trying to add existing Tags to Articles.
My models:
# app/models/user.rb
class User < ApplicationRecord
has_many :articles, dependent: :destroy
has_many :tags, dependent: :destroy
end
# app/models/article.rb
class Article < ApplicationRecord
belongs_to :user
has_many :tags
end
# app/models/tag.rb
class Tag < ApplicationRecord
belongs_to :user
belongs_to :article
has_many :articles
validates :name, presence: true, uniqueness: { scope: :user_id }
end
By using the following in the console, I'm able to add a tag to an article.
> a = Article.last
> a.tags.create(name: "unread", user_id: "1")
> a.tags
Tag Load (0.3ms) SELECT "tags".* FROM "tags" WHERE "tags"."article_id" = ? [["article_id", 29]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 6, name: "unread", user_id: 1, article_id: 29, created_at: "2020-12-28 16:05:36", updated_at: "2020-12-28 16:05:36", permalink: "unread">]>
If I try to add this same tag to different article using the same .create, I get a rollback error.
> a = Article.first
> a.tags.create(name: "unread", user_id: "1")
(0.0ms) begin transaction
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Tag Exists? (0.1ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = ? AND "tags"."user_id" = ? LIMIT ? [["name", "unread"], ["user_id", 1], ["LIMIT", 1]]
(0.0ms) rollback transaction
Thinking the problem is the .create since that tag already exists, I tried this first_or_create, but that also errored.
> a.tags.first_or_create(name: "unread", user_id: "1")
Tag Load (0.1ms) SELECT "tags".* FROM "tags" WHERE "tags"."article_id" = ? ORDER BY "tags"."id" ASC LIMIT ? [["article_id", 52], ["LIMIT", 1]]
(0.0ms) begin transaction
Tag Exists? (0.1ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = ? AND "tags"."user_id" = ? LIMIT ? [["name", "unread"], ["user_id", 1], ["LIMIT", 1]]
(0.3ms) rollback transaction
How do I add an existing tag to an article?
Edit:
I've added a join table as prescribed by Max. This has allowed me to save tags to articles via the console.
> t = Tag.where(name: "rails", user_id: "1").first_or_create
Tag Load (0.6ms) SELECT "tags".* FROM "tags" WHERE "tags"."name" = ? AND "tags"."user_id" = ? ORDER BY "tags"."id" ASC LIMIT ? [["name", "rails"], ["user_id", 1], ["LIMIT", 1]]
=> #<Tag id: 8, name: "rails", user_id: 1, created_at: "2020-12-28 21:21:25", updated_at: "2020-12-28 21:21:25", permalink: "rails">
> a.tags << t
(0.0ms) begin transaction
Article Tag Exists? (0.1ms) SELECT 1 AS one FROM "article_tags" WHERE "article_tags"."tag_id" = ? AND "article_tags"."article_id" = ? LIMIT ? [["tag_id", 8], ["article_id", 29], ["LIMIT", 1]]
Article Tag Create (0.2ms) INSERT INTO "article_tags" ("article_id", "tag_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["article_id", 29], ["tag_id", 8], ["created_at", "2020-12-28 21:22:13.567187"], ["updated_at", "2020-12-28 21:22:13.567187"]]
(17.9ms) commit transaction
Tag Load (0.1ms) SELECT "tags".* FROM "tags" INNER JOIN "article_tags" ON "tags"."id" = "article_tags"."tag_id" WHERE "article_tags"."article_id" = ? LIMIT ? [["article_id", 29], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Tag id: 6, name: "unread", user_id: 1, created_at: "2020-12-28 16:05:36", updated_at: "2020-12-28 16:05:36", permalink: "unread">, #<Tag id: 8, name: "rails", user_id: 1, created_at: "2020-12-28 21:21:25", updated_at: "2020-12-28 21:21:25", permalink: "rails">]>
I'm still unclear how to save this from the view.
I have a form in app/views/articles/edit.html.erb:
<%= form_with(model: #article, local: true) do |form| %>
<%= form.label :tag_list, "Tags, separated by comma" %>
<%= form.text_field :tag_list %>
<%= form.submit "Update Tags" %>
<% end %>
In the Articles Controller I assume I'd want the following.
def edit
#article.tags << Tag.where(name: "unread", user_id: "1").first_or_create
#article.save!
end
...or perhaps I need to loop those tags in the model? Something like this:
# app/models/article.rb
def tag_list
tags.map(&:name).join(", ")
end
def tag_list=(names)
self.tags = names.split(",").map do |name|
Tag.where(name: name.strip, user_id: current_user.id).first_or_create!
end
end
# app/controllers/articles_controller.rb
def edit
#article.tags << tag_list
#article.save!
end
But this isn't working. How do I update the tags from a view?
Edit:
I've decided not to update tags by form, so I've marked Max's answer as the solution since he pointed me to the Join Tables which got me on the right track.
If you wish to update this for posterity, I'm sure they will appreciate it.
If you want a tag to ever belong to more then a single article you need a join table:
class Article < ApplicationRecord
has_many :article_tags
has_many :tags, through: :article_tags
end
# rails g model article_tag article:references tag:references
# make sure to add a unique index on article_id and tag_id
class ArticleTag < ApplicationRecord
belongs_to :article
belongs_to :tag
validates_uniqueness_of :tag_id, scope: :article_id
end
class Tag < ApplicationRecord
has_many :article_tags
has_many :articles, through: :article_tags
end
This creates a many to many association between the two tables (an article can have multiple tags, and a tag can belong to multiple articles). This can also be done through has_and_belongs_to_many which does not have a join model but has limited flexibility.
If you want to add an existing tag(s) to an article you can use the shovel operator:
a = Article.last
a.tags << Tag.first
a.tags << Tag.second
But usually you use the tag_ids= setter to set the association from an array of ids:
class ArticlesController
def create
#article = Article.new(article_params)
# ...
end
private
def article_params
require(:article)
.permit(:title, :body, tag_ids: [])
end
end
<%= form_with(model: #article) do |f| %>
# ...
<%= f.collection_checkboxes :tag_ids, Tag.all, :id, :name %>
<% end %>
tag_ids: [] permits an array of ids.
This will automatically add/remove rows in the join table depending on what the user checks.

How do you return a single value from a scope on a has_many relation?

How do I return a single record from this scope? I tried both ways.
class Subscription < ApplicationRecord
has_many :invoices, dependent: :destroy
class Invoice < ApplicationRecord
belongs_to :subscription
scope :current, -> do
# where(paid: nil).order(:created_at).last
where(paid: nil).order(created_at: :desc).limit(1).first
end
The first way correctly adds order by ... desc limit 1, but then it executes another query without the where condition!
irb(main):004:0> s.invoices.current
Invoice Load (22.0ms) SELECT "invoices".* FROM "invoices" WHERE "invoices"."subscription_id" = $1 AND "invoices"."paid" IS NULL ORDER BY "invoices"."created_at" DESC LIMIT $2 [["subscription_id", 16], ["LIMIT", 1]]
Invoice Load (2.0ms) SELECT "invoices".* FROM "invoices" WHERE "invoices"."subscription_id" = $1 [["subscription_id", 16]]
=> #<ActiveRecord::AssociationRelation [#<Invoice id: 8, subscription_id: 16, user_id: 21, paid: "2018-03-15", created_at: "2018-03-14 22:42:48">]>
The second way also does another query, obliterating the correct results.
irb(main):007:0> s.invoices.current
Invoice Load (2.0ms) SELECT "invoices".* FROM "invoices" WHERE "invoices"."subscription_id" = $1 AND "invoices"."paid" IS NULL
ORDER BY "invoices"."created_at" DESC LIMIT $2 [["subscription_id", 16], ["LIMIT", 1]]
Invoice Load (2.0ms) SELECT "invoices".* FROM "invoices" WHERE "invoices"."subscription_id" = $1 [["subscription_id", 16]]
=> #<ActiveRecord::AssociationRelation [#<Invoice id: 8, subscription_id: 16, user_id: 21, paid: "2018-03-15", created_at: "2018-03-14 22:42:48">]>
Also, how do I get just the record, not an ActiveRecord::AssociationRelation?
Ruby 5.0.6
You might try something like:
class Invoice < ApplicationRecord
belongs_to :subscription
class << self
def for_subscription(subscription)
where(subscription: subscription)
end
def unpaid
where(paid: nil)
end
def newest
order(created_at: :desc).first
end
end
end
Which, if you have an instance of Subscription called #subscription you could use like:
Invoice.unpaid.for_subscription(#subscription).newest
I believe that should fire only one query and should return an invoice instance.
I replaced the scope with
def self.current
where(paid: nil).order(:created_at).last
end
And it worked. However I don't know why, as this method is in Invoice class, while the relation is ActiveRecord::Associations::CollectionProxy class. I wish I knew why it works. And I wish I knew why scope didn't work and performed two queries.

How to find a object and insert it in a database

I’m trying to pick up the shop_id and subscription_id from a cart and insert/save them into the purchase database.
I have a cart, line_items and subscriptions.
And this is how I’m trying to do this:
#purchase = #cart.line_items.build
#purchase = current_shop.purchases.build(purchase_params)
#purchase.save!
I got the shop_id, but the subscription_id for some reason is nil.
Any ideas what maybe wrong?
Update 1
def purchase_params
params.permit(:subscription_id, :shop_id, :created_at, :updated_at, :id, :cart_id)
end
Started POST "/line_items?subscription_id=1" for ::1 at 2017-06-18 16:45:12 +0300
Processing by LineItemsController#create as HTML
Parameters: {"authenticity_token"=>"dUonc4AnCvFTuK1b+TAKho/kmpvl7XaOM7SGcNalzdQV1+CqhY4 p7znDiL/TV12pVKeDTqlR7j5NL65X1S/75A==", "subscription_id"=>"1"}
Cart Load (0.1ms) SELECT "carts".* FROM "carts" WHERE "carts"."id" = ? LIMIT ? [["id", 14], ["LIMIT", 1]]
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "carts" ("created_at", "updated_at") VALUES (?, ?) [["created_at", 2017-06-18 13:45:12 UTC], ["updated_at", 2017-06-18 13:45:12 UTC]]
(0.7ms) commit transaction
Subscription Load (0.2ms) SELECT "subscriptions".* FROM "subscriptions" WHERE "subscriptions"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "line_items" ("subscription_id", "cart_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["subscription_id", 1], ["cart_id", 18], ["created_at", 2017-06-18 13:45:12 UTC], ["updated_at", 2017-06-18 13:45:12 UTC]]
(0.9ms) commit transaction
Redirected to http://localhost:3000/carts/18
Completed 302 Found in 21ms (ActiveRecord: 3.2ms)
I'm implementing braintree payments via the following create method and this is where I have the small peace of code:
def create
current_shop.has_payment_info?
#result = Braintree::Transaction.sale(
amount: #cart.total_price,
payment_method_nonce: params[:payment_method_nonce],
customer: {
first_name: current_shop.first_name,
last_name: current_shop.last_name,
company: current_shop.shop_name,
email: current_shop.email,
phone: current_shop.phone_number,
website: current_shop.web_page
},
options: { store_in_vault: true })
if #result.success?
current_shop.update(braintree_customer_id: #result.transaction.customer_details.id) unless current_shop.has_payment_info?
#purchase = #cart.line_items.build
#purchase.save!
#purchase = current_shop.purchases.build(purchase_params)
#purchase.save!
#cart.destroy
redirect_to front_index_path, notice: 'Your transaction was succesfully processed'
else
gon.client_token = generate_client_token
redirect_to :back, :notice => 'Something went wrong while processing your transaction. Please try again!'
end
end
Update 2
class Purchase < ApplicationRecord
belongs_to :shop
belongs_to :cart
end
class Shop < ApplicationRecord
has_many :items
has_many :purchases
has_many :subscriptions
end
class Subscription < ApplicationRecord
has_many :line_items
belongs_to :shop
end
class Cart < ApplicationRecord
has_many :line_items, dependent: :destroy
has_many :purchases
end
class LineItem < ApplicationRecord
belongs_to :subscription
belongs_to :cart
end
Build here only links up a new line_item with #cart, like this, LineItem.new(cart_id: #cart.id). subscription_id is not linked here as #cart doesn't have a relationship with subscriptions.
#cart.line_items.build
line_item = #cart.line_items.build
fetch the desired subscription_id and assign it to 'line_item.subscription_id '
line_item.subscription_id = fetch_the_subscription_id
line_item.save!

Rails: How to call one-to-one relationship in rails

I'm new to rails and I want to know how to fetch a one-to-one relationship. I want to fetch users city. In my postgresql database I have:
cities Table:
city:varchar
zipcode: integer
users Table
name:varchar
city_id:int
and in city and user model I have:
class City < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :city
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
I tried the following in my search controller but didnt work, when logged in:
current_user.city
I get the following error
Processing by SearchController#index as HTML
Parameters: {"utf8"=>"✓", "q"=>"", "criteria"=>"1", "commit"=>"Search"}
User Load (1.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
PG::UndefinedColumn: ERROR: column cities.user_id does not exist
LINE 1: SELECT "cities".* FROM "cities" WHERE "cities"."user_id" =...
^
: SELECT "cities".* FROM "cities" WHERE "cities"."user_id" = $1 LIMIT 1
Completed 500 Internal Server Error in 11ms
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column cities.user_id does not exist
LINE 1: SELECT "cities".* FROM "cities" WHERE "cities"."user_id" =...
^
: SELECT "cities".* FROM "cities" WHERE "cities"."user_id" = $1 LIMIT 1):
why am I suppose to add a user_id column to cities table, when I have cities foreign key in users table? I dont want to add user_id into cities table.
You can use has_one :through association with join table. Some example for you below.
user model:
class User < ActiveRecord::Base
has_one :city, through: :user_city
has_one :user_city
end
city model:
class City < ActiveRecord::Base
belongs_to :user
end
user city join model:
class UserCity < ActiveRecord::Base
belongs_to :city
belongs_to :user
end
migration for join tables:
class JoinUserCity < ActiveRecord::Migration
def change
create_table :user_cities do |t|
t.integer :user_id
t.integer :city_id
end
end
end
Test in rails console:
=> u = User.create
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "users" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2014-12-07 15:47:14.595728"], ["updated_at", "2014-12-07 15:47:14.595728"]]
(3.3ms) commit transaction
=> #<User id: 4, created_at: "2014-12-07 15:47:14", updated_at: "2014-12-07 15:47:14">
=> u.city
City Load (0.2ms) SELECT "cities".* FROM "cities" INNER JOIN "user_cities" ON "cities"."id" = "user_cities"."city_id" WHERE "user_cities"."user_id" = ? LIMIT 1 [["user_id", 4]]
=> nil
=> c = City.create
(0.1ms) begin transaction
SQL (0.5ms) INSERT INTO "cities" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2014-12-07 15:47:24.535039"], ["updated_at", "2014-12-07 15:47:24.535039"]]
(3.3ms) commit transaction
=> #<City id: 1, created_at: "2014-12-07 15:47:24", updated_at: "2014-12-07 15:47:24">
irb(main):004:0> u.city = c
UserCity Load (0.3ms) SELECT "user_cities".* FROM "user_cities" WHERE "user_cities"."user_id" = ? LIMIT 1 [["user_id", 4]]
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "user_cities" ("city_id", "user_id") VALUES (?, ?) [["city_id", 1], ["user_id", 4]]
(1.0ms) commit transaction
=> #<City id: 1, created_at: "2014-12-07 15:47:24", updated_at: "2014-12-07 15:47:24">
irb(main):005:0> u.save
(0.1ms) begin transaction
(0.1ms) commit transaction
=> true
=> u = User.last
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
=> #<User id: 4, created_at: "2014-12-07 15:47:14", updated_at: "2014-12-07 15:47:14">
=> u.city
City Load (0.2ms) SELECT "cities".* FROM "cities" INNER JOIN "user_cities" ON "cities"."id" = "user_cities"."city_id" WHERE "user_cities"."user_id" = ? LIMIT 1 [["user_id", 4]]
=> #<City id: 1, created_at: "2014-12-07 15:47:24", updated_at: "2014-12-07 15:47:24">
take a look at the document of has_one and belogns_to,
belongs_to(name, options = {})
Specifies a one-to-one association with another class. This method should only be used if this class
contains the foreign key. If the other class contains the foreign key, then you should use has_one
instead.
as the user table has the foreign key, you should change your model definition like this
class City < ActiveRecord::Base
has_one :user
end
class User < ActiveRecord::Base
belongs_to :city
end

Records Won't Insert into Join Table for has_many :through Association

I have 2 models: User and Collective, along with their join table, Membership. When a user creates a new collective, I want to simultaneously create a new membership between the two. Right now I have my create function partially working, in that the collective 'new' form adds a collective record to my db but no membership record is added.
User Model
class User < ActiveRecord::Base
has_many :collectives, :through => :memberships
has_many :memberships
end
Collective Model
class Collective < ActiveRecord::Base
has_many :users, :through => :memberships
has_many :memberships
end
Membership Model
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :collective
validates :user_id, presence: true
validates :collective_id, presence: true
end
Collective Controller
def new
#collective = Collective.new if user_signed_in?
end
def create
#collective = Collective.new(collective_params)
if #collective.save
current_user.memberships.create(collective_id: #collective)
flash[:success] = "Collective created!"
redirect_to collective_url(#collective)
else
render 'new'
end
end
private
def collective_params
params.require(:collective).permit(:name, :description, :location, :num_of_users, :num_of_projects)
end
end
Log
Started POST "/collectives" for 127.0.0.1 at 2014-11-06 10:49:39 -0500
Processing by CollectivesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"Dh/rG1N6ulrJSIiEaAgudnaltjxnKwCw5sdUQxG9qnE=", "collective"=>{"name"=>"Honda Civic", "location"=>"Cars", "description"=>"Blaahhhhh"}, "commit"=>"Create Collective"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "collectives" ("created_at", "description", "location", "name", "num_of_projects", "num_of_users", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?) [["created_at", "2014-11-06 15:49:40.003338"], ["description", "Blaahhhhh"], ["location", "Cars"], ["name", "Honda Civic"], ["num_of_projects", 0], ["num_of_users", 1], ["updated_at", "2014-11-06 15:49:40.003338"]]
(145.3ms) commit transaction
(0.0ms) begin transaction
(0.1ms) commit transaction
Redirected to http://localhost:3000/collectives/7
Completed 302 Found in 208ms (ActiveRecord: 146.7ms)
It seems like the line current_user.memberships.create(collective_id: #collective) is just being ignored for some reason given that the redirect happens fine. Help would be much appreciated. Thanks in advance!
I would say the problem is with passing instance object instead of id. It should be:
if #collective.save
current_user.memberships.create(collective_id: #collective.id)
flash[:success] = "Collective created!"
redirect_to collective_url(#collective)
else
render 'new'
end
Since create method only returns true/false (false in this case because of validation) and does not raise any exception, it gets redirected into show action.

Resources