Querying a Model Using an Array in Rails - ruby-on-rails-4

I have two models, Question and Answer.
class Answer < ActiveRecord::Base
belongs_to :question
end
class Question < ActiveRecord::Base
has_many :answers
end
In my controller action, what I want to do is to determine all of the questions that a user has answered. I have a query that finds all of the answers for a user:
#answers = current_user.answers
Now, I want to find out what Questions those relate to. I tried
#questions = Question.where("id in ?", #answers)
but it doesn't work. I get this error:
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '4,5,6)' at line 1: SELECT `questions`.* FROM `questions` WHERE (id in 4,5,6)
When I try this:
#questions = Question.where("id in ?", #answers.question_id)
I get this error (question_id is a field in Answer):
undefined method `question_id' for [4, 5, 6]:Array
How can I best query Questions based on Answers a User has?

Question.where(id: #answers.map(&:question_id))

You can approach the problem from a different angle. You could have a custom scope in your Question model:
scope :by_answerer, -> (user_id) { includes(:answers).where(answers: {user_id: user_id}) }
Then in your User model:
def answered_questions
Question.by_answerer(id)
end

The clue is in the SQL that is generated (in the error message)
SELECT `questions`.* FROM `questions` WHERE (id in 4,5,6)
that's not valid syntax. You want
SELECT `questions`.* FROM `questions` WHERE (id in (4,5,6))
so you're looking for
#questions = Question.where("id in (?)", #answers)

Related

NoMethodError - new since upgrading to Rails 4

I'm at my wit's end. I upgraded to Rails 4.2.10, and everything is terrible.
Here is the relevant part of /models/product.rb
class Product < ActiveRecord::Base
delegate_attributes :price, :is_master, :to => :master
And here is /models/variant.rb:
class Variant < ActiveRecord::Base
belongs_to :product
The variants table has fields for "price" and "is_master". Products table does not.
It used to be the case that one could access Product.price and it would get/set the price for the master variant (there's really only one variant per product, the way things are currently set up).
Now it complains that:
NoMethodError: undefined method `price=' for #<Product:0x0000000d63b980>
It's true. There's no method called price=. But why wasn't this an issue before, and what on earth should I put in that method if I create it?
Here's the code to generate a product in db/seeds.rb:
product = Product.create!({
name: "Product_#{i}",
description: Faker::Lorem.sentence,
store_id: u.store.id,
master_attributes: {
listing_folder_id: uuids[i],
version_folder_id: uuids[i]
}
})
product.price = 10
product.save!
end
delegate_attributes isn't a Rails method and looks like it comes from a gem (or gems) that aren't actively maintained?
If there's a new version of whatever gem you're using that might help, because the short answer is that part of the "delegating" of an attribute would involve getting and setting the attribute, so it would generate #price= for you.
If you want to define it yourself, this should do it (within your Product class):
def price=(*args)
master.price=(*args)
end
or if you want to be more explicit:
def price=(amount)
master.price = amount
end

Rails 4 Has_Many With Finder_SQL Deprecated

