Using `assign_attributes` saves `has_many through:` association immediately - ruby-on-rails

As far as I know, assign_attributes (unlike update_attributes) is not supposed to save the record or for that matter, any record.
So it quite startled me when I discovered that this is not true when supplying _ids for a has_many through: relation.
Consider the following example:
class GroupUser < ApplicationRecord
belongs_to :group
belongs_to :user
end
class Group < ApplicationRecord
has_many :group_users
has_many :users, through: :group_users
end
class User < ApplicationRecord
has_many :group_users
has_many :groups, through: :group_users
validates :username, presence: true
end
So we have users and groups in an m-to-m relationship.
Group.create # Create group with ID 1
Group.create # Create group with ID 2
u = User.create(username: 'Johny')
# The following line inserts two `GroupUser` join objects, despite the fact
# that we have called `assign_attributes` instead of `update_attributes`
# and, equally disturbing, the user object is not even valid as we've
# supplied an empty `username` attribute.
u.assign_attributes(username: '', group_ids: [1, 26])
The log as requested by a commenter:
irb(main):013:0> u.assign_attributes(username: '', group_ids: [1, 2])
Group Load (0.2ms) SELECT "groups".* FROM "groups" WHERE "groups"."id" IN (1, 2)
Group Load (0.1ms) SELECT "groups".* FROM "groups" INNER JOIN "group_users" ON "groups"."id" = "group_users"."group_id" WHERE "group_users"."user_id" = ? [["user_id", 1]]
(0.0ms) begin transaction
SQL (0.3ms) INSERT INTO "group_users" ("group_id", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["group_id", 1], ["user_id", 1], ["created_at", "2017-06-29 08:15:11.691941"], ["updated_at", "2017-06-29 08:15:11.691941"]]
SQL (0.1ms) INSERT INTO "group_users" ("group_id", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["group_id", 2], ["user_id", 1], ["created_at", "2017-06-29 08:15:11.693984"], ["updated_at", "2017-06-29 08:15:11.693984"]]
(2.5ms) commit transaction
=> nil
I daresay that update_attributes and the _ids construct are mostly used for processing web forms - in this case a form that updates the user itself as well as its group association. So I think it is quite safe to say that the general assumption here is all or nothing, and not a partial save.
Am I using it wrong in some way?

#gokul-m suggests reading about the issue at https://github.com/rails/rails/issues/17368. One of the comments in there points to a temporary workaround: https://gist.github.com/remofritzsche/4204e399e547ff7e3afdd0d89a5aaf3e

an example of my solution to this problem:
ruby:
def assign_parameters(attributes, options = {})
with_transaction_returning_status {self.assign_attributes(attributes, options)}
end

You can handle validation with assign_attributes like so
#item.assign_attributes{ year: "2021", type: "bad" }.valid?

Related

Eager load Rails' has_many with two primary_keys and foreign_keys

