Find Non Orphan records in Rails - ruby-on-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)

Related

Rails 4 ActiveRecords: Save multiple records at once with validation

Hi i have the following situation. I can save this array of hashes
products = [{
:name=> 0,
:key => 12345,
:label => "test1",
},{
:name=> 0,
:key => 12145,
:label => "test",
}]
at once with
products.map {|p| Product.new(p).save }
or
Product.create(products)
Product.create!(products)
but the all my uniqueness validations like
validates :key, presence: true, uniqueness: true
are ignored, using the rails console. I am able to save this hash multiple times. Does anyone has some advice? Thanks in advance!
SOLUTION
As simple as restarting my rails console. After that my logs look like the logs from #Akadisoft.
Also using this way:
products.map {|p| Product.new(p).save }
which return an array with booleans [false,true] either the record was saved or not, which is nice for further evaluation.
I have tried your exact solution and everything works as intented..
The first time I ran the command Product.create(products) I have the following result
=> #<ActiveRecord::Relation [#<Product id: 1, key: "12345", name: 0, label: "test1", created_at: "2015-12-09 19:21:01", updated_at: "2015-12-09 19:21:01">, #<Product id: 2, key: "12145", name: 0, label: "test", created_at: "2015-12-09 19:21:01", updated_at: "2015-12-09 19:21:01">]>
We can see that it created two products (id:1 and id:2).
Now the second I run the exact same command here is the result:
[#<Product id: nil, key: "12345", name: 0, label: "test1", created_at: nil, updated_at: nil>, #<Product id: nil, key: "12145", name: 0, label: "test", created_at: nil, updated_at: nil>]
irb(main):015:0>
We can see the ID is nil because it didn't save the products. I can also see in the console output that DB transaction were rollbacked.
I tried this on console and it only added one record.
14:42 $ rails c
Loading development environment (Rails 4.2.5)
2.2.2 :001 > products = [{:key =>1},{:key=>1}]
=> [{:key=>1}, {:key=>1}]
2.2.2 :002 > products.map {|p| Product.new(p).save }
(0.1ms) begin transaction
Product Exists (0.1ms) SELECT 1 AS one FROM "products" WHERE "products"."key" = 1 LIMIT 1
SQL (0.8ms) INSERT INTO "products" ("key", "created_at", "updated_at") VALUES (?, ?, ?) [["key", 1], ["created_at", "2015-12-09 19:42:32.488255"], ["updated_at", "2015-12-09 19:42:32.488255"]]
(0.4ms) commit transaction
(0.0ms) begin transaction
Product Exists (0.1ms) SELECT 1 AS one FROM "products" WHERE "products"."key" = 1 LIMIT 1
(0.0ms) rollback transaction
=> [true, false]
2.2.2 :003 > Product.all
Product Load (0.2ms) SELECT "products".* FROM "products"
=> #<ActiveRecord::Relation [#<Product id: 1, key: 1, created_at: "2015-12-09 19:42:32", updated_at: "2015-12-09 19:42:32">]>

Updating Rails 4 Nested Attributes

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

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

undefined method with has_many association

I have 2 models with a one-to-many association: User and Recipe. the User class has_many :recipes while the Recipe class belongs_to :user. I've already run the migration, reloaded the rails console, and checked to make sure that user_id is a column in the recipes table. Still, I get an undefined method error when I try to append a recipe to a user:
2.0.0-p598 :047 > user.recipes << Recipe.first
NoMethodError: undefined method `recipes' for #<User:0x00000004326fa0>
here is the migration code (I've already run rake db:migrate):
class AddUserIdToRecipes < ActiveRecord::Migration
def change
add_column :recipes, :user_id, :integer
end
end
Here is the User model code:
class User < ActiveRecord::Base
has_one :profile
has_many :recipes
end
Here is the Recipe model code:
class Recipe < ActiveRecord::Base
validates_presence_of :title, :body
belongs_to :user
def long_title
"#{title} - #{published_at}"
end
end
Why does recipes still show up as an undefined method?
Try this on your console:
irb(main):007:0> user = User.new first_name: 'John', last_name: 'Doe'
=> #<User id: nil, first_name: "John", last_name: "Doe", created_at: nil, updated_at: nil>
irb(main):008:0> user.save
(0.1ms) begin transaction
SQL (0.6ms) INSERT INTO "users" ("created_at", "first_name", "last_name", "updated_at") VALUES (?, ?, ?, ?) [["created_at", "2015-01-19 21:14:33.489371"], ["first_name", "John"], ["last_name", "Doe"], ["updated_at", "2015-01-19 21:14:33.489371"]]
(0.6ms) commit transaction
=> true
irb(main):009:0> r = Recipe.new name: 'oooohh awesome', description: 'my description goes here'
=> #<Recipe id: nil, name: "oooohh awesome", description: "my description goes here", created_at: nil, updated_at: nil, user_id: nil>
irb(main):010:0> r.save
(0.1ms) begin transaction
SQL (0.2ms) INSERT INTO "recipes" ("created_at", "description", "name", "updated_at") VALUES (?, ?, ?, ?) [["created_at", "2015-01-19 21:15:16.548090"], ["description", "my description goes here"], ["name", "oooohh awesome"], ["updated_at", "2015-01-19 21:15:16.548090"]]
(1.2ms) commit transaction
=> true
irb(main):011:0> user.recipes << Recipe.first
Recipe Load (0.2ms) SELECT "recipes".* FROM "recipes" ORDER BY "recipes"."id" ASC LIMIT 1
(0.0ms) begin transaction
SQL (0.2ms) UPDATE "recipes" SET "updated_at" = ?, "user_id" = ? WHERE "recipes"."id" = 1 [["updated_at", "2015-01-19 21:15:49.181586"], ["user_id", 1]]
(1.3ms) commit transaction
Recipe Load (0.2ms) SELECT "recipes".* FROM "recipes" WHERE "recipes"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Recipe id: 1, name: "oooohh awesome", description: "sper long deskdk", created_at: "2015-01-19 21:10:24", updated_at: "2015-01-19 21:15:49", user_id: 1>]>
irb(main):012:0> user.save
(0.1ms) begin transaction
(0.0ms) commit transaction
=> true
irb(main):013:0> user.recipes
=> #<ActiveRecord::Associations::CollectionProxy [#<Recipe id: 1, name: "oooohh awesome", description: "sper long deskdk", created_at: "2015-01-19 21:10:24", updated_at: "2015-01-19 21:15:49", user_id: 1>]>
irb(main):014:0> user.recipes.first
=> #<Recipe id: 1, name: "oooohh awesome", description: "sper long deskdk", created_at: "2015-01-19 21:10:24", updated_at: "2015-01-19 21:15:49", user_id: 1>
irb(main):015:0>
you can see that Recipe.first has been inserted into user.recipes and its saved.
I made two models similar to yours, and have exactly the same setup as you. You can follow code above to write your controllers.

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