How to add an entry to the model has_many: through - ruby-on-rails

class Project < ActiveRecord::Base
has_many :accounts
has_many :sites, through: :accounts
end
class Site < ActiveRecord::Base
has_many :accounts
has_many :projects, through: :accounts
accepts_nested_attributes_for :accounts
end
class Account < ActiveRecord::Base
belongs_to :site
belongs_to :project
end
p = Project.find(1)
2.1.4 :011 > p.sites.create({"url"=>"site.ru", "accounts_attributes"=>{"0"=>{"email"=>"mail#site.ru"}}})
(0.3ms) BEGIN
SQL (1.8ms) INSERT INTO `sites` (`created_at`, `updated_at`, `url`) VALUES ('2015-09-04 07:09:53', '2015-09-04 07:09:53', 'site.ru')
SQL (0.3ms) INSERT INTO `accounts` (`created_at`, `email`, `site_id`, `updated_at`) VALUES ('2015-09-04 07:09:53', 'mail#site.ru', 3, '2015-09-04 07:09:53')
SQL (0.3ms) INSERT INTO `accounts` (`created_at`, `project_id`, `site_id`, `updated_at`) VALUES ('2015-09-04 07:09:53', 1, 3, '2015-09-04 07:09:53')
(1.2ms) COMMIT
=> #<Site id: 3, url: "site.ru", created_at: "2015-09-04 07:09:53", updated_at: "2015-09-04 07:09:53">
Question:
Why are added 2 record?
To add a single entry in the Account model with fields site_id, project_id, email?

The first account record is created automatically, because the Site is related to the Project through the Account.
The second record is created because you have accepts_nested_attributes_for :accounts in your Site model, and you pass the nested attributes while creating the Site record.
Could you clarify, what you want to archieve?

Related

Rails: Querying has_one through belongs_to adds PK null in query

I'm getting an error when trying to query a relation. These are my models:
class Course < ApplicationRecord
belongs_to :subject, inverse_of: :courses
# This is the important relation
has_one :department, through: :subject
end
class Subject < ApplicationRecord
belongs_to :department, inverse_of: :subjects
has_many :courses, dependent: :destroy, inverse_of: :subject
end
class Department < ApplicationRecord
has_many :subjects, dependent: :destroy
end
The thing is that if I do:
> Course.where(department: Department.first)
Department Load (0.3ms) SELECT `departments`.* FROM `departments` ORDER BY `departments`.`id` ASC LIMIT 1
Course Load (0.7ms) SELECT `courses`.* FROM `courses` WHERE `courses`.`id` IS NULL
=> []
As you can see, it includes a WHERE courses.id IS NULL. I'm able anyway to access this relation by doing:
> Course.joins(:subject).where(subjects: { department: Department.first })
Department Load (0.4ms) SELECT `departments`.* FROM `departments` ORDER BY `departments`.`id` ASC LIMIT 1
Course Load (0.5ms) SELECT `courses`.* FROM `courses` INNER JOIN `subjects` ON `subjects`.`id` = `courses`.`subject_id` WHERE `subjects`.`department_id` = 1
And I get many objects as result, but I'd like to know if I'm missing something to get this to work.
BTW, if I try to access the method directly, it works correctly:
> Course.first.department
Course Load (0.9ms) SELECT `courses`.* FROM `courses` ORDER BY `courses`.`id` ASC LIMIT 1
Department Load (0.6ms) SELECT `departments`.* FROM `departments` INNER JOIN `subjects` ON `departments`.`id` = `subjects`.`department_id` WHERE `subjects`.`id` = 1 LIMIT 1
=> #<Department:0x00007fc40dfab5f8
id: 1,
name: "Matemática",
slug: "matematica",
code: 1,
created_at: Sat, 15 Jun 2019 20:49:33 UTC +00:00,
updated_at: Sat, 15 Jun 2019 20:49:33 UTC +00:00>
Thank you!

