Using Rails 5.2 and Active Storage, I set up an Item class with some images:
class Item < ApplicationRecord
has_many_attached :images
end
I'd like to use ActiveRecord::QueryMethods.includes to eager-load the images, pretty standard Rails stuff with has_many, but:
Item.includes(:images)
=> ActiveRecord::AssociationNotFoundError ("Association named 'images' was not found on Item; perhaps you misspelled it?")
Item.includes(:attachments)
=> ActiveRecord::AssociationNotFoundError ("Association named 'attachments' was not found on Item; perhaps you misspelled it?")
Item.includes(:active_storage_attachments)
=> ActiveRecord::AssociationNotFoundError ("Association named 'active_storage_attachments' was not found on Item; perhaps you misspelled it?")
Any idea how to make it work?
ActiveStorage provides a method to prevent N+1 queries
Gallery.where(user: Current.user).with_attached_photos
https://api.rubyonrails.org/classes/ActiveStorage/Attached/Model.html#method-i-has_many_attached
So, in your case:
Item.with_attached_images
…aaand I found the answer:
Item.reflections.keys
=> ["location", "images_attachments", "images_blobs", "taggings", "base_tags", "tag_taggings", "tags"]
The name of the Active Storage-generated association is images_attachments even though it is accessible through Item#images. Here's the solution:
Item.includes(:images_attachments)
Item Load (0.6ms) SELECT "items".* FROM "items" LIMIT $1 [["LIMIT", 11]]
ActiveStorage::Attachment Load (0.6ms) SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_type" = $1 AND "active_storage_attachments"."name" = $2 AND "active_storage_attachments"."record_id" IN ($3, $4, $5, $6, $7) [["record_type", "Item"], ["name", "images"], ["record_id", 3], ["record_id", 2], ["record_id", 4], ["record_id", 5], ["record_id", 1]]
=> #<ActiveRecord::Relation […]>
Related
I have
class CustomSessionsController < Devise::SessionsController
def create
#user = resource # needed for Merit
super
end
protected
def after_sign_in_path_for(resource)
#user = resource # needed for Merit
resource.update_streak
super
And
grant_on 'custom_sessions#create', badge: :streak, level: 3, temporary: true, model_name: 'User' do |user|
puts user.inspect
user.streak.count >= 3
end
But it gives the error
[merit] no target_obj found on Rule#applies?
And I can't access the model and it doesn't grant the badge or log the user. What is wrong? I followed the guide.
https://github.com/merit-gem/merit/wiki/How-to-grant-badges-on-user-using-Devise
It's doing something.
Processing by CustomSessionsController#create as HTML
Parameters: {"utf8"=>"√", "authenticity_token"=>"gqUQjF9hfzJdQqxAAQJxv7bi+kZYwuv1NWtOP0YhkbjHKwnfa5WAb/CkRZ5c+Xi5yVlnJ2v774w3XLhTa1b1sQ==", "user"=>{"email"=>"student#gmail.com", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Log in"}
User Load (6.0ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 ORDER BY "users"."id" ASC LIMIT 1 [["email", "student#gmail.com"]]
(7.0ms) BEGIN
SQL (3.0ms) UPDATE "users" SET "last_sign_in_at" = $1, "current_sign_in_at" = $2, "sign_in_count" = $3, "updated_at" = $4 WHERE "users"."id" = $5 [["last_sign_in_at", "2018-08-09 05:38:58.345271"], ["current_sign_in_at", "2018-08-10 01:40:51.644592"], ["sign_in_count", 15], ["updated_at", "2018-08-10 01:40:51.668609"], ["id", 3]]
(25.0ms) COMMIT
Streak Load (21.0ms) SELECT "streaks".* FROM "streaks" WHERE "streaks"."user_id" = $1 LIMIT 1 [["user_id", 3]]
Redirected to http://localhost:3000/
(1.0ms) BEGIN
SQL (7.0ms) INSERT INTO "merit_actions" ("user_id", "action_method", "target_model", "target_data", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["user_id", 3], ["action_method", "create"], ["target_model", "custom_sessions"], ["target_data", "--- \n...\n"], ["created_at", "2018-08-10 01:40:53.539847"], ["updated_at", "2018-08-10 01:40:53.539847"]]
(8.0ms) COMMIT
Merit::Action Load (6.0ms) SELECT "merit_actions".* FROM "merit_actions" WHERE "merit_actions"."processed" = $1 [["processed", "f"]]
(3.0ms) BEGIN
SQL (2.0ms) UPDATE "merit_actions" SET "processed" = $1, "updated_at" = $2 WHERE "merit_actions"."id" = $3 [["processed", "t"], ["updated_at", "2018-08-10 01:40:53.581875"], ["id", 17]]
(20.0ms) COMMIT
User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
[merit] no target_obj found on Rule#applies?
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
[merit] no target_obj found on Rule#applies?
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT 1
Completed 302 Found in 2567ms (ActiveRecord: 293.2ms)
Merit 2.4, Rails 4.2.
I tried
grant_on 'custom_sessions#create', badge: :streak, level: 3, temporary: true do
puts current_user.inspect
current_user.streak.count >= 3
end
But it gave
[merit] no target found: uninitialized constant CustomSession. base_target_finder.rb:13:in 'find'
error NameError (undefined local variable or method 'current_user'
I tried
grant_on 'custom_sessions#create', badge: :streak, level: 3, temporary: true, to: :itself do |user|
puts user.inspect
user.streak.count >= 3
end
def create
#custom_session = resource # needed for Merit
def after_sign_in_path_for(resource)
#custom_session = resource # needed for Merit
But it gave
[merit] no target found: uninitialized constant CustomSession. C:/ruby23/lib/ruby/gems/2.3.0/gems/merit-2.4.0/lib/merit/base_target_finder.rb:13:in `find'
true
Completed 500 Internal Server Error in 2181ms (ActiveRecord: 177.1ms)
NoMethodError (undefined method `streak' for true:TrueClass):
app/models/merit/badge_rules.rb:43:in `block in initialize'
I got it working with
grant_on 'custom_sessions#create', badge: :streak, level: 3, temporary: true, model_name: 'User', to: :itself do |user|
def create
super
#custom_session = resource # needed for Merit
But I don't know why because the /users/sign_in path does not have an :id parameter.
https://github.com/merit-gem/merit#how-merit-finds-the-target-object
Merit would fetch the Article object from the database, found by the :id param sent in that update action.
Is there a way to find a record by created_at: beginning_of_week?
Something like:
Message.find_by(created_at: Date.today.beginning_of_week).
But it doesn't work.
Because created_at is a DateTime object which includes both a Date and Time value, then your
Message.find_by(created_at: Date.today.beginning_of_week)
# Message Load (6.2ms) SELECT "messages".* FROM "messages" WHERE "messages"."created_at" = $1 LIMIT $2 [["created_at", "2018-01-29"], ["LIMIT", 1]]
... will try to find a record at exactly 2018-01-29 00:00:00 which is a Message record that is exactly created at midnight, instead of 2018-01-29 that you might have expected. You do not want that and instead want ANY record that is created in that day (as far as I understood your question). So, you can try the following instead.
date_beginning_this_week = Date.today.beginning_of_week
Message.where(created_at: date_beginning_this_week..(date_beginning_this_week + 1.day))
# Message Load (0.2ms) SELECT "messages".* FROM "messages" WHERE ("messages"."created_at" BETWEEN $1 AND $2) LIMIT $3 [["created_at", "2018-01-29"], ["created_at", "2018-01-30"], ["LIMIT", 11]]
I'm using factory_bot to create objects for organisation, but here organisations are created before create call, and because of validation create request is not processed.
it 'should increase organisation count by 1' do
expect do
post :create, params: { organisation: attributes_for(:organisation)}, xhr: true
end.to change(Organisation, :count).by(1)
end
What did I do wrong in this?
(0.6ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
(0.2ms) BEGIN
(0.2ms) SAVEPOINT active_record_1
Organisation Exists (0.7ms) SELECT 1 AS one FROM "organisations" WHERE "organisations"."name" = $1 LIMIT $2 [["name", "Organisation 2"], ["LIMIT", 1]]
Organisation Exists (0.2ms) SELECT 1 AS one FROM "organisations" WHERE "organisations"."email" = $1 LIMIT $2 [["email", "organisation#domain.com"], ["LIMIT", 1]]
SQL (0.5ms) INSERT INTO "organisations" ("name", "created_at", "updated_at", "email", "phone_number", "location") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id" [["name", "Organisation 2"], ["created_at", "2018-01-27 05:36:13.649627"], ["updated_at", "2018-01-27 05:36:13.649627"], ["email", "organisation#domain.com"], ["phone_number", "+918292929292"], ["location", "Karur"]]
Processing by Admins::OrganisationsController#create as JS
Parameters: {"organisation"=>{"email"=>"organisation#domain.com", "location"=>"Karur", "name"=>"Organisation 2", "phone_number"=>"+918292929292"}}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 127], ["LIMIT", 1]]
(0.1ms) SAVEPOINT active_record_1
Organisation Exists (0.3ms) SELECT 1 AS one FROM "organisations" WHERE "organisations"."name" = $1 LIMIT $2 [["name", "Organisation 2"], ["LIMIT", 1]]
Organisation Exists (0.5ms) SELECT 1 AS one FROM "organisations" WHERE "organisations"."email" = $1 LIMIT $2 [["email", "organisation#domain.com"], ["LIMIT", 1]]
When I try to print the count,
it 'should increase organisation count by 1' do
p Organisation.count
login_admin
p Organisation.count
expect do
post :create, params: {organisation: attributes_for(:organisation)}, xhr: true
end.to change(Organisation, :count).by(1)
end
end
the log,
Run options: include {:full_description=>/Admins::OrganisationsController\ POST\ \#\ create\ with\ valid\ params\ should\ increase\ organisation\ count\ by\ 1/}
0
1
expected #count to have changed by 1, but was changed by 0
./spec/controllers/admins/organisations_controller_spec.rb:71:in `block (4 levels) in <top (required)>'
-e:1:in `load'
-e:1:in `<main>'
Organisation is created when admin object is created. There will an association between admin and organisation.
Show your login_admin method and factories of admin and organisation if further help needed.
I am using FriendlyID, and it creates a slug based on some attributes of a record upon creation.
extend FriendlyId
friendly_id :name_and_school, use: :slugged
def name_and_school
"#{first_name} #{last_name} #{school.name}"
end
I also have the following on my model:
validates :first_name, :last_name, presence: true
validates_associated :school, presence: true
However, when I go to test this validation and submit a form with an empty value for first_name, last_name and school, I get the following error:
Position Load (0.8ms) SELECT "positions".* FROM "positions" WHERE 1=0
(0.8ms) BEGIN
(0.6ms) ROLLBACK
Completed 500 Internal Server Error in 51ms (ActiveRecord: 11.7ms)
NoMethodError - undefined method `name' for nil:NilClass:
app/models/profile.rb:79:in `name_and_school'
friendly_id (5.1.0) lib/friendly_id/slugged.rb:295:in `should_generate_new_friendly_id?'
friendly_id (5.1.0) lib/friendly_id/slugged.rb:304:in `set_slug'
So what's clear is that it is hitting that name_and_school method, even before it hits ActiveRecord's validation checks.
If it hit the validation checks first, it wouldn't generate this error it would just reload the page with the respective errors.
So how do I fix this and make sure the FriendlyID slug is only generated if all validations pass?
Edit 1
So I tried insisting that name_and_school only returns a valid string if the values are present like so:
def name_and_school
"#{first_name} #{last_name} #{school.name}" if first_name.present? && last_name.present? && school.present?
end
This now works for empty/invalid first_name and last_name attributes.
However, when I leave the school field blank, it now gives me this error:
Completed 500 Internal Server Error in 31689ms (Searchkick: 9.5ms | ActiveRecord: 23.6ms)
NoMethodError - undefined method `name' for nil:NilClass:
app/models/profile.rb:139:in `search_data'
searchkick (1.4.0) lib/searchkick/index.rb:289:in `search_data'
searchkick (1.4.0) lib/searchkick/index.rb:66:in `block in bulk_index'
searchkick (1.4.0) lib/searchkick/index.rb:66:in `bulk_index'
searchkick (1.4.0) lib/searchkick/index.rb:54:in `store'
searchkick (1.4.0) lib/searchkick/logging.rb:28:in `block in store'
activesupport (5.0.0.1) lib/active_support/notifications.rb:164:in `block in instrument'
activesupport (5.0.0.1) lib/active_support/notifications/instrumenter.rb:21:in `instrument'
activesupport (5.0.0.1) lib/active_support/notifications.rb:164:in `instrument'
searchkick (1.4.0) lib/searchkick/logging.rb:27:in `store'
searchkick (1.4.0) lib/searchkick/index.rb:96:in `reindex_record'
searchkick (1.4.0) lib/searchkick/model.rb:113:in `reindex'
app/models/profile.rb:146:in `reindex_profile'
Which corresponds to this in my model:
after_commit :reindex_profile
def search_data
{
name: name,
bib_color: bib_color,
height: height,
weight: weight,
player_type: player_type,
school_name: school.name,
age: age,
position_name: positions.map(&:name)
}
end
def reindex_profile
reindex
end
Once the new record is committed, it runs that callback -- which expects a valid school attribute.
So the nil value is still escaping the validates_associated check.
I even tried adding that same if statement to the reindex_profile method like so:
def reindex_profile
reindex if first_name.present? && last_name.present? && school.present? && bib_color.present? && player_type.present? && age.present?
end
But that doesn't work either.
Edit 2
After try #codyeatworld's answer, we are making progress.
The issue now is that even though I am not getting those errors any more, the record is being created despite the validates_associated :school, presence: true not being true.
For instance, here is an example of the request:
Started POST "/profiles" for ::1 at 2016-11-13 16:32:19 -0500
Processing by ProfilesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"skJA+9NB3XhR/JLgQ==", "profile"=>{"avatar"=>#<ActionDispatch::Http::UploadedFile:0x007fbf3b35d9a8 #tempfile=#<Tempfile:/var/folders/0f/hgplttnd7d/T/RackMultipart20161113-16651-1wvdzdx.jpg>, #original_filename="Random-Image.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"profile[avatar]\"; filename=\"Random-Image.jpg\"\r\nContent-Type: image/jpeg\r\n">, "first_name"=>"Random", "last_name"=>"Brown", "dob(3i)"=>"13", "dob(2i)"=>"11", "dob(1i)"=>"1986", "weight"=>"", "height"=>"", "bib_color"=>"", "player_type"=>"player", "position_ids"=>[""], "school_id"=>"", "grade"=>"", "tournament_ids"=>[""], "email"=>"", "cell_phone"=>"", "home_phone"=>"", "grades_attributes"=>{"0"=>{"subject"=>"", "result"=>"", "grade_type"=>"csec", "_destroy"=>"false"}}, "transcripts_attributes"=>{"0"=>{"url_cache"=>"", "_destroy"=>"false"}}, "achievements_attributes"=>{"0"=>{"body"=>"", "achievement_type"=>"academic", "_destroy"=>"false"}}, "videos_attributes"=>{"0"=>{"vimeo_url"=>"", "official"=>"", "_destroy"=>"false"}}, "articles_attributes"=>{"0"=>{"source"=>"", "title"=>"", "url"=>"", "_destroy"=>"false"}}}, "commit"=>"Create Profile"}
User Load (1.8ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 2], ["LIMIT", 1]]
Role Load (1.8ms) SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 2]]
Role Load (1.3ms) SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'coach') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 2]]
Role Load (1.3ms) SELECT "roles".* FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'player') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 2]]
(1.7ms) SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL)) OR ((roles.name = 'coach') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 2]]
The "mime_type" Shrine metadata field will be set from the "Content-Type" request header, which might not hold the actual MIME type of the file. It is recommended to load the determine_mime_type plugin which determines MIME type from file content.
Tournament Load (1.2ms) SELECT "tournaments".* FROM "tournaments" WHERE 1=0
Position Load (0.8ms) SELECT "positions".* FROM "positions" WHERE 1=0
(0.9ms) BEGIN
SQL (2.2ms) INSERT INTO "profiles" ("first_name", "last_name", "dob", "bib_color", "created_at", "updated_at", "player_type", "grade", "home_phone", "cell_phone", "email", "avatar_data") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING "id" [["first_name", "Yashin"], ["last_name", "Brown"], ["dob", Thu, 13 Nov 1986], ["bib_color", ""], ["created_at", 2016-11-13 21:32:19 UTC], ["updated_at", 2016-11-13 21:32:19 UTC], ["player_type", 0], ["grade", ""], ["home_phone", ""], ["cell_phone", ""], ["email", ""], ["avatar_data", "{\"id\":\"83d162a3d33bdc5d2527502e1d423ab3.jpg\",\"storage\":\"cache\",\"metadata\":{\"filename\":\"Random-Image.jpg\",\"size\":61798,\"mime_type\":\"image/jpeg\"}}"]]
SQL (2.0ms) INSERT INTO "transcripts" ("profile_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["profile_id", 30], ["created_at", 2016-11-13 21:32:19 UTC], ["updated_at", 2016-11-13 21:32:19 UTC]]
Profile Load (3.7ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."id" = $1 LIMIT $2 [["id", 30], ["LIMIT", 1]]
(1.6ms) COMMIT
(0.9ms) BEGIN
SQL (2.6ms) UPDATE "profiles" SET "updated_at" = $1, "avatar_data" = $2 WHERE "profiles"."id" = $3 [["updated_at", 2016-11-13 21:32:19 UTC], ["avatar_data", "{\"id\":\"ec63dcc18ed5d60aa7a6626550f9f9ea.jpg\",\"storage\":\"store\",\"metadata\":{\"filename\":\"Random-Image.jpg\",\"size\":61798,\"mime_type\":\"image/jpeg\"}}"], ["id", 30]]
(1.3ms) COMMIT
Profile Store (311.8ms) {"id":30}
Profile Store (32.2ms) {"id":30}
Profile Store (26.7ms) {"id":30}
Profile Store (18.9ms) {"id":30}
Redirected to http://localhost:3000/profiles/30
Completed 302 Found in 530ms (Searchkick: 389.6ms | ActiveRecord: 32.2ms)
Notice that "school_id"=>"", also notice that the URL is now profiles/30 (rather than the FriendlyID URL, which means the friendlyID slug didn't execute which is what I want).
Why is this record still being created despite my validates_associated call I have on the model?
I think you're looking for should_generate_new_friendly_id then.
def should_generate_new_friendly_id?
first_name.present? && last_name.present? && school.present?
end
FriendlyId will only try to create a slug if these conditions are met.
I think the offending line in searchkick is the school.name attribute.
def search_data
{
name: name,
bib_color: bib_color,
height: height,
weight: weight,
player_type: player_type,
# school_name: school.name,
school_name: (school.present? ? school.name : nil),
age: age,
position_name: positions.map(&:name)
}
end
I'm assuming that you want to validate the presence of school. The method validates_associated will run the validations for the school object/model. It doesn't check the presence of school_id.
validates :first_name, :last_name, :school_id, presence: true
# Remove validates_associated :school, presence: true
I think you can also omit _id:
belongs_to :school
validates :school, presence: true
I have a model called as Template which has self referential has-many through association with a join model called as RelatedTemplate.
The Template model is written like this:
has_many :related_templates, :foreign_key => :parent_id
has_many :children, :through => :related_templates, :source => :child
has_many :inverse_related_templates, class_name: "RelatedTemplate", :foreign_key => :child_id
has_many :parents, :through => :inverse_related_templates, :source => :parent
validates :name, presence: true, uniqueness: { case_sensitive: false },
length: { maximum: 100, too_long: "should be maximum %{count} characters" }
The join-model RelatedTemplate is:
belongs_to :parent, class_name: 'Template'
belongs_to :child, class_name: 'Template'
In the console when i tried to create a child template with the same name as the parent name then the validation doesn't work:
rails c --sandbox
Loading development environment in sandbox (Rails 4.2.5)
Any modifications you make will be rolled back on exit
irb(main):001:0> a = Template.new
=> #<Template id: nil, client_id: nil, something_id: nil, name: nil, description: nil, another_something_id: nil, video_id: nil, container_id: nil>
irb(main):002:0> a.something_id=1
=> 1
irb(main):003:0> a.name="Hamza"
=> "Hamza"
irb(main):004:0> a.another_something_id=16
=> 16
irb(main):005:0> a.container_id=2
=> 2
irb(main):006:0> a.children.build(name: "Hamza", another_something_id: 16, container_id: 2)
=> #<Template id: nil, client_id: nil, something_id: nil, name: "Hamza", description: nil, another_something_id: 16, video_id: nil, container_id: 2>
irb(main):007:0> a.save
(0.9ms) SAVEPOINT active_record_1
Template Exists (1.3ms) SELECT 1 AS one FROM "templates" WHERE LOWER("templates"."name") = LOWER('Hamza') LIMIT 1
Container Load (0.7ms) SELECT "containers".* FROM "containers" WHERE "containers"."id" = $1 LIMIT 1 [["id", 2]]
Template Exists (0.5ms) SELECT 1 AS one FROM "templates" WHERE LOWER("templates"."name") = LOWER('Hamza') LIMIT 1
Container Load (0.2ms) SELECT "containers".* FROM "containers" WHERE "containers"."id" = $1 LIMIT 1 [["id", 2]]
SQL (0.3ms) INSERT INTO "templates" ("something_id", "name", "another_something_id", "container_id") VALUES ($1, $2, $3, $4) RETURNING "id" [["something_id", 1], ["name", "Hamza"], ["another_something_id", 16], ["container_id", 2]]
SQL (0.2ms) INSERT INTO "templates" ("name", "another_something_id", "container_id") VALUES ($1, $2, $3) RETURNING "id" [["name", "Hamza"], ["another_something_id", 16], ["container_id", 2]]
SQL (0.6ms) INSERT INTO "related_templates" ("parent_id", "child_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["parent_id", 156], ["child_id", 157], ["created_at", "2016-05-05 07:03:51.496346"], ["updated_at", "2016-05-05 07:03:51.496346"]]
(0.2ms) RELEASE SAVEPOINT active_record_1
=> true
Now the query:
irb(main):016:0> a.name
=> "Hamza"
irb(main):017:0> a.children.first.name
Template Load (1.9ms) SELECT "templates".* FROM "templates" INNER JOIN "related_templates" ON "templates"."id" = "related_templates"."child_id" WHERE "related_templates"."parent_id" = $1 ORDER BY "templates"."id" ASC LIMIT 1 [["parent_id", 158]]
=> "Hamza"
Dont know what is wrong here ?
Both object only exist in memory, when you validate them. The uniqueness validation passes in both cases, because the validation checks if there is a similar record in the database already.
Have a look at the logs:
# you call save
irb(main):007:0> a.save
(0.9ms) SAVEPOINT active_record_1
# Rails validates the first object
Template Exists (1.3ms) SELECT 1 AS one FROM "templates" WHERE LOWER("templates"."name") = LOWER('Hamza') LIMIT 1
Container Load (0.7ms) SELECT "containers".* FROM "containers" WHERE "containers"."id" = $1 LIMIT 1 [["id", 2]]
# Rails validates the second object
Template Exists (0.5ms) SELECT 1 AS one FROM "templates" WHERE LOWER("templates"."name") = LOWER('Hamza') LIMIT 1
Container Load (0.2ms) SELECT "containers".* FROM "containers" WHERE "containers"."id" = $1 LIMIT 1 [["id", 2]]
# At this point Rails thinks that both records will be fine, because the validation passed
# Rails saved the first object
SQL (0.3ms) INSERT INTO "templates" ("something_id", "name", "another_something_id", "container_id") VALUES ($1, $2, $3, $4) RETURNING "id" [["something_id", 1], ["name", "Hamza"], ["another_something_id", 16], ["container_id", 2]]
# Rails saved the second object
SQL (0.2ms) INSERT INTO "templates" ("name", "another_something_id", "container_id") VALUES ($1, $2, $3) RETURNING "id" [["name", "Hamza"], ["another_something_id", 16], ["container_id", 2]]
This is good example why Rails uniqueness validations are not 100% secure. Another example might be race conditions, when multiple requests hit the application at the same time.
The only way to avoid this kind of problems, is to add a unique index to the database table.