Eager Loading with Rails Query - ruby-on-rails

In my rails app, projects have many steps, and steps can have questions
I'd like to write a method that checks whether a project has any questions and return the id of the step with the question.
Currently, I have the following in my project.rb
def step_with_question
question_step = ""
steps.order(:published_on).each do |step|
if step.question
question_step = step.id
end
end
return question_step
end
But I think this is inefficient and think there is probably a much faster way to do this with eager loading (this creates a query for every step in the project). Does anyone have advice for how to do this?

You can use joins to only return associated :steps which actually have :questions associations with them:
#project = Project.joins(steps: :questions).order('steps.published_on').find(id)
This query will return only the project steps that actually have an associated question. You can now safely loop through the steps records and return or use step.id
#project.steps.each do |step|
question_step = step.id
# do something with the question_step
end

I didn't understand what your code does exactly, but if you want to access a Question, from a Step, you can use the method includes:
project = Project.find(id) # Get a product just to show how it works
# To tell Rails to make a single query when you want to
# access the questions, do something like this:
steps_with_questions = project.steps.includes(:question)
This way, when you try to access a question, it'll be already loaded.
The best way to work with these is to write a scope for step.rb, like this:
scope :with_questions, lambda { includes :questions }
Now you only need to call:
project.steps.with_questions
Making the code a lot easier to read.
EDIT: Your code would look like this: (without the scope I mentioned earlier)
def step_with_question
question_step = ""
steps.order(:published_on).includes(:question).each do |step|
if step.question
question_step = step.id
end
end
return question_step
end

Related

Rails association collection build if not exist

I have three models Exam, User and ExamResult. ExamResult contains the records for all the students (User) for exams (Exam). For one particular Exam record, there should be one record in ExamResult for each student. In the edit method of ExamController, depending if the ExamResult record has been created for one student, I need to build one new record or just skip it. Not sure if this is idiomatic Rails way of doing it.
# ExamController
def edit
User.students.each do |s|
#exam.exam_results.build(user_id: s.id) unless #exam.exam_results.find_by(user_id: s.id)
end
end
or this way:
def edit
newIds = User.students.map(&:id) - #exam.exam_results.map(&:user_id)
newIds.each do |id|
#exam.exam_results.build(user_id: id)
end
end
Maybe neither is idiomatic Rails. Any suggestions are welcome.
Edit
Bring find_or_initialize_by (recommended by #user3334690) on the table. If I understand the doc correctly, this should do the same as previous two implementations.
def edit
User.students.each do |s|
#exam.exam_results.find_or_initialize_by(user_id: s.id)
end
end
Instead of build you can use find_or_create_by_user_id in above case.
def edit
User.students.each do |s|
ExamResult.find_or_create_by_exam_id_and_user_id(exam_id, user_id)
end
end
there is this way:
def edit
User.students.each do |s|
ExamResult.where(exam_id: #exam.id, user_id: s.id).first_or_create
end
end
Use your second example:
def edit
newIds = User.students.map(&:id) - #exam.exam_results.map(&:user_id)
newIds.each do |id|
#exam.exam_results.build(user_id: id)
end
end
As I said in comments above this will query the database twice regardless of the number of students which will scale much better. If you're in a really extreme environment you can write it to use only one query along with "pluck" to pull only the "id" column (and avoid the object creation overhead) like this:
newIds = User.students.where("users.id not in (select user_id from exam_results where exam_id=?)", #exam.id).pluck(:id)
However, the readability is reduced for that. Your original would also benefit from using "pluck" instead of "map".
One other style note - I would use "new_ids" which is the standard idiomatic way of doing it with Rails.

using .dup with multiple records

I am creating a Stripe payment engine for the Catarse project. I have three records I need to copy from my User, who is a project owner to my User's project. But because I'm a beginner, the code looks like S&%#te!
#project_controller.rb
def create
...
check_for_stripe_keys
....
end
def show
...
check_for_stripe_keys
....
end
....
def check_for_stripe_keys
if #project.stripe_userid.nil?
#project.reload
#project.stripe_access_token = #project.user.stripe_access_token.dup
#project.stripe_key = #project.user.stripe_key.dup
#project.stripe_userid = #project.user.stripe_userid.dup
elsif #project.stripe_userid != #project.user.userid
#project.stripe_access_token = #project.user.stripe_access_token.dup
#project.stripe_key = #project.user.stripe_key.dup
#project.stripe_userid = #project.user.stripe_userid.dup
end
#project.save
end
....
I only need those three records because my stripe code is an engine. Three things:
1) Initially I thought to user update_attributes but I don't know if its possible to use .dup in that method.
2) Is it possible to put this in a helper located in the engine thats accessible to the main_app so users don't have to edit the main_app project_controller code?
3) Is there a cleaner way to show the above .dup code? 'Cause it's fugly!
Thanks for your help!
This is a more compact way to write what you already have:
[:stripe_access_token, :stripe_key, :stripe_userid].each do |field|
#project.send("#{field.to_s}=", #project.user.send(field).dup)
end
Maybe something like this:
Gemfile:
gem 'deep_cloneable'
Code:
#user.dup :include => [:stripe_userid, :stripe_access_token, :stripe_key]
I didn't quite follow your relationships. But take a look at this answer: What is the easiest way to duplicate an activerecord record?
Here is deep_cloneable:
https://github.com/moiristo/deep_cloneable

