How to save in database only "full" unique objects? - ruby-on-rails

guys.
I have a model Order with attrbutes :name, :phone, :product_id
How to save in database only unique objects of Order - with unique combination of :name, :phone and :product?
In example:
already in db:
Order name: 'Bob', phone: '123', product_id: '4'
must not been saved:
Order name: 'Bob', phone: '123', product_id: '4'
must be saved:
Order name: 'Bob', phone: '123', product_id: '5'
must be saved:
Order name: 'Bob', phone: '1234', product_id: '4'

Try to set a unique validation with scope. Docs are here
class Order < ActiveRecord::Base
validates :name, uniqueness: { scope: [:phone, :product_id], message: "Not UNIQ" }
end
Here is a result:
[29] pry(main)> Order.all
Order Load (0.3ms) SELECT "orders".* FROM "orders"
=> [#<Order:0x007fd07cc6fd00 id: 1, name: "Bob", phone: "1234", product_id: 4, created_at: Thu, 11 Aug 2016 02:56:22 UTC +00:00, updated_at: Thu, 11 Aug 2016 02:56:22 UTC +00:00>]
[30] pry(main)> o1 = Order.new(name:"Bob", phone:"1234", product_id: 4)
=> #<Order:0x007fd07d5f3390 id: nil, name: "Bob", phone: "1234", product_id: 4, created_at: nil, updated_at: nil>
[31] pry(main)> o1.valid?
Order Exists (0.4ms) SELECT 1 AS one FROM "orders" WHERE ("orders"."name" = 'Bob' AND "orders"."phone" = '1234' AND "orders"."product_id" = 4) LIMIT 1
=> false
[32] pry(main)> o1.errors
=> #<ActiveModel::Errors:0x007fd07d65b580 #base=#<Order:0x007fd07d5f3390 id: nil, name: "Bob", phone: "1234", product_id: 4, created_at: nil, updated_at: nil>, #messages={:name=>["Not UNIQ"]}>
[33] pry(main)> o2 = Order.new(name:"Bob", phone:"12345", product_id: 4)
=> #<Order:0x007fd07cc7f3e0 id: nil, name: "Bob", phone: "12345", product_id: 4, created_at: nil, updated_at: nil>
[34] pry(main)> o2.valid?
Order Exists (0.4ms) SELECT 1 AS one FROM "orders" WHERE ("orders"."name" = 'Bob' AND "orders"."phone" = '12345' AND "orders"."product_id" = 4) LIMIT 1
=> true

Related

ActiveRecord Collection subtraction

Had a question on subtracting queries from similar ActiveRecord collections.
Let say I have one query that is as follows:
all_users = User.all
users_with_adequate_reviews = User.joins(:reviews).select("users.id, count(*) as num_reviews").group(:id).having("num_reviews > 5")
if I do all_users - users_with_adequate_reviews, I get what I would expect from which is users with fewer than review count of 5. How does ActiveRecord relation subtraction know to remove the similar records even though i only select a few attributes from users (primarily the id). Was looking to see documentation on this but couldn't find it anywhere
WHERE IS SUBTRACTION METHOD DEFINED ?
Subtraction on ActiveRecord relation is defined on ActiveRecord::Delegation module.
If you're digging that source code, you can see that method is delegated from Array class.
So we need to dig Array's subtraction to understand how ActiveRecord relation's subtraction works.
HOW DOES ARRAY SUBTRACTION WORK ?
This is taken from documentation about Array subtraction / difference.
Array Difference
Returns a new array that is a copy of the original array, removing any
items that also appear in other_ary. The order is preserved from the
original array.
It compares elements using their hash and eql? methods for efficiency.
It means subtraction evaluates two methods : hash && eql? from each object to perform task.
HOW DO THOSE METHODS WORK ON ACTIVE RECORD OBJECT ?
The code below is taken from ActiveRecord::Core module.
def ==(comparison_object)
super ||
comparison_object.instance_of?(self.class) &&
!id.nil? &&
comparison_object.id == id
end
alias :eql? :==
def hash
if id
self.class.hash ^ id.hash
else
super
end
end
You can see both hash & eql? only evaluates class and id.
It means all_users - users_with_adequate_reviews will exclude some objects ONLY IF there are any objects from both elements that have same object's id and object's class.
ANOTHER SAMPLE
irb(main):001:0> users = User.all
User Load (26.4ms) SELECT `users`.* FROM `users` LIMIT 11
=> #<ActiveRecord::Relation [
#<User id: 1, name: "Bob", created_at: "2020-06-09 13:03:45", updated_at: "2020-06-09 13:03:45">,
#<User id: 2, name: "Danny", created_at: "2020-06-09 13:04:14", updated_at: "2020-06-09 13:04:14">,
#<User id: 3, name: "Alan", created_at: "2020-06-09 13:05:30", updated_at: "2020-06-09 13:05:30">,
#<User id: 4, name: "Joe", created_at: "2020-06-09 13:07:00", updated_at: "2020-06-09 13:07:00">]>
irb(main):002:0> users_with_multiple_emails = User.joins(:user_emails).select("users.id, users.name, count(*) as num_emails").group(:id).having("num_emails > 1")
User Load (2.8ms) SELECT users.id, users.name, count(*) as num_emails FROM `users` INNER JOIN `user_emails` ON `user_emails`.`user_id` = `users`.`id` GROUP BY `users`.`id` HAVING (num_emails > 1) LIMIT 11
=> #<ActiveRecord::Relation [#<User id: 1, name: "Bob">]>
irb(main):003:0> users - users_with_multiple_emails
=> [
#<User id: 2, name: "Danny", created_at: "2020-06-09 13:04:14", updated_at: "2020-06-09 13:04:14">,
#<User id: 3, name: "Alan", created_at: "2020-06-09 13:05:30", updated_at: "2020-06-09 13:05:30">,
#<User id: 4, name: "Joe", created_at: "2020-06-09 13:07:00", updated_at: "2020-06-09 13:07:00">]
As you can see all users - users_with_multiple_emails excludes first object (Bob).
Why ? It's because Bob from both elements have same id and class (id: 1, class: User)
Subtraction returns different result if it's like this
irb(main):001:0> users = User.all
User Load (26.4ms) SELECT `users`.* FROM `users` LIMIT 11
=> #<ActiveRecord::Relation [
#<User id: 1, name: "Bob", created_at: "2020-06-09 13:03:45", updated_at: "2020-06-09 13:03:45">,
#<User id: 2, name: "Danny", created_at: "2020-06-09 13:04:14", updated_at: "2020-06-09 13:04:14">,
#<User id: 3, name: "Alan", created_at: "2020-06-09 13:05:30", updated_at: "2020-06-09 13:05:30">,
#<User id: 4, name: "Joe", created_at: "2020-06-09 13:07:00", updated_at: "2020-06-09 13:07:00">]>
irb(main):002:0> users_with_multiple_emails = User.joins(:user_emails).select("users.name, count(*) as num_emails").group(:id).having("num_emails > 1")
User Load (2.3ms) SELECT users.name, count(*) as num_emails FROM `users` INNER JOIN `user_emails` ON `user_emails`.`user_id` = `users`.`id` GROUP BY `users`.`id` HAVING (num_emails > 1) LIMIT 11
=> #<ActiveRecord::Relation [#<User id: nil, name: "Bob">]>
irb(main):003:0> users - users_with_multiple_emails
=> [
#<User id: 1, name: "Bob", created_at: "2020-06-09 13:03:45", updated_at: "2020-06-09 13:03:45">,
#<User id: 2, name: "Danny", created_at: "2020-06-09 13:04:14", updated_at: "2020-06-09 13:04:14">,
#<User id: 3, name: "Alan", created_at: "2020-06-09 13:05:30", updated_at: "2020-06-09 13:05:30">,
#<User id: 4, name: "Joe", created_at: "2020-06-09 13:07:00", updated_at: "2020-06-09 13:07:00">]
This time users_with_multiple_emails only select name & num_emails.
As you can see all users - users_with_multiple_emails doesn't exclude Bob.
Why ? It's because Bob from both elements have different id.
Bob's id from users : 1
Bob's id from users_with_multiple_emails : nil

ActiveRecord 'where' query not working for certain string columns in postgresql

I'm new to activerecord and postgresql and I have the following issue with regards to a activerecord query. Please help me understand why this is happening and how I can resolve it.
I have multiple text columns which is declared in a standard way in my schema file.
create_table "time_entries", force: :cascade do |t|
t.string "user"
t.string "email"
etc ....
end
I'm trying to find records with 'user' = 'Test'.
TimeEntry.where(user: "Test")
works fine, however
TimeEntry.where("user = 'Test'")
does not work.
I can see that the two query yields different sql queries but I'm trying to understand why this works for the 'email' field but not for the 'user' field.
User Field Query:
irb(main):011:0> TimeEntry.where(user: "Test")
TimeEntry Load (0.5ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."user" = $1 [["user", "Test"]]
=> #<ActiveRecord::Relation [#<TimeEntry id: 1, user: "Test", email: "test#testemail.com", task: nil, description: "TEST CRM", billable: "No", start_date: "2016-01-03", start_time: "2000-01-01 14:20:22", end_date: "2016-01-03", end_time: "2000-01-01 14:26:29", duration: "2000-01-01 00:06:07", tags: nil, amount: 111, created_at: "2017-01-03 15:39:51", updated_at: "2017-01-03 15:39:51", project_id: 1, resource_id: nil>]>
irb(main):012:0> TimeEntry.where("user = 'Test'")
TimeEntry Load (0.4ms) SELECT "time_entries".* FROM "time_entries" WHERE (user = 'Test')
=> #<ActiveRecord::Relation []>
irb(main):013:0>
Email Field Query:
irb(main):013:0> TimeEntry.where(email: "test#testemail.com")
TimeEntry Load (0.3ms) SELECT "time_entries".* FROM "time_entries" WHERE "time_entries"."email" = $1 [["email", "test#testemail.com"]]
=> #<ActiveRecord::Relation [#<TimeEntry id: 1, user: "Test", email: "test#testemail.com", task: nil, description: "TEST CRM", billable: "No", start_date: "2016-01-03", start_time: "2000-01-01 14:20:22", end_date: "2016-01-03", end_time: "2000-01-01 14:26:29", duration: "2000-01-01 00:06:07", tags: nil, amount: 111, created_at: "2017-01-03 15:39:51", updated_at: "2017-01-03 15:39:51", project_id: 1, resource_id: nil>]>
irb(main):014:0> TimeEntry.where("email = 'test#testemail.com'")
TimeEntry Load (0.4ms) SELECT "time_entries".* FROM "time_entries" WHERE (email = 'test#testemail.com')
=> #<ActiveRecord::Relation [#<TimeEntry id: 1, user: "Test", email: "test#testemail.com", task: nil, description: "TEST CRM", billable: "No", start_date: "2016-01-03", start_time: "2000-01-01 14:20:22", end_date: "2016-01-03", end_time: "2000-01-01 14:26:29", duration: "2000-01-01 00:06:07", tags: nil, amount: 111, created_at: "2017-01-03 15:39:51", updated_at: "2017-01-03 15:39:51", project_id: 1, resource_id: nil>]>
Thanks for your help.
Ok, seems this issue occurs due to 'user' being a reserved keyword in postgres.
The following code works fine:
TimeEntry.where('time_entries.user = ?', 'Test')
or
TimeEntry.where('"user" = ?','Test')

values are not updating in rails 3

I am tried to update attribute normally but it is not updating.
Here is my customer model
class Customer < ActiveRecord::Base
attr_accessible :name, :phone_number, :email
attr_accessible :first_name
attr_accessible :last_name
before_validation :parse_name
def name
"#{first_name} #{last_name}".strip
end
private
def parse_name
if attributes['name'].present?
self.first_name, self.last_name = attributes['name'].strip.split(' ', 2)
end
end
end
I tried to update first_name and last_name but it is not updating but if I tried to update email it is updating fine
Here is my rails console trace
Updating email
2.1.0dev :004 > a = Customer.find(5)
Customer Load (0.2ms) SELECT `customers`.* FROM `customers` WHERE `customers`.`id` = 5 LIMIT 1
=> #<Customer id: 5, name: "First Last", phone_number: "1234567890", created_at: "2014-09-15 12:48:30", updated_at: "2014-09-15 14:32:17", first_name: "Fir
2.1.0dev :008 > a = Customer.find(5)
Customer Load (0.1ms) SELECT `customers`.* FROM `customers` WHERE `customers`.`id` = 5 LIMIT 1
=> #<Customer id: 5, name: "First Last", phone_number: "1234567890", created_at: "2014-09-15 12:48:30", updated_at: "2014-09-15 14:32:17", first_name: "Fir
st", last_name: "Last", email: "shri#mail.com">
2.1.0dev :009 > a.email = "Test#test.com"
=> "Test#test.com"
2.1.0dev :010 > a.save
(0.2ms) BEGIN
Customer Exists (0.2ms) SELECT 1 AS one FROM `customers` WHERE (`customers`.`phone_number` = BINARY '1234567890' AND `customers`.`id` != 5) LIMIT 1
(0.4ms) UPDATE `customers` SET `email` = 'Test#test.com', `updated_at` = '2014-09-15 20:38:31' WHERE `customers`.`id` = 5
(104.9ms) COMMIT
=> true
2.1.0dev :011 > a
=> #<Customer id: 5, name: "First Last", phone_number: "1234567890", created_at: "2014-09-15 12:48:30", updated_at: "2014-09-15 20:38:31", first_name: "Fir
st", last_name: "Last", email: "Test#test.com">
But for updating first_name and last_name is not working
updating last_name
2.1.0dev :012 > a.last_name = "last_name"
=> "last_name"
2.1.0dev :013 > a.save
(0.2ms) BEGIN
Customer Exists (0.7ms) SELECT 1 AS one FROM `customers` WHERE (`customers`.`phone_number` = BINARY '1234567890' AND `customers`.`id` != 5) LIMIT 1
(0.2ms) COMMIT
=> true
2.1.0dev :014 > a.save!
(0.2ms) BEGIN
Customer Exists (0.2ms) SELECT 1 AS one FROM `customers` WHERE (`customers`.`phone_number` = BINARY '1234567890' AND `customers`.`id` != 5) LIMIT 1
(0.1ms) COMMIT
=> true
2.1.0dev :015 > a
=> #<Customer id: 5, name: "First Last", phone_number: "1234567890", created_at: "2014-09-15 12:48:30", updated_at: "2014-09-15 20:38:31", first_name: "Fir
st", last_name: "Last", email: "Test#test.com">
Using update attributes
2.1.0dev :016 > a.update_attributes(:first_name => "test_name", :last_name => "test_name")
(0.2ms) BEGIN
Customer Exists (0.2ms) SELECT 1 AS one FROM `customers` WHERE (`customers`.`phone_number` = BINARY '1234567890' AND `customers`.`id` != 5) LIMIT 1
(0.2ms) COMMIT
=> true
2.1.0dev :017 > a
=> #<Customer id: 5, name: "First Last", phone_number: "1234567890", created_at: "2014-09-15 12:48:30", updated_at: "2014-09-15 20:38:31", first_name: "Fir
st", last_name: "Last", email: "Test#test.com">
I tried to resolve it but it is not working.
Can any please tell me what I am missing.
I am using rails 3.2.14 and rails 2.1.0
You have a before_validation method where first and last name are set depending on name. Your record already has a name, so it splits this name and extract first and last name.
Your before_validation is overwriting the value you want to set. I would remove the parse name method all together and have the #name method simply be a calculated value based on first_name and last_name e.g.
class Customer < ActiveRecord::Base
attr_accessible :phone_number, :email, :first_name, :last_name
def name
"#{first_name} #{last_name}".strip
end
end
If you really must have #name= I would do it as
def name=(name_string)
self.first_name, self.last_name = name_string.strip.split(' ', 2)
end
You will have to save for these values to hold. You could also do something like.
def update_name(name_string)
update_attributes(Hash[[:first_name,:last_name].zip(name_string.strip.split(' ', 2))])
end
Which will run save with and return true or false based on validity

Rails ActiveRecord : scope incoherence

In my model I have :
#models/friend.rb
scope :approved_friend, where(:approved => true)
And the Rails console outputs :
User.find(2).friends
=> [#<Friend id: 18, user_id: 2, approved: true, created_at: "2013-04-23 09:18:59", updated_at: "2013-04-23 09:18:59", friend_id: 1>]
User.find(2).friends.approved_friend
=> []
Notice that approved is true in the output ...
Where it gets crazy is here :
User.find(1).friends.approved_friend
=> [#<Friend id: 19, user_id: 1, approved: true, created_at: "2013-04-23 09:19:36", updated_at: "2013-04-23 09:19:36", friend_id: 2>]
Am-I missing something ?
EDIT :
On one hand you have this query :
SELECT "friends".* FROM "friends" WHERE "friends"."user_id" = 2
=> [#<Friend id: 18, user_id: 2, approved: true, created_at: "2013-04-23 09:18:59", updated_at: "2013-04-23 09:18:59", friend_id: 1>]
On the other hand, you've got this (query through scope) :
SELECT "friends".* FROM "friends" WHERE "friends"."user_id" = 2 AND "friends"."approved" = 't'
=> []
Since the :status field is in the Friend model, you might have to change the scope to this
scope :approved_friend, where('friends.approved' => true).includes(:friend)

:autosave ignored on has_many relation -- what am I missing?

I have a pair of classes:
class Collection < ActiveRecord::Base
has_many :items, autosave: true
end
class Item < ActiveRecord::Base
belongs_to :collection
end
From the docs:
When :autosave is true all children is saved, no matter whether they are new records:
But when I update an Item and save its parent Collection, the Item's upated attributes don't get saved:
> c = Collection.first
=> #<Collection id: 1, name: "collection", created_at: "2012-07-23 00:00:10", updated_at: "2012-07-23 00:00:10">
> i = c.items.first
=> #<Item id: 1, collection_id: 1, name: "item1", created_at: "2012-07-23 00:00:25", updated_at: "2012-07-23 00:00:25">
> i.name = 'new name'
=> "new name"
> c.save
=> true
> Collection.first.items
=> [#<Item id: 1, collection_id: 1, name: "item1", created_at: "2012-07-23 00:00:25", updated_at: "2012-07-23 00:00:25">]
So, what am I missing?
I'm using Rails 3.2.5 and Ruby 1.9.2.
So I've done some digging about in the source of ActiveRecord. We can get hold of c's autosave assocations:
> c.class.reflect_on_all_autosave_associations
=> [#<ActiveRecord::Reflection::AssociationReflection:0x007fece57b3bd8 #macro=:has_many, #name=:items, #options={:autosave=>true, :extend=>[]}, #active_record=Collection(id: integer, name: string, created_at: datetime, updated_at: datetime), #plural_name="items", #collection=true, #class_name="Item", #klass=Item(id: integer, collection_id: integer, name: string, created_at: datetime, updated_at: datetime), #foreign_key="collection_id", #active_record_primary_key="id", #type=nil>]
I think this illustrates that the association has been set up for autosaving.
We can then get the instance of the association corresponding to c:
> a = c.send :association_instance_get, :items
=> #<ActiveRecord::Associations::HasManyAssociation:0x007fece738e920 #target=[#<Item id: 1, collection_id: 1, name: "item1", created_at: "2012-07-23 00:00:25", updated_at: "2012-07-23 00:00:25">], #reflection=#<ActiveRecord::Reflection::AssociationReflection:0x007fece57b3bd8 #macro=:has_many, #name=:items, #options={:autosave=>true, :extend=>[]}, #active_record=Collection(id: integer, name: string, created_at: datetime, updated_at: datetime), #plural_name="items", #collection=true, #class_name="Item", #klass=Item(id: integer, collection_id: integer, name: string, created_at: datetime, updated_at: datetime), #foreign_key="collection_id", #active_record_primary_key="id", #type=nil>, #owner=#<Collection id: 1, name: "collection", created_at: "2012-07-23 00:00:10", updated_at: "2012-07-23 00:00:10">, #updated=false, #loaded=true, #association_scope=[#<Item id: 1, collection_id: 1, name: "item1", created_at: "2012-07-23 00:00:25", updated_at: "2012-07-23 00:00:25">], #proxy=[#<Item id: 1, collection_id: 1, name: "item1", created_at: "2012-07-23 00:00:25", updated_at: "2012-07-23 00:00:25">], #stale_state=nil>
We can then find the actual objects that are associated via this association:
> a.target
=> [#<Item id: 1, collection_id: 1, name: "item1", created_at: "2012-07-23 00:00:25", updated_at: "2012-07-23 00:00:25">]
The object found here does not have update that I'd made earlier.
The problem here is the line
i = c.items.first
This line pulls the correct item from the database, but doesn't attach it to the collection c. It is a distinct ruby object from the object
i = c.items[0]
If you replace the first line with the second your example will work.

Resources