Updating Rails 4 Nested Attributes - ruby-on-rails

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

Related

Find Non Orphan records in Rails

Following up on the question : Want to find records with no associated records in Rails
I am wondering how I can get all the NON orphan records returned as an AssociationRelation instead of an Array. When trying to subtract the total records of the table from the rails 6 .missing ones, the result is correct, but it's in the form of an array.
Here is a console example :
p = ProductResearch.first
(Product.all - p.products.where.missing(:keywords)).class
=> Array
How do I get the association ?
( With the help of #max below I found a query, without missing, that returns the expected result as an association. It's like :
irb(main):206:0> p.products.includes(:keywords).where.not(keywords: { id: nil }).class
=> Product::ActiveRecord_AssociationRelation
and it does return the non orphan ones only.
Given:
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post
end
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
# Referential integrity is for wusses! YOLO!
t.belongs_to :post, null: true, foreign_key: false
t.timestamps
end
end
end
p1 = Post.create!(title: 'Foo')
3.times { p1.comments.create! }
p2 = Post.create!(title: 'Bar')
3.times { p2.comments.create! }
p2.destroy! # orphans the comments
If you do an INNER JOIN on posts you will only get rows with at least one match in the join table:
irb(main):014:0> Comment.joins(:post)
Comment Load (0.3ms) SELECT "comments".* FROM "comments" INNER JOIN "posts" ON "posts"."id" = "comments"."post_id" LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Comment id: 1, post_id: 1, created_at: "2021-05-11 08:59:04", updated_at: "2021-05-11 08:59:04">, #<Comment id: 2, post_id: 1, created_at: "2021-05-11 08:59:04", updated_at: "2021-05-11 08:59:04">, #<Comment id: 3, post_id: 1, created_at: "2021-05-11 08:59:04", updated_at: "2021-05-11 08:59:04">]>
This gives you the "non-orphaned" posts.
The opposite is of course an OUTER JOIN:
irb(main):016:0> Comment.left_joins(:post).where(posts: { id: nil })
Comment Load (0.3ms) SELECT "comments".* FROM "comments" LEFT OUTER JOIN "posts" ON "posts"."id" = "comments"."post_id" WHERE "posts"."id" IS NULL LIMIT ? [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Comment id: 4, post_id: 2, created_at: "2021-05-11 08:59:26", updated_at: "2021-05-11 08:59:26">, #<Comment id: 5, post_id: 2, created_at: "2021-05-11 08:59:26", updated_at: "2021-05-11 08:59:26">, #<Comment id: 6, post_id: 2, created_at: "2021-05-11 08:59:26", updated_at: "2021-05-11 08:59:26">]>
Rails 6.1 added the .missing query method which is a shortcut for the above query:
Comment.where.missing(:post)

How to get the results of other tables using includes or joins in Ruby on Rails

I want to query the results of private_classes. The scenario is:
class PrivateClass < ActiveRecord::Base
belongs_to :private_school
has_many :lesson_plans
end
class JoinedPrivateSchool < ActiveRecord::Base
belongs_to :student
belongs_to :private_school
end
class PrivateSchool < ActiveRecord::Base
belongs_to :teacher
has_many :private_classes
has_many :joined_private_schools
end
class Student < ActiveRecord::Base
has_many :joined_private_schools
end
JoinPrivateSchool has the attribute private_school_id.
I am doing:
s = Student.find(9)
s.joined_private_schools
which results in:
=> #<ActiveRecord::Associations::CollectionProxy [#<JoinedPrivateSchool id: 8,
user_id: 73, student_id: 9, private_school_id: 28, created_at: "2017-02-16
12:38:37", updated_at: "2017-02-16 12:38:37">, #<JoinedPrivateSchool id: 9,
user_id: 73, student_id: 9, private_school_id: 33, created_at: "2017-02-16
12:42:01", updated_at: "2017-02-16 12:42:01">, #<JoinedPrivateSchool id: 12,
user_id: 73, student_id: 9, private_school_id: 32, created_at: "2017-02-16
13:19:02", updated_at: "2017-02-16 13:19:02">]>
If I do:
c = s.joined_private_schools.includes(private_school: :private_classes)
it results in:
JoinedPrivateSchool Load (0.6ms) SELECT "joined_private_schools".* FROM
"joined_private_schools" WHERE "joined_private_schools"."student_id" = $1
[["student_id", 9]]
PrivateSchool Load (0.9ms) SELECT "private_schools".* FROM "private_schools"
WHERE "private_schools"."id" IN (28, 33, 32) ORDER BY
"private_schools"."created_at" DESC
PrivateClass Load (0.7ms) SELECT "private_classes".* FROM "private_classes"
WHERE "private_classes"."private_school_id" IN (33, 32, 28) ORDER BY
"private_classes"."created_at" DESC
=> #<ActiveRecord::AssociationRelation [#<JoinedPrivateSchool id: 8, user_id:
73, student_id: 9, private_school_id: 28, created_at: "2017-02-16 12:38:37",
updated_at: "2017-02-16 12:38:37">, #<JoinedPrivateSchool id: 9, user_id: 73,
student_id: 9, private_school_id: 33, created_at: "2017-02-16 12:42:01",
updated_at: "2017-02-16 12:42:01">, #<JoinedPrivateSchool id: 12, user_id: 73,
student_id: 9, private_school_id: 32, created_at: "2017-02-16 13:19:02",
updated_at: "2017-02-16 13:19:02">]>
But that is still the wrong results.
I need to get the results of private_classes using one query so I can avoid multiple each loops.
If you want to have PrivateClass, you're starting from the wrong point (User). You need to start from PrivateClass joining all the intermediary tables and using the user id as condition:
PrivateClass.joins(private_school: :user).where(user: {id: 9})
# or a little more performant (avoids one join)
PrivateClass.joins(:private_school).where(private_school: {user_id: 9})
You can use :through on associations (at least in Rails 4.2)
I updated this, I first missed that you wanted private_classes
class JoinedPrivateSchool < ActiveRecord::Base
belongs_to :student
belongs_to :private_school
has_many :private_classes, through: :private_schools
end
class Student < ActiveRecord::Base
has_many :joined_private_schools
has_many :private_classes, through: :joined_private_schools
end
s = Student.find(9)
s.private_classes

Rails where not behaving as expected

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">]>

assocition methods on active record relations

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

entry returned on first query, empty on second

I have an application which allows lawyers and law students to answer legal questions. Their answers can be voted up. Beside each answer on the views/question/show.html.erb, the application indicates whether an answer has been voted up and by who (a lawyer, or a law student). However, it's behaving very oddly. Currently, on a test question, if a lawyer votes up an answer, the application is not showing the upvote, but if a student votes up an answer, then both the student's and the lawyer's vote will be displayed, but both are displayed as student votes.
This is the code in the show action of the Questions controller that retrieves all the answers for a question, and then queries for the type of votes each answer has.
def show
#question = Question.find(params[:id])
#answers = #question.answers
#answers.each do |a|
#lawyervotes = AnswerVote.where({:answer_id => a.id, :lawyervote => true}).reload
puts #lawyervotes.inspect
puts "lawyervotes"
#studentvotes = AnswerVote.where({:answer_id => a.id, :studentvote => true}).reload
#uservotes = AnswerVote.where({:answer_id => a.id, :lawyervote => nil, :studentvote => nil}).reload
end
end
If I look in the console for the puts statements, it shows that #lawyervotes contains one result, but then it's suddenly an empty array. Currently, there are two answers for this question, which is why the puts statement is run twice, but I don't know why it's empty on the second time through
[#<AnswerVote id: 34, value: 3, answer_id: 54, user_id: 37, created_at: "2013-05-08 18:29:34", updated_at: "2013-05-08 18:29:34", lawyervote: true, studentvote: nil>]
lawyervotes
[]
lawyervotes
Note, the reason why I put reload on the end of each query was to avoid an ActiveRecord::AssociationTypeMismatch error I was getting, which according to another SO answer I found can happen when you query with 'where.' I found another SO answer that said putting 'reload' on the end of a where query can help avoid that error.
Can you explain why this odd behavior might be happening with my lawyervotes and student votes and possibly tell me how to rewrite the show action to avoid it. Thank you in advance.
Update
This is the console record showing that question 62 has two answers, each with one answer_vote. One of the answer votes was by a lawyer (lawyer = true) while one was by a student (student = true), however, they're both showing up as student votes in my application, even after trying dmitry's solution.
>> q = Question.find_by_id(62)
Question Load (0.2ms) SELECT "questions".* FROM "questions" WHERE "questions"."id" = 62 LIMIT 1
=> #<Question id: 62, details: "I have a terminal illness but don't have time to go...", question: "What happens if I die without a will?", user_id: 35, accepted_answer_id: nil, created_at: "2013-05-08 18:19:48", updated_at: "2013-05-08 18:19:48", city: "Toronto", province: nil, province_id: 6>
>> q.answers
Answer Load (0.2ms) SELECT "answers".* FROM "answers" WHERE "answers"."question_id" = 62
=> [#<Answer id: 54, content: "There is legislation that determines the rules of i...", accepted: nil, user_id: 50, question_id: 62, created_at: "2013-05-08 18:20:41", updated_at: "2013-05-08 18:20:41">, #<Answer id: 55, content: "Ontario has statutory provisions that detail who in...", accepted: nil, user_id: 37, question_id: 62, created_at: "2013-05-08 18:22:53", updated_at: "2013-05-08 18:22:53">]
>> a54 = Answer.find_by_id(54)
Answer Load (0.3ms) SELECT "answers".* FROM "answers" WHERE "answers"."id" = 54 LIMIT 1
=> #<Answer id: 54, content: "There is legislation that determines the rules of i...", accepted: nil, user_id: 50, question_id: 62, created_at: "2013-05-08 18:20:41", updated_at: "2013-05-08 18:20:41">
>> a54.answer_votes
AnswerVote Load (0.2ms) SELECT "answer_votes".* FROM "answer_votes" WHERE "answer_votes"."answer_id" = 54
=> [#<AnswerVote id: 34, value: 3, answer_id: 54, user_id: 37, created_at: "2013-05-08 18:29:34", updated_at: "2013-05-08 18:29:34", lawyervote: true, studentvote: nil>]
>> a55 = Answer.find_by_id(55)
Answer Load (0.3ms) SELECT "answers".* FROM "answers" WHERE "answers"."id" = 55 LIMIT 1
=> #<Answer id: 55, content: "Ontario has statutory provisions that detail who in...", accepted: nil, user_id: 37, question_id: 62, created_at: "2013-05-08 18:22:53", updated_at: "2013-05-08 18:22:53">
>> a55.answer_votes
AnswerVote Load (0.3ms) SELECT "answer_votes".* FROM "answer_votes" WHERE "answer_votes"."answer_id" = 55
=> [#<AnswerVote id: 35, value: 3, answer_id: 55, user_id: 50, created_at: "2013-05-08 18:37:32", updated_at: "2013-05-08 18:37:32", lawyervote: nil, studentvote: true>]
Update
I put this code in the loop
puts AnswerVote.where({:answer_id => a.id}).reload.inspect
puts "inspectinganswervote"
and got this result
[#<AnswerVote id: 34, value: 3, answer_id: 54, user_id: 37, created_at: "2013-05-08 18:29:34", updated_at: "2013-05-08 18:29:34", lawyervote: true, studentvote: nil>]
inspectinganswervote
[#<AnswerVote id: 35, value: 3, answer_id: 55, user_id: 50, created_at: "2013-05-08 18:37:32", updated_at: "2013-05-08 18:37:32", lawyervote: nil, studentvote: true>]
inspectinganswervote
Update
Answer.rb
class Answer < ActiveRecord::Base
attr_accessible :accepted, :content, :question_id, :user_id
has_many :comments
belongs_to :question
belongs_to :user
has_many :answer_votes
has_and_belongs_to_many :watchers, :join_table => "answer_watchers", :class_name => "User"
has_reputation :votes, source: :user, aggregated_by: :sum
has_reputation :lawyervotes, source: :user, aggregated_by: :sum
has_reputation :studentvotes, source: :user, aggregated_by: :sum
has_reputation :best, source: :user, aggregated_by: :sum
#
def add_to_watchers(user)
self.watchers << user unless self.watchers.include?(user)
end
after_create :creator_watches_me
private
def creator_watches_me
self.watchers << user unless self.watchers.include?(user)
end
end
AnswerVote.rb
class AnswerVote < ActiveRecord::Base
attr_accessible :answer_id, :user_id, :value, :answer, :lawyervote, :studentvote
belongs_to :answer
belongs_to :user
validates_uniqueness_of :answer_id, scope: :user_id
validates_inclusion_of :value, in: [1,-1,10,-10, 3]
validate :ensure_not_author
scope :lawyers, where(lawyervote: true)
scope :students, where(studentvote: true)
def ensure_not_author
errors.add :user_id, "is the author of the answer" if answer.user_id == user_id
end
end
One of the problems -- you rewrite your #lawyervotes array during the next iteration. One of the ways out would be to append it instead (using something like:
#lawyervotes = []
#answers.each do |a|
#lawyervotes <<= AnswerVote.where({:answer_id => a.id, :lawyervote => true}).reload
...
end
But it is super-terrible, non-Rails style. As I mentioned above, you do not need this iteration through #answers, you simply write:
UPDATED
#lawyervotes = #question.answers.map {|a| a.answer_votes.lawyers}.reject!(&:empty?).flatten
#studentvotes = #question.answers.map {|a| a.answer_votes.students}.reject!(&:empty?).flatten
And in you AnswerVotes model:
scope :lawyers, where(lawyervote: true)
scope :students, where(studentvote: true)
You are getting the lawyervotes array as empty for second answer as the second answer has only one AnswerVote with laywervote = nil and studentvote = true :) So the vote is present in the #studentvotes variable.
If you inspect your #studentvotes as well, you will see your second vote printed in the second iteration of the loop.

Resources