How to write short, clean rspec tests for method with many model calls?

I'm having trouble coming up with some tests for a method I want to write.
The method is going to take a hash of some data and create a bunch of associated models with it. The problem is, I'm having a hard time figuring out what the best practice for writing this sort of test is.
For example, the code will:
Take a hash that looks like:
{
:department => 'CS',
:course_title => 'Algorithms',
:section_number => '01B'
:term => 'Fall 2012',
:instructor => 'Bob Dylan'
}
And save it to the models Department, Course, Section, and Instructor.
This will take many calls to model.find_or_create, etc.
How could I go about testing each separate purpose of this method, e.g.:
it 'should find or create department' do
# << Way too many stubs here for each model and all association calls
dept = mock_model(Department)
Department.should_receive(:find_or_create).with(:name => 'CS').and_return(dept)
end
Is there a way to avoid the massive amounts of stubs to keep each test FIRST (fast independent repeatable self-checking timely) ? Is there a better way to write this method and/or these tests? I'd really prefer to have short, clean it blocks.
Thank you so much for any help.
Edit:
The method will probably look like this:
def handle_course_submission(param_hash)
department = Department.find_or_create(:name => param_hash[:department])
course = Course.find_or_create(:title => param_hash[:course_title])
instructor = Instructor.find_or_create(:name => param_hash[:instructor])
section = Section.find_or_create(:number => param_hash[:section_number], :term => param_hash[:term])
# Maybe put this stuff in a different method?
course.department = department
section.course = course
section.instructor = instructor
end
Is there a better way to write the method? How would I write the tests? Thanks!
For passing an array of sections to be created:
class SectionCreator
# sections is the array of parameters
def initialize(sections)
#sections = sections
end
# Adding the ! here because I think you should use the save! methods
# with exceptions as mentioned in one of my myriad comments.
def create_sections!
#sections.each do |section|
create_section!(section)
end
end
def create_section!(section)
section = find_or_create_section(section[:section_number], section[:term])
section.add_course!(section_params)
end
# The rest of my original example goes here
end
# In your controller or wherever...
def action
SectionCreator.new(params_array).create_sections!
rescue ActiveRecord::RecordInvalid => ex
errors = ex.record.errors
render json: errors
end
Hopefully this covers it all.
My first thought is that you may be suffering from a bigger design flaw. Without seeing the greater context of your method it is hard to give much advice. However, in general it is good to break the method up into smaller pieces and follow the single level of abstraction principle.
http://www.markhneedham.com/blog/2009/06/12/coding-single-level-of-abstraction-principle/
Here is something you could try although as mentioned before this is definitely still not ideal:
def handle_course_submission(param_hash)
department = find_or_create_department(param_hash[:department])
course = find_or_create_course(param_hash[:course_title])
# etc.
# call another method here to perform the actual work
end
private
def find_or_create_department(department)
Department.find_or_create(name: department)
end
def find_or_create_course(course_title)
Course.find_or_create(title: course_title)
end
# Etc.
In the spec...
let(:param_hash) do
{
:department => 'CS',
:course_title => 'Algorithms',
:section_number => '01B'
:term => 'Fall 2012',
:instructor => 'Bob Dylan'
}
end
describe "#save_hash" do
before do
subject.stub(:find_or_create_department).as_null_object
subject.stub(:find_or_create_course).as_null_object
# etc.
end
after do
subject.handle_course_submission(param_hash)
end
it "should save the department" do
subject.should_receive(:find_or_create_department).with(param_hash[:department])
end
it "should save the course title" do
subject.should_receive(:find_or_create_course).with(param_hash[:course_title])
end
# Etc.
end
describe "#find_or_create_department" do
it "should find or create a Department" do
Department.should_receive(:find_or_create).with("Department Name")
subject.find_or_create_department("Department Name")
end
end
# etc. for the rest of the find_or_create methods as well as any other
# methods you add
Hope some of that helped a little. If you post more of your example code I may be able to provide less generalized and possibly useful advice.
Given the new context provided, I would split the functionality up amongst your models a little more. Again, this is really just the first thing that comes to mind and could definitely be improved upon. It seems to me like the Section is the root object here. So you could either add a Section.create_course method or wrap it in a service object like so:
Updated this example to use exceptions
class SectionCreator
def initialize(param_hash)
number = param_hash.delete(:section_number)
term = param_hash.delete(:term)
#section = find_or_create_section(number, term)
#param_hash = param_hash
end
def create!
#section.add_course!(#param_hash)
end
private
def find_or_create_section(number, term)
Section.find_or_create(number: number, term: term)
end
end
class Section < ActiveRecord::Base
# All of your current model stuff here
def add_course!(course_info)
department_name = course_info[:department]
course_title = course_info[:course_title]
instructor_name = param_hash[:instructor]
self.course = find_or_create_course_with_department(course_title, department_name)
self.instructor = find_or_create_instructor(instructor_name)
save!
self
end
def find_or_create_course_with_department(course_title, department_name)
course = find_or_create_course(course_title)
course.department = find_or_create_department(department_name)
course.save!
course
end
def find_or_create_course(course_title)
Course.find_or_create(title: course_title)
end
def find_or_create_department(department_name)
Department.find_or_create(name: department_name)
end
def find_or_create_instructor(instructor_name)
Instructor.find_or_create(name: instructor_name)
end
end
# In your controller (this needs more work but..)
def create_section_action
#section = SectionCreator.new(params).create!
rescue ActiveRecord::RecordInvalid => ex
flash[:alert] = #section.errors
end
Notice how adding the #find_or_create_course_with_department method allowed us to add the association of the department in there while keeping the #add_course method clean. That is why I like to add those methods even though they sometimes seem superflous like in the case of the #find_or_create_instructor method.
The other advantage of breaking out the methods in this fashion is that they become easier to stub in tests as I showed in my first example. You can easily stub all of these methods to make sure the database isn't actually being hit and your tests run fast while at the same time guarantee through the test expectations that the functionality is correct.
Of course, a lot of this comes down to personal preference on how you want to implement it. In this case the service object is probably unnecessary. You could just as easily have implemented that as the Section.create_course method I referenced earlier like so:
class Section < ActiveRecord::Base
def self.create_course(param_hash)
section = find_or_create(number: param_hash.delete(:section_number), term: param_hash.delete(:term))
section.add_course(param_hash)
section
end
# The rest of the model goes here
end
As to your final question, you can definitely stub out methods in RSpec and then apply expectations like should_receive on top of those stubs.
It's getting late so let me know if I missed anything.