Two belong_to referring the same table + eager loading

First of all, based on this (Rails association with multiple foreign keys) I figured out how to make two belong_to pointing to the same table.
I have something like that
class Book < ApplicationRecord
belongs_to :author, inverse_of: :books
belongs_to :co_author, inverse_of: :books, class_name: "Author"
end
class Author < ApplicationRecord
has_many :books, ->(author) {
unscope(:where).
where("books.author_id = :author_id OR books.co_author_id = :author_id", author_id: author.id)
}
end
It's all good. I can do either
book.author
book.co_author
author.books
However, sometimes I need to eager load books for multiple authors (to avoid N queries).
I am trying to do something like:
Author.includes(books: :title).where(name: ["Lewis Carroll", "George Orwell"])
Rails 5 throws at me: "ArgumentError: The association scope 'books' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported."
I am trying to figure out what I should do?
Should I go with many-to-many association? It sounds like a solution. However, it looks like it will introduce it's own problems (I need "ordering", meaning that I need explicitly differentiate between main author and co-author).
Just trying to figure out whether I am missing some simpler solution...
Why do you not use HABTM relation? For example:
# Author model
class Author < ApplicationRecord
has_and_belongs_to_many :books, join_table: :books_authors
end
# Book model
class Book < ApplicationRecord
has_and_belongs_to_many :authors, join_table: :books_authors
end
# Create books_authors table
class CreateBooksAuthorsTable < ActiveRecord::Migration
def change
create_table :books_authors do |t|
t.references :book, index: true, foreign_key: true
t.references :author, index: true, foreign_key: true
end
end
end
You can use eagerload like as following:
irb(main):007:0> Author.includes(:books).where(name: ["Lewis Carroll", "George Orwell"])
Author Load (0.1ms) SELECT "authors".* FROM "authors" WHERE "authors"."name" IN (?, ?) LIMIT ? [["name", "Lewis Correll"], ["name", "George Orwell"], ["LIMIT", 11]]
HABTM_Books Load (0.1ms) SELECT "books_authors".* FROM "books_authors" WHERE "books_authors"."author_id" IN (?, ?) [["author_id", 1], ["author_id", 2]]
Book Load (0.1ms) SELECT "books".* FROM "books" WHERE "books"."id" IN (?, ?) [["id", 1], ["id", 2]]
Try this:
Author.where(name: ["Lewis Carroll", "George Orwell"]).include(:books).select(:title)

Duplicate insertion when saving model with has_many :through association

In the example below saving a model results in duplicate insertions.
shipment_params = {name: "Guy", from_country: "US", from_city: "a Ferry", from_state: "NY", from_postal_code: "11232-1716", from_street_address: "Broadway, 1", to_country: "US", to_city: "idge", to_state: "MA", to_postal_code: "01234-5678", to_street_address: "Massachusetts Avenue, 1234", service_code: "GROUND"}
shipment = Shipment.new(shipment_params)
shipment.save!
class Shipment < ActiveRecord::Base
has_many :shippings
has_many :orders, :through => :shippings
end
class Shipping < ActiveRecord::Base
belongs_to :order
belongs_to :shipment
end
class Order < ActiveRecord::Base
has_many :shippings
has_many :transactions #, :class_name => "OrderTransaction"
has_many :shipments, :through => :shippings
end
The following SQL is shown:
irb(main):014:0> #shipment.save
(0.3ms) BEGIN
SQL (0.4ms) INSERT INTO `shipments` (`name`, `from_country`, `from_city`, `from_state`, `from_postal_code`, `from_street_address`, `to_country`, `to_city`, `to_state`, `to_postal_code`, `to_street_address`, `service_code`, `created_at`, `updated_at`) VALUES ('121', 'US', 'ds Ferry', 'NY', '33-1716', 'd, 329', 'US', 'ss', 'ss', '1234-1228', 'r Avenue, 343', 'GROUND', '2016-03-14 15:04:15', '2016-03-14 15:04:15')
SQL (5.0ms) INSERT INTO `orders` (`created_at`, `updated_at`) VALUES ('2016-03-14 15:04:15', '2016-03-14 15:04:15')
SQL (0.3ms) INSERT INTO `shippings` (`shipment_id`, `order_id`, `created_at`, `updated_at`) VALUES (3, 6, '2016-03-14 15:04:15', '2016-03-14 15:04:15')
SQL (0.3ms) INSERT INTO `shippings` (`shipment_id`, `order_id`, `created_at`, `updated_at`) VALUES (3, 6, '2016-03-14 15:04:15', '2016-03-14 15:04:15')
(4.1ms) COMMIT
=> true
What do you think?

