When I retrieve a contact like this...
c = Contact.where(:user_id => 37)
I can't take advantage of the association the contact has with the Province.rb model, as c.province would produce a no method error.
Contact Load (0.2ms) SELECT "contacts".* FROM "contacts" WHERE "contacts"."user_id" = 37
=> [#<Contact id: 13, firm: "aldjflkads", address: "55 SO Avenue", city: "Toronto", postalcode: "M3A B2B", mobile: "999 999 999", office: "", user_id: 37, created_at: "2013-05-02 18:52:01", updated_at: "2013-05-02 18:52:01", province_id: 6>]
>> c.province
NoMethodError: undefined method `province' for #<ActiveRecord::Relation:0x007fbe94bd9cf0>
However, when I find a contact by user id this way....
>> c = Contact.find_by_user_id(37)
I can then call c.province and c.province.name
Contact Load (0.3ms) SELECT "contacts".* FROM "contacts" WHERE "contacts"."user_id" = 37 LIMIT 1
=> #<Contact id: 13, firm: "aldjflkads", address: "55 aldjfla;skfj", city: "Toronto", postalcode: "M4G B2B", mobile: "999 999 999", office: "", user_id: 37, created_at: "2013-05-02 18:52:01", updated_at: "2013-05-02 18:52:01", province_id: 6>
>> c.province
Province Load (0.2ms) SELECT "provinces".* FROM "provinces" WHERE "provinces"."id" = 6 LIMIT 1
=> #<Province id: 6, name: "Ontario", created_at: "2013-04-19 02:37:11", updated_at: "2013-04-19 02:37:11">
>> c.province.name
=> "Ontario"
Question: Is there a way I can take advantage of association methods if I retrieve data like this
Contact.where(:user_id => 37)
Update
My Contact.rb model belongs_to :provinces, however, there's other data (address, postal code etc) that's native to the contact model. Therefore, if I did this (as suggested in the first answer), it would only allow me to access the province, not any of the other details I need.
#contactdetails = Contact.where({:user_id => #user.id}).first.province
c = Contact.where(:user_id => 37).first.province
where() returns a collection.. whereas find_by_id assumes a unique result and only returns one
Related
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
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
I'm having a hard time with Rails and nested attributes and would really appreciate some help.
Here is the output from my console session where I was attempting to get the updated values to save but as you can see, they don't seem to take on the next line when I perform the find again:
irb(main):070:0* e = Equipment.find(26)
Equipment Load (0.5ms) SELECT "equipment".* FROM "equipment" WHERE "equipment"."id" = $1 LIMIT 1 [["id", 26]]
=> #<Equipment id: 26, name: "fdsfsdsdfsd2", created_at: "2015-11-02 15:26:43", updated_at: "2015-11-02 16:38:55", site_id: 57, type_id: 3>
irb(main):071:0> e.update({"name"=>"fdsfsdsdfsd2", "site_id"=>"57", "type_id"=>"3", "equipment_properties_attributes"=>{"0"=>{"id"=>"15", "value"=>"2015-10-34", "property_id"=>"4"}, "1"=>{"id"=>"16", "value"=>"fsdfdsfsd", "property_id"=>"5"}}})
(0.6ms) BEGIN
EquipmentProperty Load (0.7ms) SELECT "equipment_properties".* FROM "equipment_properties" WHERE "equipment_properties"."equipment_id" = $1 AND "equipment_properties"."id" IN (15, 16) [["equipment_id", 26]]
(0.2ms) COMMIT
=> true
irb(main):072:0> e.equipment_properties
EquipmentProperty Load (0.5ms) SELECT "equipment_properties".* FROM "equipment_properties" WHERE "equipment_properties"."equipment_id" = $1 [["equipment_id", 26]]
=> #<ActiveRecord::Associations::CollectionProxy [#<EquipmentProperty id: 15, equipment_id: 26, property_id: 4, value: "2015-10-34", created_at: "2015-11-02 15:26:51", updated_at: "2015-11-02 15:26:51">, #<EquipmentProperty id: 16, equipment_id: 26, property_id: 5, value: "fsdfdsfsd", created_at: "2015-11-02 15:26:51", updated_at: "2015-11-02 15:26:51">]>
irb(main):073:0> e = Equipment.find(26)
Equipment Load (0.5ms) SELECT "equipment".* FROM "equipment" WHERE "equipment"."id" = $1 LIMIT 1 [["id", 26]]
=> #<Equipment id: 26, name: "fdsfsdsdfsd2", created_at: "2015-11-02 15:26:43", updated_at: "2015-11-02 16:38:55", site_id: 57, type_id: 3>
irb(main):074:0> e.equipment_properties
EquipmentProperty Load (0.6ms) SELECT "equipment_properties".* FROM "equipment_properties" WHERE "equipment_properties"."equipment_id" = $1 [["equipment_id", 26]]
=> #<ActiveRecord::Associations::CollectionProxy [#<EquipmentProperty id: 15, equipment_id: 26, property_id: 4, value: "2015-10-30", created_at: "2015-11-02 15:26:51", updated_at: "2015-11-02 15:26:51">, #<EquipmentProperty id: 16, equipment_id: 26, property_id: 5, value: "fsdfdsfsd", created_at: "2015-11-02 15:26:51", updated_at: "2015-11-02 15:26:51">]>
The same thing is happening with the web interface. I can provide additional details if anyone needs them but I am allowing the parameters through and on creation, the initial values are saved.
I've been beating my head against this all morning and I suspect it is something stupid but I'm just not sure what to try next. Thanks!
UPDATE 1:
Equipment Model:
class Equipment < ActiveRecord::Base
belongs_to :site
belongs_to :type
has_and_belongs_to_many :properties
has_many :equipment_properties
accepts_nested_attributes_for :equipment_properties, reject_if: :all_blank, allow_destroy: true
end
And also the equipment_properties model:
class EquipmentProperty < ActiveRecord::Base
belongs_to :equipment
belongs_to :property
has_one :type, through: :equipment
end
Also, of relevance might be that I can update the individual equipment_property without nesting and that does work.
UPDATE 2:
I managed to add this to the controller and it saves the values now. Not pretty but it works I guess...
equipment_params[:equipment_properties_attributes].each do |property|
ep = EquipmentProperty.where(id: property[1][:id]).first
#logger.debug "EP Value: #{ep.value}"
#logger.debug "Property Value: #{property[1][:value]}"
ep.value = property[1][:value]
ep.save
end
This is what I ended up adding to the controller to resolve this. Definitely a hack though and I'm not sure why the updates are taking:
equipment_params[:equipment_properties_attributes].each do |property|
ep = EquipmentProperty.where(id: property[1][:id]).first
#logger.debug "EP Value: #{ep.value}"
#logger.debug "Property Value: #{property[1][:value]}"
ep.value = property[1][:value]
ep.save
end
I'm trying to retrieve a set of items using where in rails. If I retrieve all I see:
2.2.2 :027 > JourneyLeg.all
JourneyLeg Load (0.2ms) SELECT "journey_legs".* FROM "journey_legs"
=> #<ActiveRecord::Relation [
#<JourneyLeg id: 1, start_station: 24, end_station: 25, departure_time: "2000-01-01 06:37:00", arrival_time: "2000-01-01 06:45:00", journey: 6, created_at: "2015-07-25 11:32:42", updated_at: "2015-07-25 11:32:42">,
#<JourneyLeg id: 2, start_station: 25, end_station: 26, departure_time: "2000-01-01 06:46:00", arrival_time: "2000-01-01 06:50:00", journey: 6, created_at: "2015-07-25 11:32:42", updated_at: "2015-07-25 11:32:42">]>
And now I want to only return items where start_station is 24, so I use JourneyLeg.where(:start_station => 24)
2.2.2 :028 > JourneyLeg.where(:start_station => 24)
JourneyLeg Load (0.1ms) SELECT "journey_legs".* FROM "journey_legs" WHERE "journey_legs"."id" = ? [["start_station", 24]]
=> #<ActiveRecord::Relation []>
But for some reason this is querying id and not start_station (and hence not finding anything) but I have no idea why.
Update 1
Thanks to the comment from David, I spotted that the issue is caused by the fact I have an attribute on the model called start_station which is an integer but also a has_one relationship called start_station
has_one :start_station, :class_name => "Station", :primary_key => "start_station", :foreign_key => "id"
If I remove this relationship or rename it, then the following works as expected with the various suggestions:
JourneyLeg.where(:start_station => 24)
JourneyLeg.where(start_station: 24)
Try this
JourneyLeg.where("start_station = '24'")
This should work based on Documentation of where
JourneyLeg.where({:start_station => 24})
I have tried this with my local application's model called Article, following variant of where works (Rails 4.2.2, Ruby 2.0.0p247)
Variant 1
irb(main):006:0> Article.where({id: 2})
Article Load (1.0ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? [["id", 2]]
=> #<ActiveRecord::Relation [#<Article id: 2, title: "World", text: nil, created_at: "2015-07-25 21:09:16", updated_at: "2015-07-25 21:09:16">]>
Variant 2
irb(main):009:0> Article.where({:id => 2})
Article Load (0.0ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? [["id", 2]]
=> #<ActiveRecord::Relation [#<Article id: 2, title: "World", text: nil, created_at: "2015-07-25 21:09:16", updated_at: "2015-07-25 21:09:16">]>
Variant 3
irb(main):010:0> Article.where(id: 2)
Article Load (0.0ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? [["id", 2]]
=> #<ActiveRecord::Relation [#<Article id: 2, title: "World", text: nil, created_at: "2015-07-25 21:09:16", updated_at: "2015-07-25 21:09:16">]>
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