Can I make Rails update_attributes with nested form find existing records and add to collections instead of creating new ones?

Scenario: I have a has_many association (Post has many Authors), and I have a nested Post form to accept attributes for Authors.
What I found is that when I call post.update_attributes(params[:post]) where params[:post] is a hash with post and all author attributes to add, there doesn't seem to be a way to ask Rails to only create Authors if certain criteria is met, e.g. the username for the Author already exists. What Rails would do is just failing and rollback update_attributes routine if username has uniqueness validation in the model. If not, then Rails would add a new record Author if one that does not have an id is in the hash.
Now my code for the update action in the Post controller becomes this:
def update
#post = Post.find(params[:id])
# custom code to work around by inspecting the author attributes
# and pre-inserting the association of existing authors into the testrun's author
# collection
params[:post][:authors_attributes].values.each do |author_attribute|
if author_attribute[:id].nil? and author_attribute[:username].present?
existing_author = Author.find_by_username(author_attribute[:username])
if existing_author.present?
author_attribute[:id] = existing_author.id
#testrun.authors << existing_author
end
end
end
if #post.update_attributes(params[:post])
flash[:success] = 'great!'
else
flash[:error] = 'Urgg!'
end
redirect_to ...
end
Are there better ways to handle this that I missed?
EDIT: Thanks for #Robd'Apice who lead me to look into overriding the default authors_attributes= function that accepts_nested_attributes_for inserts into the model on my behalf, I was able to come up with something that is better:
def authors_attributes=(authors_attributes)
authors_attributes.values.each do |author_attributes|
if author_attributes[:id].nil? and author_attributes[:username].present?
author = Radar.find_by_username(radar_attributes[:username])
if author.present?
author_attributes[:id] = author.id
self.authors << author
end
end
end
assign_nested_attributes_for_collection_association(:authors, authors_attributes, mass_assignment_options)
end
But I'm not completely satisfied with it, for one, I'm still mucking the attribute hashes from the caller directly which requires understanding of how the logic works for these hashes (:id set or not set, for instance), and two, I'm calling a function that is not trivial to fit here. It would be nice if there are ways to tell 'accepts_nested_attributes_for' to only create new record when certain condition is not met. The one-to-one association has a :update_only flag that does something similar but this is lacking for one-to-many relationship.
Are there better solutions out there?
This kind of logic probably belongs in your model, not your controller. I'd consider re-writing the author_attributes= method that is created by default for your association.
def authors_attributes=(authors_attributes)
authors_attributes.values.each do |author_attributes|
author_to_update = Author.find_by_id(author_attributes[:id]) || Author.find_by_username(author_attributes[:username]) || self.authors.build
author_to_update.update_attributes(author_attributes)
end
end
I haven't tested that code, but I think that should work.
EDIT: To retain the other functionality of accepts_nested_Attributes_for, you could use super:
def authors_attributes=(authors_attributes)
authors_attributes.each do |key, author_attributes|
authors_attributes[key][:id] = Author.find_by_username(author_attributes[:username]).id if author_attributes[:username] && !author_attributes[:username].present?
end
super(authors_attributes)
end
If that implementation with super doesn't work, you probably have two options: continue with the 'processing' of the attributes hash in the controller (but turn it into a private method of your controller to clean it up a bit), or continue with my first solution by adding in the functionality you've lost from :destroy => true and reject_if with your own code (which wouldn't be too hard to do). I'd probably go with the first option.
I'd suggest using a form object instead of trying to get accepts_nested_attributes to work. I find that form object are often much cleaner and much more flexible. Check out this railscast

I want to map my database lookup tables to a hash, good idea?

I am developing a Rails web application and am confused about how to utilize the lookup table values in my models. Here is an example model from my app:
table name: donations
id
amount
note
user_id
appeal_id
donation_status_id
donation_type_id
is_anonymous
created_at
updated_at
The fields *donation_status_id* and *donation_type_id* refer to lookup tables. So in my code I have several random places where I make calls like this:
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus.find_by_name("completed").id
#do something
end
To my inexperienced eyes, a one-off query to the DonationStatus table seems incredibly wasteful here, but I don't see any other good way to do it. The first idea I thought of was to read all my lookup tables into a hash at application startup and then just query against that when I need to.
But is there a better way to do what I am trying to do? Should I not worry about queries like this?
Thanks!
Since you have two models, you should use ActiveRecord Model Associations when building the models.
class Donation
has_one :donation_status
end
class DonationStatus
belongs_to :donation
end
Then when you do
my_donation = Donation.find(params[:id])
if my_donation.donation_status.status_name == 'complete'
#do something
end
For more information, you may want to read up how rails is doing the model associations http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html Don't worry about performance, rails has taken care of that for you if you follow how the way it should be done
How about putting it in a constant? For example, something like this:
class DonationStatus < ActiveRecord::Base
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id
PENDING_DONATION_ID = DonationStatus.find_by_name("pending").id
# ...
end
class DonationsController < ApplicationController
def some_action
my_donation = Donation.find(params[:id])
if my_donation.donation_status_id == DonationStatus::COMPLETED_DONATION_ID
#do something
end
end
This way, DonationStatus.find_by_name("pending").id gets executed exactly one. I'm assuming, of course, that this table won't change often.
BTW, I learned this trick in Dan Chak's book, Enterprise Rails.
EDIT: I forgot to mention: in practice, I declare constants like this:
COMPLETED_DONATION_ID = DonationStatus.find_by_name("completed").id rescue "Can't find 'completed' in donation_statuses table"
What you could do is add this method to Donation:
# Donation.rb
def completed?
self.donation_status.name == 'completed' ? true : false
end
And then just do my_donation.completed?. If this is called a second time, Rails will look to cache instead of going to the DB.
You could add memcached if you want, or use Rails' caching further, and do:
def completed?
return Rails.cache.fetch("status_#{self.donation_status_id}_complete") do
self.donation_status.name == 'completed' ? true : false
end
end
What that will do is make a hash key called (for example) "status_1_complete" and if it's not defined the first time, will evaluate the block and set the value. Otherwise, it will just return the value. That way, if you had 1,000,000,000 donations and each of them had donation_status 1, it would go directly to the cache. memcached is quite fast and popular.

Resources