Rails string as a foreign key

I have a relation between User and Course (typical enrollment data). A User has_many Course and vice-versa (typical JOIN table scenario).
I am attempting to migrate my previous has_and_belongs_to_many relationship between these two models to a has_many :through relationship. My files currently look like:
class User < ActiveRecord::Base
has_and_belongs_to_many :courses
end
and
class Course < ActiveRecord::Base
has_and_belongs_to_many :users
end
and the table name that joins the two models is courses_users.
I now need to migrate this relationship to the has_many :through association, and also make the column type of user_id a string, as I want to use the g_number (string) attribute of User as the foreign key. Note: I don't care about the performance difference between int and varchar/string.
The short and simple problem is that I need users.g_number to reference enrollments.user_id as a foreign key, and both are strings.
My attempt at a migration and model rework is this:
class User < ActiveRecord::Base
has_many :enrollment
has_many :courses, :through => :enrollment
end
and
class Course < ActiveRecord::Base
has_many :enrollment
has_many :users, :through => :enrollment
end
lastly
class Enrollment < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
then the migration
class ChangeUserIdJoin < ActiveRecord::Migration
def self.up
rename_table :courses_users, :enrollments
end
def self.down
rename_table :enrollments, :courses_users
end
end
Everything works fine here. I can do queries like User.courses and Course.users. But now I want to change the type of the user_id column in the join table to a string so that I can store the g_number (string attribute on User) and join on that instead of the serial id column of User.
When I attempt to change the user_id column type to string in the migration:
class ChangeUserIdJoin < ActiveRecord::Migration
def self.up
change_column :courses_users, :user_id, :string
rename_table :courses_users, :enrollments
end
def self.down
rename_table :enrollments, :courses_users
change_column :courses_users, :user_id, :integer
end
end
the queries Course.users and User.courses start failing (below from Rails console). User.courses returns an empty array (whereas before there are multiple Course objects), and Course.users throws an exception because of mismatched column types (which obviously makes sense):
u = User.take
User Load (0.9ms) SELECT "users".* FROM "users" LIMIT 1
=> #<User id: 1, username: "director", g_number: "g00000000", password_digest: "$2a$10$dvcOd3rHfbcR1Rn/D6VhsOokj4XiIkQbHxXLYjy5s4f...", created_at: "2016-01-06 01:36:00", updated_at: "2016-01-06 01:36:00", first_name: "Director", last_name: "", role: 0, registered: true>
2.1.5 :002 > u.courses
Course Load (0.9ms) SELECT "courses".* FROM "courses" INNER JOIN "enrollments ON "courses"."id" = "enrollments"."course_id" WHERE "enrollments"."user_id" = $1 [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy []>
2.1.5 :003 > c = Course.take
Course Load (0.7ms) SELECT "courses".* FROM "courses" LIMIT 1
=> #<Course id: 12754, year: 2015, semester: 0, department: 7, course: 101, section: 1, name: "SPA 101 01 - Elementary Spanish I">
2.1.5 :004 > c.users
PG::UndefinedFunction: ERROR: operator does not exist: integer = character varying
LINE 1: ... "users" INNER JOIN "enrollments" ON "users"."id" = "enrollm...
I need to be able to join on enrollments.user_id = users.g_number. What do I need to do in order to change the user_id column to a string type in the Enrollment model/table, and still be able to do Active Record queries like User.courses and Course.users?
Try by specifying the foreign and primary keys in enrollments model, like this
belongs_to :user foreign_key: :user_id, primary_key: :g_number

