My model:
class User
has_many :items
end
class Item
belongs_to :user
def self.with_color(color, is_loaded)
if is_loaded
self.select { |i| i.color == color }
else
self.where(:color => color)
end
end
end
There are 2 types of code in my controllers.
## this line loads 'items' of the user and selects with the red color ##
#colored_items = current_user.items.with_color('red', false)
## this code should not load items twice ##
#user_with_items = User.includes(:items).find(params[:user_id])
(...)
colored_items = #user_with_items.with_color('red', true)
If I only write "select" version, it doesnt work with 1 example, and 'where' version loads items again even if they are already loaded.
How can I check is object already loaded or not?
How about using scopes?
Taken from example:
class Shirt < ActiveRecord::Base
scope :colored, lambda { |color| where(:color => color) }
end
More to read here: http://apidock.com/rails/ActiveRecord/Scoping/Named/ClassMethods/scope
With your example, this should be (not tested)
class User
has_many :items
end
class Item
belongs_to :user
scope :colored, lambda { |color| where(:color => color) }
end
## this line loads 'items' of the user and selects with the red color ##
#colored_items = current_user.items.colored('red')
## use of 'includes' here to solve (n+1) issue
#user_with_items = User.includes(:items).find(params[:user_id])
colored_items = #user_with_items.colored('red')
Update:
class User < ActiveRecord::Base
has_many :items
def colored_items(color)
self.items.to_a.select { |i| i.color == color }
end
end
user = User.includes(:items).find(1)
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
Item Load (0.4ms) SELECT "items".* FROM "items" WHERE "items"."user_id" IN (1)
=> #<User id: 1, name: nil, created_at: "2012-03-25 00:39:52", updated_at: "2012-03-25 00:39:52">
user.colored_items "red"
=> [#<Item id: 1, color: "red", user_id: 1, created_at: "2012-03-25 00:41:00", updated_at: "2012-03-25 00:41:00">]
user.colored_items "blue"
=> [#<Item id: 2, color: "blue", user_id: 1, created_at: "2012-03-25 00:41:04", updated_at: "2012-03-25 00:41:04">]
So only 2 SQLs here.
Related
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)
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
Today i want change my Postgres enum to Rails 4.1 enum.
I make some steps.
migrations:
create_table :users do |t|
# other column here
t.column :status, :integer
end
in model:
class User < ActiveRecord::Base
enum status: [ :local, :tourist ]
end
now create user:
=> User.create(email: 'ffoo#mai.com', password: '12345678', status: 'local')
=> #<User id: "53621ec84d6163124", email: "ffoo#mai.com", status: 0, ......>
=> u = User.last
=> User Load (0.9ms) SELECT "users".* FROM "users" ........
=> #<User id: "53621ec84d6163124", email: "ffoo#mai.com", status: 0, ......>
=> u.local?
=> true
Try find local users(and now i have strange behavior of my enum):
=> User.where("status <> ?", User.statuses[:local])
=> User Load (0.9ms) SELECT "users".* FROM "users" WHERE (status <> 0)
=> []
WTF?
=> User.where("status <> ?", User.statuses[:tourist])
=> User Load (0.9ms) SELECT "users".* FROM "users" WHERE (status <> 1)
=> #<User id: "53621ec84d6163124", email: "ffoo#mai.com", status: 0, ......>
So problem, this query User.where("status <> ?", User.statuses[:local]) should return my local user. Or i doing something wrong?
You are using a wrong operator here.
"status <> ?" equals "status != ?"
Instead you want to use
"status = ?"
Postgres comparison operators
class Event < ActiveRecord::Base
has_many :meetings
end
class Meeting < ActiveRecord::Base
belongs_to :event
end
How to write mysql query to search all events group_by meeting DATE(start_at)?
Event.inludes(:meetings).group ...
As a result I want to get a Hash:
{"2014-01-24"=>[#<Event id: , title: "First", created_at: "2014-01-24 16:02:52", updated_at: "2014-01-24 16:02:52">, #<Event id: 2, title: "Second", created_at: "2014-01-24 16:02:52", updated_at: "2014-01-24 16:02:52">], "2013-01-29"=>[#<Event id: 3, title: "Third", created_at: "2013-01-29 05:30:40", updated_at: "2014-01-29 05:30:40">], ...]}
P.S: I am using PostgreSQL
Now I get it by this way:
hash = {}
Meeting.where("extract(month from start_at) = ?", Date.today.month).pluck('DATE(start_at)').uniq.each do |date|
hash[date] = Event.includes(:meetings).where("DATE(meetings.start_at) = ?", date).references(:meetings)
end
But it produced so many queries to the database :(
Event.joins(:meetings).group('meetings.start_at') should do. But want you want is a group_by array method http://apidock.com/ruby/Enumerable/group_by so what you should do is
#events.group_by {|e| e.meeting.start_date}
In case of many to many you should be better off with
result = Hash.new
Meeting.include(:events).each {|m| result[m.start_at]||=[]; result[m.start_at] << m.events}
and with one liner you could
Meeting.includes(:events).inject(Hash.new) do |result, m|
result[m.start_at]||=[]
result[m.start_at] << w.events
result
end
This code should execute two database calls i think
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.