I am upgrading a Rails app to 4.0. I am receiving the following deprecation warning. I have Google'd on this, but have not found anything that tells how to change this.
DEPRECATION WARNING: The :finder_sql association option is deprecated. Please find an alternative (such as using scopes)...
Here is the scope that is causing the warning:
has_many :elective_instructors,
:class_name => "Instructor",
:finder_sql => proc { "SELECT DISTINCT people.* FROM people
INNER JOIN class_sections ON class_sections.instructor_id = people.id
INNER JOIN courses ON courses.id = class_sections.course_id
INNER JOIN taken_classes ON class_sections.id = taken_classes.class_section_id
WHERE
courses.core = FALSE
AND
taken_classes.student_id = #{id}
AND
people.type = 'Instructor'
AND
people.ignore = FALSE" }
Any ideas would be greatly appreciated. Thanks!
As of 4.1, :finder_sql is not just deprecated - it has been completely REMOVED from Rails.
Here is one way to do something similar through the use of scopes. Let's say we have a User class and Job class (so a user can have many jobs). And let's say that we want to find all distinct jobs that this user holds (contrived example, but it illustrates the point) and let's say we want to use custom SQL for this. We can use find_by_sql as follows
class Job < ActiveRecord::Base
scope :distinct_user_jobs, -> (user_id){ find_by_sql(["SELECT DISTINCT jobs.* FROM jobs WHERE jobs.user_id=?", user_id]) }
end
And then pass in the user_id
user = User.first
Job.distinct_user_jobs(user.id)

Eager Loading Multiple Associations and serialization

In my project I have 3 models Assignment, Question and MultipleChoice with the following associations
assignment.rb
has_many :questions, dependent: :destroy
question.rb
belongs_to :assignment, class_name: 'Assignment', foreign_key: :assignment_id
has_many :multiple_choices, dependent: :destroy
multiple_choice.rb
belongs_to :question
Now I want to make a query like below
#assignment = Assignment.find(params[:id])
#questions = #assignment.questions.includes(:multiple_choices)
This is not working as expected.
So, I want all questions that belongs to the assignment for the params[:id] and the associated multiple choices that belongs to a question. My above query do not give any error but it only show questions not multiple choices associated with question. How can I do this? I am learning api development for rails. So I want to send this value as json and probably I will need serialization. How can I do this? I am working on rails 4.
Edit
well the output for
#questions = #assignment.questions.includes(:multiple_choices) and
#questions = #assignment.questions.eager_load(:multiple_choices) and
#questions = #assignment.questions are all same.
I dont understand why the output do not include any value from multiple choices table
Output of the command
#assignment.questions.eager_load(:multiple_choices).to_sql id
=> "SELECT \"questions\".\"id\" AS t0_r0, \"questions\".\"content\" AS t0_r1, \"questions\".\"q_type\" AS t0_r2, \"quest
ions\".\"created_at\" AS t0_r3, \"questions\".\"updated_at\" AS t0_r4, \"questions\".\"assignment_id\" AS t0_r5, \"multi
ple_choices\".\"id\" AS t1_r0, \"multiple_choices\".\"content\" AS t1_r1, \"multiple_choices\".\"created_at\" AS t1_r2,
\"multiple_choices\".\"updated_at\" AS t1_r3, \"multiple_choices\".\"question_id\" AS t1_r4 FROM \"questions\" LEFT OUTE
R JOIN \"multiple_choices\" ON \"multiple_choices\".\"question_id\" = \"questions\".\"id\" WHERE \"questions\".\"assignm
ent_id\" = $1"
The behaviour for includes function changed in Rails 4. You can find more details here:
http://blog.arkency.com/2013/12/rails4-preloading/
I also suppose, that if you will use eager_load instead of includes, you will get the result you need.
#assignment is an object from which you are getting the questions.
But with #questions = #assignment.questions.includes(:multiple_choices) how can you get the multiple_choices without calling this on an object?
Try this->
#assignment = Assignment.includes(:questions).find(params[:id])
#assignment.questions.includes(:multiple_choices).collect {|question| question.multiple_choices }
This also includes eager loading.
Hope you'll get what you expect.

Rails 4: strong_params,nested_attributes_for and belongs_to association trouble

I really can't get my head around Rails 4 strong parameters, belongs_to association and form with fields_for.
Imagine I have model for quoting some price:
class Quote < ActiveRecord::Base
belongs_to :fee
accepts_nested_attributes_for :fee
Now, I have seeded some fees into the db, and have put some radiobuttons on my form_for #quote using fields_for. The values of the radiobuttons are simply ids of the records.
Here is the troubling part, the controller:
def create
#quote = Quote.new(quote_params)
...
end
def quote_params
params.require(:quote).permit(:amount_from, fee_attributes: [:id])
end
From my understanding, automagically Rails should fetch fee record with some id, but there is some mystic error instead.
params hash is: "quote"=>{"amount_from"=>"1200", "fee_attributes"=>{"id"=>"1"}}
Log tail:
Completed 404 Not Found in 264ms
ActiveRecord::RecordNotFound (Couldn't find Fee with ID=1 for Quote with ID=)
app/controllers/quotes_controller.rb:14:in `create'
I really don't understand what is going on here, have read Rails association guide, googled for hour for all info, but to no avail.
What I want to achieve here is to understand the correct "Rails way" to fetch some associations for new Quote object using some params I've put in the form.
Guess I got nested_attributes_for wrong, somehow thought it would call Fee.find automagically.
I've opted for ditching fields_for helpers from the form and rendering fields manually like
radio_button_tag 'fee[id]', fee.id
Then in controller I have 2 params methods now:
def quote_params
params.require(:quote).permit(:amount_from)
end
def fee_params
params.require(:fee).permit(:id)
end
And my action looks like
def create
#quote = Quote.new(quote_params)
#quote.fee = Fee.find(fee_params[:id])
...
Any additions on best practices when one has to handle lots of different objects with not so straight init logic are welcome.

union between two has_many associations

I use rails 4.1 with Postgres and I have a model like this one:
class User < ActiveRecord::Base
has_many :events
has_many :event_memberships
has_many :shared_events, through: :event_memberships, source: event
def all_events
#... code goes here
end
end
I'd like to do an union between events and shared_events associations but I can't make it work.
The only thing I can make it work is this:
def all_events
events + shared_events
end
But it will execute 2 SQL queries instead of one and I won't be able to order or limit the results.
I have also tried something like this:
def all_events
User.find_by_sql("(#{events.to_sql}) UNION (#{shared_events.to_sql})")
end
Or this method https://coderwall.com/p/9hohaa
But both methods throw this error:
PG::UndefinedParameter: ERROR: there is no parameter $1
That seems to be known bug in rails 4+ and it won't be resolved https://github.com/rails/rails/issues/13686
So my only option here seems to write plain old SQL.
Anyone with a better idea ?