Rake task foreign key assignment in a many to many relationship

I have two tables: Group and Keyword. They have a many-to-many relationship via another table called Assignments. I have a rake task where I generate random keywords, save the keyword to the Keyword's table and assign it to the current group's keywords. The code looks like this (I used a similar syntax to what I use in the console; however, I am not sure about it's correctness):
Group.populate 30 do |group|
Faker::Lorem.words(rand(3..7)).each do |key|
k = Keyword.create(name: key)
group.keywords << k
end
end
The << is returning this error: NoMethodError: undefined method<<'`.
If I ran the inner two lines in the console I get what I expect (note: I simplified the below models so you will see more keys in the log, but ignore that):
(0.1ms) begin transaction
SQL (2.0ms) INSERT INTO "keywords" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", "bla"], ["created_at", "2015-03-14 18:25:29.179793"], ["updated_at", "2015-03-14 18:25:29.179793"]]
(1.2ms) commit transaction
=> #<Keyword id: 1, name: "bla", created_at: "2015-03-14 18:25:29", updated_at: "2015-03-14 18:25:29">
(0.2ms) begin transaction
SQL (1.8ms) INSERT INTO "assignments" ("group_id", "keyword_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["group_id", 1], ["keyword_id", 1], ["created_at", "2015-03-14 18:26:53.650257"], ["updated_at", "2015-03-14 18:26:53.650257"]]
(0.8ms) commit transaction
=> #<ActiveRecord::Associations::CollectionProxy [#<Keyword id: 1, name: "bla", created_at: "2015-03-14 18:25:29", updated_at: "2015-03-14 18:25:29">]>
The Group model looks like this:
class Group < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :keywords, through: :assignments, dependent: :destroy
end
The Keyword model looks like that:
class Keyword < ActiveRecord::Base
has_many :assignments
has_many :groups, through: :assignments
end
And the Assignment model looks like this:
class Assignment < ActiveRecord::Base
belongs_to :group
belongs_to :keyword
end
How can I modify this code to work as expected?
TRy this
class Group < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :active_assignments, dependent: :destroy, class_name: "Assignment"
has_many :keywords, through: :active_assignments, source: :keyword
class Keyword < ActiveRecord::Base
has_many :assignments, dependent: :destroy
has_many :groups, through: :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :group
belongs_to :keyword
end
Group.populate 30 do |group|
Faker::Lorem.words(rand(3..7)).each do |key|
group.keywords << Keyword.create(name: key)
end
end
I found the problem in my program. Even though it was a stupid mistake from me, I will leave the question and the error reported in case anybody fall into the same issue in the future.
I had this error because I had a field in my Group table which is named keywords, which of course conflicted with the Keywords table (which has many-to-many relation to Groups). I had this field from before I decided to create the many-to-many relation, and I forgot to remove it. Then, of course when you type something like this command group.keywords << Keyword.create(name: key) you will get this weird error: NoMethodError: undefined method<<.
However, fixing this mistake will not make the rake task run. This is because Group.populate returns a Populator object. If you run this task you will get this error: NoMethodError: undefined method keywords for #<Populator::Record:0x007fa1ee78fba8>. To fix that issue I had to assign the foreign keys manually in the task like so:
Group.populate 30 do |group|
Faker::Lorem.words(rand(3..7)).each do |key|
k = Keyword.create(name: key)
a = Assignment.create(keyword_id: k.id, group_id: group.id)
end
end
which will run as expected.

Resources