I have two models
class TimeEntry < ApplicationRecord
belongs_to :contract
end
class Timesheet < ApplicationRecord
belongs_to :contract
has_many :time_entries, primary_key: :contract_id, foreign_key: :contract_id
end
Additionally, both models have a date column.
The problem: A Timesheet is only for a fixed date and by scoping only to contract_id I always get all time_entries of a contract for each Timesheet.
I tried to scope it like this:
has_many :time_entries, ->(sheet) { where(date: sheet.date) }, primary_key: :contract_id, foreign_key: :contract_id
This works, but unforunately it is not eager loadable:
irb(main):019:0> Timesheet.where(id: [1,2,3]).includes(:time_entries).to_a
Timesheet Load (117.9ms) SELECT "timesheets".* FROM "timesheets" WHERE "timesheets"."id" IN ($1, $2, $3) [["id", 1], ["id", 2], ["id", 3]]
TimeEntry Load (0.3ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."date" = $1 AND "time_entries"."contract_id" = $2 [["date", "2014-11-21"], ["contract_id", 1]]
TimeEntry Load (0.3ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."date" = $1 AND "time_entries"."contract_id" = $2 [["date", "2014-11-22"], ["contract_id", 1]]
TimeEntry Load (0.3ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."date" = $1 AND "time_entries"."contract_id" = $2 [["date", "2014-11-23"], ["contract_id", 1]]
Is it possible, to provide Rails with two primary_keys AND foreign_keys? Or how could I make the example above eager loadable to avoid n+1 queries?
You can use a custom SQL query for the association to retrieve the TimeEntry records for a given Timesheet in this way:
class Timesheet < ApplicationRecord
belongs_to :contract
has_many :time_entries, lambda {
select('*')
.from('time_entries')
.where('time_entries.date = timesheets.date')
.where('time_entries.contract_id = timesheets.contract_id')
}, primary_key: :contract_id, foreign_key: :contract_id
end
Then, can use
timesheets = Timesheet.where(id: [1,2,3]).eager_load(:time_entries)
time_entries = timesheets.first.time_entries
Note:- this will only work with while eager loading, not preloading. That's why explicitly using the keyword instead of includes.

ActiveRecord nested associations

I have some models like Service, Payment and Manager
class Service < ApplicationRecord
validates :name, presence: true
has_and_belongs_to_many :tickets
has_many :payments
end
class Payment < ApplicationRecord
belongs_to :manager
belongs_to :service
validates :value, :manager_id, :service_id, presence: true
end
class Manager < ApplicationRecord
validates :name, presence: true
end
Service can have multiple Payments and each Payment has single Manager.
I want to get the whole data using all nested associations as a hash that I could reformat (or map) and sent to the client. I am stuck with query:
Service.includes(payments: :manager).references(:payments, :managers)
as it is using lazy load, and I need to do something like:
services.first.payments.first.manager
to get data, and it is non-optimal.
Is it possible to get all data with all nested associations?
I did calculations like this:
services = Service.includes(payments: :manager)
.references(:payments, :managers)
result = []
services.each do |service|
service.payments.each do |payment|
manager_name = payment[:manager][:name]
value = payment[:value]
service[manager_name] = value
end
result.push(service)
end
and got an error NoMethodError (undefined method '[]' for nil:NilClass):
on manager_name = payment[:manager][:name] line.
You can define an additional has_many through relationship which should give you what you want.
has_many :managers, through: :payments
class Service < ApplicationRecord
validates :name, presence: true
has_and_belongs_to_many :tickets
has_many :payments
has_many :managers, through: :payments
end
class Payment < ApplicationRecord
belongs_to :manager
belongs_to :service
validates :value, :manager_id, :service_id, presence: true
end
class Manager < ApplicationRecord
validates :name, presence: true
end
I did not understand your need %100 but I come up and idea like following. Please review it.
payments = Payment.joins(:service).select(:service_id, :manager_id, :value).
group_by(&:service_id)
managers = Manager.where('id IN(SELECT manager_id FROM payments)').
select(:name,:id)
group_by(&:id).transform_values{ |value| value.first }
result = []
ResultClass = Struct.new(:service, :manager, :payment_value)
Service.all.each do |service|
payment = payments[service.id]
manager = managers[payment.manager_id]
result << ResultClass.new(service, manager, payment.value)
end
With this code, we are actually trying to accomplish rails includes mechanism under the hood. What we did actually here?
We get the payments and converted them to hash that the id is hash key
Then we get the managers those have payments and we converted also these records with manager id as hash key
We created ResultClass with Struct to store the references of objects like service and manager
We start the iteration. Because of the fact that we have payments and members as hash, there is fast look up and there is no time complexity. In overall, this iteration's time complexity is O(n)
If you have any questions please feel free to drop a comment.
After lots of tries I've finally find some misspells I made and archieved behaviour I want using such code
services = Service.eager_load(payments: :manager)
services.reduce([]) do |acc, service|
service_rec = {
id: service[:id],
name: service[:name],
surgery: service[:surgery]
}
service.payments.each do |payment|
manager_name = payment.manager[:name]
value = payment[:value]
service_rec[manager_name] = value
end
acc.push(service_rec)
end
and SQL query it produces
SQL (0.2ms) SELECT "services"."id" AS t0_r0, "services"."code" AS t0_r1, "services"."name" AS t0_r2, "services"."surgery" AS t0_r3, "services"."enabled" AS t0_r4, "services"."created_at" AS t0_r5, "services"."updated_at" AS t0_r6, "payments"."id" AS t1_r0, "payments"."service_id" AS t1_r1, "payments"."manager_id" AS t1_r2, "payments"."value" AS t1_r3, "payments"."created_at" AS t1_r4, "payments"."updated_at" AS t1_r5, "managers"."id" AS t2_r0, "managers"."name" AS t2_r1, "managers"."enabled" AS t2_r2, "managers"."created_at" AS t2_r3, "managers"."updated_at" AS t2_r4 FROM "services" LEFT OUTER JOIN "payments" ON "payments"."service_id" = "services"."id" LEFT OUTER JOIN "managers" ON "managers"."id" = "payments"."manager_id"
It is interesting that using includes instead of eager_load produces three queries instead
Service Load (0.1ms) SELECT "services".* FROM "services"
↳ app/services/payments_service.rb:6
Payment Load (0.1ms) SELECT "payments".* FROM "payments" WHERE "payments"."service_id" IN (?, ?, ?, ?, ?, ?, ?, ?) [["service_id", 1], ["service_id", 2], ["service_id", 3], ["service_id", 4], ["service_id", 5], ["service_id", 6], ["service_id", 7], ["service_id", 8]]
↳ app/services/payments_service.rb:6
Manager Load (0.1ms) SELECT "managers".* FROM "managers" WHERE "managers"."id" IN (?, ?, ?, ?) [["id", 1], ["id", 2], ["id", 3], ["id", 4]]
Also we can use Service.includes(payments: :manager).references(:payments, :managers) and got the same query as eager_load but it's longer to type))
Thanks everyone for participation! Does anyone have another opinion about eager_load or code optimisation proposals?

Modeling pedigree properly

I'm creating an app to represent the pedigree of livestock. Each child has one dam (e.g. ewe) and one sire (e.g. ram). A dam/sire pairing can have multiple children (e.g. lambs) and a dam and sire may have many more children independent of the other. I am trying to represent this relationship so that I could do something like ewe.children and get a listing of her offspring. Similarly, I'd like to be able to do something like lamb.ewe to get her mother or lamb.ewe.ewe to get her maternal grandmother.
from schema.rb...
create_table "parent_child_relationships", force: :cascade do |t|
t.integer "parent_id"
t.integer "child_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["child_id"], name: "index_parent_child_relationships_on_child_id"
t.index ["parent_id", "child_id"], name: "index_parent_child_relationships_on_parent_id_and_child_id", unique: true
t.index ["parent_id"], name: "index_parent_child_relationships_on_parent_id"
end
from parent_child_relationship.rb...
class ParentChildRelationship < ApplicationRecord
belongs_to :sire, class_name: "Animal"
belongs_to :dam, class_name: "Animal"
belongs_to :children, class_name: "Animal"
end
from animal.rb...
has_one :sire_relationship, class_name: "ParentChildRelationship",
foreign_key: "child_id",
dependent: :destroy
has_one :dam_relationship, class_name: "ParentChildRelationship",
foreign_key: "child_id",
dependent: :destroy
has_many :child_relationships, class_name: "ParentChildRelationship",
foreign_key: "parent_id",
dependent: :destroy
has_one :sire, through: :sire_relationship, source: :child
has_one :dam, through: :dam_relationship, source: :child
has_many :children, through: :child_relationships, source: :parent
In the console, I run the following commands to grab the animals I want to relate to each other...
s = Shepherd.first
ewe = s.animals.find_by(id: 37)
ram = s.animals.find_by(id: 133)
lamb = s.animals.find_by(id: 61)
Now, when I try to create the sire_relationship and dam_relationship I get an error since it doesn't seem to see the relationship as being unique. The sire_relationship is replaced by the dam_relationship...
>> lamb.create_sire_relationship(parent_id: ram.id)
(0.1ms) begin transaction
SQL (0.7ms) INSERT INTO "parent_child_relationships" ("parent_id", "child_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["parent_id", 133], ["child_id", 61], ["created_at", "2018-01-15 15:33:06.649936"], ["updated_at", "2018-01-15 15:33:06.649936"]]
(2.5ms) commit transaction
ParentChildRelationship Load (0.2ms) SELECT "parent_child_relationships".* FROM "parent_child_relationships" WHERE "parent_child_relationships"."child_id" = ? LIMIT ? [["child_id", 61], ["LIMIT", 1]]
=> #<ParentChildRelationship id: 1, parent_id: 133, child_id: 61, created_at: "2018-01-15 15:33:06", updated_at: "2018-01-15 15:33:06">
>> lamb.create_dam_relationship(parent_id: ewe.id)
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "parent_child_relationships" ("parent_id", "child_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["parent_id", 37], ["child_id", 61], ["created_at", "2018-01-15 15:33:35.045703"], ["updated_at", "2018-01-15 15:33:35.045703"]]
(1.0ms) commit transaction
ParentChildRelationship Load (0.1ms) SELECT "parent_child_relationships".* FROM "parent_child_relationships" WHERE "parent_child_relationships"."child_id" = ? LIMIT ? [["child_id", 61], ["LIMIT", 1]]
(0.0ms) begin transaction
SQL (0.5ms) DELETE FROM "parent_child_relationships" WHERE "parent_child_relationships"."id" = ? [["id", 1]]
(1.2ms) commit transaction
=> #<ParentChildRelationship id: 2, parent_id: 37, child_id: 61, created_at: "2018-01-15 15:33:35", updated_at: "2018-01-15 15:33:35">
Creating the children_relationships, I get these errors...
>> ewe.child_relationships.create(child_id: lamb.id)
(0.1ms) begin transaction
SQL (0.9ms) INSERT INTO "parent_child_relationships" ("parent_id", "child_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["parent_id", 37], ["child_id", 61], ["created_at", "2018-01-15 15:37:11.436086"], ["updated_at", "2018-01-15 15:37:11.436086"]]
(0.1ms) rollback transaction
ActiveRecord::RecordNotUnique: SQLite3::ConstraintException: UNIQUE constraint failed: parent_child_relationships.parent_id, parent_child_relationships.child_id: INSERT INTO "parent_child_relationships" ("parent_id", "child_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)
from (irb):13
>> ram.child_relationships.create(child_id: lamb.id)
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "parent_child_relationships" ("parent_id", "child_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["parent_id", 133], ["child_id", 61], ["created_at", "2018-01-15 15:37:25.264947"], ["updated_at", "2018-01-15 15:37:25.264947"]]
(2.5ms) commit transaction
Finally, if I check to see whether I can access the sire of lamb, I get another error...
>> lamb.dam
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) :child in model ParentChildRelationship. Try 'has_many :dam, :through => :dam_relationship, :source => <name>'. Is it one of sire, dam, or children?
from (irb):21
>> lamb.sire
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) :child in model ParentChildRelationship. Try 'has_many :sire, :through => :sire_relationship, :source => <name>'. Is it one of sire, dam, or children?
from (irb):22
I get similar errors if I do ewe.children or ram.children.
I'm looking for an extra pair of eyes to tell me what I'm doing wrong or whether there's an easier way to achieve what I'm after.
The problem is that you only have one parent_id in your animals table which can only store the ID of a single parent. This works for bacteria but not for animals which have two parents. Your parent_id is getting written to when you set the dam and the sire.
There are multiple ways to do this but I think the simplest is to have a dam_id and sire_id in your animals table.
This is the migration to create the table:
class CreateAnimals < ActiveRecord::Migration[5.1]
def change
create_table :animals do |t|
t.integer :dam_id, index: true
t.integer :sire_id, index: true
t.timestamps
end
end
end
This is what your model will look like this. Notice that you need two belongs_to/has_many relationships:
class Animal < ApplicationRecord
belongs_to :dam, class_name: 'Animal'
belongs_to :sire, class_name: 'Animal'
has_many :children_as_sire, class_name: 'Animal', foreign_key: :sire_id
has_many :children_as_dam, class_name: 'Animal', foreign_key: :dam_id
def children
children_as_dam + children_as_sire
end
end
Notice the getter method children that grabs both children_as_dam and children_as_sire. This will result in two SQL queries which is not ideal. If you're tracking the sec of the Animal, you could do something like:
def children?
case sex
when 'male'
children_as_sire
when 'female'
children_as_dam
end
end
I wrote some specs to demonstrate:
require 'rails_helper'
RSpec.describe Animal, type: :model, focus: true do
it 'can be created' do
expect { Animal.create }.to_not raise_error
end
it 'can have a dam' do
animal = Animal.new
animal.update! dam: Animal.create
expect(animal.dam).to be_a(Animal)
expect(animal.sire).to be_nil
end
it 'can have a sire' do
animal = Animal.new
animal.update! sire: Animal.create
expect(animal.sire).to be_a(Animal)
expect(animal.dam).to be_nil
end
it 'can have both a dam and a sire and tell the difference' do
dam = Animal.create
sire = Animal.create
child = Animal.create dam: dam, sire: sire
expect(child.reload.dam).to eq(dam)
expect(child.reload.sire).to eq(sire)
end
it 'grandma' do
grandma = Animal.create
dam = Animal.create dam: grandma
child = Animal.create dam: dam
expect(child.reload.dam.dam).to eq(grandma)
end
it 'has children' do
sire = Animal.create
animal = Animal.create sire: sire
expect(sire.reload.children).to include(animal)
end
end
Notice that you can't add children to a model:
animal = Animal.create
animal.children << Animal.create # will raise an error
Instead, you have to manually set the sire and dam (which is probably what you want to do since you're keeping track).

Cannot save record to database RAILS nested forms

I cannot seem to save my record with nested forms.
This is what pry says:
pry(#<VenuesController>)> #venue.save
(0.3ms) begin transaction
Tag Exists (0.2ms) SELECT 1 AS one FROM "tags" WHERE ("tags"."name" = 'All ' AND "tags"."id" != 2) LIMIT 1
Tag Exists (0.1ms) SELECT 1 AS one FROM "tags" WHERE "tags"."name" = '' LIMIT 1
(0.1ms) rollback transaction
=> false
I thought I followed everything correctly.
This is my nested form for tags
Tags:
<%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name %><br>
Make a New Tag Here: <br>
<%= f.fields_for :tags, Tag.new do |tag_field| %>
<%= tag_field.label :name_tag %>
<%= tag_field.text_field :name %>
<% end %>
This is my venue model
class Venue < ActiveRecord::Base
has_many :venue_tags
has_many :tags, :through => :venue_tags
accepts_nested_attributes_for :tags, allow_destroy: true
end
And my tag model
class Tag < ActiveRecord::Base
has_many :venue_tags
has_many :venues, :through => :venue_tags
validates_uniqueness_of :name
end
However, when take off the 'validate uniqueness of name', I can save it, but new tags are added by itself.
And this is the pry log that tells it's true, but now I'm getting the correct tag added to the venue, but ALSO a new tag added to the venue (one I did not create myself). I am assuming this is happening because the New Tag Fields_for text was blank, which created a new tag by itself.
(0.2ms) begin transaction
SQL (0.6ms) INSERT INTO "venues" ("name", "address", "discount", "latitude", "longitude", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?, ?) [["name", "SPICEBOX"], ["address", "33 Pell St, New York, NY 10013, United States"], ["discount", "10% OFF with Student ID"], ["latitude", 40.714831], ["longitude", -73.998628], ["created_at", "2015-11-03 06:12:52.400643"], ["updated_at", "2015-11-03 06:12:52.400643"]]
SQL (0.2ms) INSERT INTO "venue_tags" ("tag_id", "venue_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["tag_id", 2], ["venue_id", 11], ["created_at", "2015-11-03 06:12:52.404715"], ["updated_at", "2015-11-03 06:12:52.404715"]]
SQL (0.2ms) INSERT INTO "tags" ("name", "created_at", "updated_at") VALUES (?, ?, ?) [["name", ""], ["created_at", "2015-11-03 06:12:52.408821"], ["updated_at", "2015-11-03 06:12:52.408821"]]
SQL (0.1ms) INSERT INTO "venue_tags" ("venue_id", "tag_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["venue_id", 11], ["tag_id", 9], ["created_at", "2015-11-03 06:12:52.411692"], ["updated_at", "2015-11-03 06:12:52.411692"]]
(1.4ms) commit transaction
=> true
You should probably add a validates :presence to tag name. You seem to have a tag in your db that has no name and when you add another with no name it is not unique and won't pass validations.
Take a look at the form or the strong parameters to see how that value is being cleared, if it is.
If it's blank because it was intentionally submitted that way, you can ignore it if blank.
accepts_nested_attributes_for :tags, allow_destroy: true, :reject_if => lambda { |a| a[:name].blank? }
However, when take off the 'validate uniqueness of name', I can save
it
Check the database for the name and see if it already exists.
Since you are able to save the record, you code is probably ok.
To see why the record doesn't want to save, you can ask out the errors of the object you are trying to save. You can do this in your pry console.
#venue.save
...
=> false
#venue.errors.messages
=> ...
It will probably tell you that you already have a record with the same name. If that it's the case, then it would make sense your record isn't saving.

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

Resources