Rails: Passing an Argument to a Concern - ruby-on-rails

DHH wrote an article advocating for the use of concerns. It seems like a good practice, and in a lot of cases, they work well with my app. There are several cases, however, where multiple models have similar but slightly different methods, such as:
def find_or_create_membership
user_membership = User::Membership.where(:group_id => self.group_id,
:user_id => self.invitee_id).first_or_create(:status => "invited")
end
and:
def find_or_create_membership
user_membership = User::Membership.where(:group_id => self.group_id,
:user_id => self.invitee_id).first_or_create(:status => "declined")
end
These methods are identical save that the first sets status to "invited" and the second to "declined". Is there a way I could pass an argument to these methods via a concern?

You might be interested in Paramix.
Never used it myself, though. Dunno, smells like a False-Good-Idea©.

Related

Testing an association model helper method rails rspec

I have two models, User and Account.
# account.rb
belongs_to :user
# user.rb
has_one :account
Account has an attribute name. And in my views, I was calling current_user.account.name multiple times, and I heard that's not the great of a way to do it. So I was incredibly swift, and I created the following method in my user.rb
def account_name
self.account.name
end
So now in my view, I can simply call current_user.account_name, and if the association changes, I only update it in one place. BUT my question is, do I test this method? If I do, how do I test it without any mystery guests?
I agree there is nothing wrong with current_user.account.name - while Sandi Metz would tell us "User knows too much about Account" this is kind of the thing you can't really avoid w/ Active Record.
If you found you were doing a lot of these methods all over the User model you could use the rails delegate method:
delegate :name, :to => :account, :prefix => true
using the :prefix => true option will prefix the method in the User model so it is account_name. In this case I would assume you could write a very simple unit test on the method that it returns something just incase the attribute in account would ever change your test would fail so you would know you need to update the delegate method.
There's nothing wrong with current_user.account.name
There's no difference between calling it as current_user.account.name, or making current_user.account_name call it for you
You're probably not calling current_user in the model, like you say
You should have a spec for it if you use it
Personally I see no good reason for any of this. Just use current_user.account.name.
If you are worrying about efficiency, have current_user return a user that joins account.
This is going to be a bit off-topic. So, apologies in advance if it's not interesting or helpful.
TL;DR: Don't put knowledge of your models in your views. Keep your controllers skinny. Here's how I've been doing it.
In my current project, I've been working to make sure my views have absolutely no knowledge of anything about the rest of the system (to reduce coupling). This way, if you decide to change how you implement something (say, current_user.account.name versus current_user.account_name), then you don't have to go into your views and make changes.
Every controller action provides a #results hash that contains everything the view needs to render correctly. The structure of the #results hash is essentially a contract between the view and the controller.
So, in my controller, #results might look something like {current_user: {account: {name: 'foo'}}}. And in my view, I'd do something like #results[:current_user][:account][:name]. I like using a HashWithIndifferentAccess so I could also do #results['current_user']['account']['name'] and not have things blow up or misbehave.
Also, I've been moving as much logic as I can out of controllers into service objects (I call them 'managers'). I find my managers (which are POROs) a lot easier to test than controllers. So, I might have:
# app/controllers/some_controller.rb
class SomeController
def create
#results = SomeManager.create(params)
if #results[:success]
# happy routing
else
# sad routing
end
end
end
Now, my controllers are super skinny and contain no logic other than routing. They don't know anything about my models. (In fact, almost all of my controller actions look exactly the same with essentially the same six lines of code.) Again, I like this because it creates separation.
Naturally, I need the manager:
#app/managers/some_manager.rb
class SomeManager
class << self
def create(params)
# do stuff that ends up creating the #results hash
# if things went well, the return will include success: true
# if things did not go well, the return will not include a :success key
end
end
end
So, in truth, the structure of #results is a contract between the view and the manager, not between the view and the controller.

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.

How do I create dynamic definitions for state machine based on user defined data in DB

I am trying to write an application that will allow users to manage workflows using the State Machine gem but I am not sure how to proceed in allowing users to define their own state machines using the State Machine gem for ruby.
In the dynamic definitions portion of the gem documentation it says that I should be able to do this by replacing code like this below with a data source.
def transitions
[
{:parked => :idling, :on => :ignite},
{:idling => :first_gear, :first_gear => :second_gear, :on => :shift_up}
# ...
]
end
I am not sure how to go about doing this. how do I define the transistions from a database?
Because transitions is just a method, you could implement this any way you want. Here's one possible way.
I'll assume you're using ActiveRecord.
Define a Transition model and an associated transitions table with to, from, and on columns, all strings. Then you can start defining transitions, e.g.:
Transition.create(:from => "parked", :to => "idling", :on => "ignite")
Then in your transitions method:
def transitions
transitions_data = []
Transition.all.each do |transition|
transitions_data << { transition.from.to_sym => transition.to.to_sym, :on => transition.on.to_sym }
end
transitions_data
end
You can then use the other code in the documentation you linked to create a state machine dynamically.
This is just one example, and could be much further optimized. I'll leave that part to you. Hopefully this will give you a good start.

How do I apply the Law of Demeter to this?

I have an admittedly ugly query to do, to find a particular role related to the current role. This line produces the correct result:
#person_event_role.event_role.event.event_roles.
joins(:mission_role).where(:mission_roles => {:title => 'Boss'}).
first.person_event_roles.first.person
(You can infer the associations from the plurality of those calls)
The only way to get this information requires a ton of knowledge of the structure of the database, but to remove the coupling... It would require filling in a bunch of helper functions in each step of that chain to give back the needed info...
I think the thing to do here is to create the helper functions where appropriate. I'm unclear what the beginning of your association chain is here, but I'd probably assign it a method #event that returns event_role.event. From there, an event has an #boss_role, or whatever makes sense semantically, and that method is
event_roles.joins(:mission_role).where(:mission_roles => {:title => 'Boss'}).first
Finally, also on the Event model, there's a #boss method, which gets
boss_roles.first.person_event_roles.first.person
So, your original query becomes
#person_event_role.event.boss
Each leg of the chain is then self-contained and easy to understand, and it doesn't require the beginning of your chain to be omniscient about the end of it. I don't fully comprehend the full reach of these associations, but I'm pretty sure that just breaking it into three or four model methods will give you the clean reading and separation of concerns you're looking for. You might even break it down further for additional ease of reading, but that becomes a question of style.
Hope that helps!
The following is by the original questioner
I think I followed this advice and ended up with:
#person_event_role.get_related_event_roles_for('Boss').first.filled_by.first
#person_event_role:
def get_related_event_roles_for(role)
event.event_roles_for(role)
end
def event
event_role.event
end
#event:
def event_roles_for(role)
event_roles.for_role(role)
end
#event_role:
scope :for_role, lambda {|role| joins(:mission_role).where(:mission_roles => {:title => role})}
def filled_by
person_event_roles.collect {|per| per.person}
end

Ensuring APIs are separate

TL;DR: Crafting an API. Need different fields for different versions. Teach me, wise ones.
I'm currently trying to figure out the best way to craft a versioned API. That is to say, I wish to have a URL of /api/v1/projects.json that would show a list of projects with a bunch of fields and api/v2/projects.json to show a list of projects with separate fields.
I've been thinking about this problem for about 15 minutes which probably means it's all wrong. At the moment I've got this in my app/models/project.rb file:
def self.api_fields
{
:v1 => ["name"],
:v2 => ["name", "tickets_count"]
}
end
Then I can use this in my API controllers (api/v1/projects_controller.rb) like this:
def index
respond_with(Project.all(:select => Project.api_fields[:v1]))
end
This is great and works as I'd like it to, but there's probably a better way about it. That's your task! Share with me your mountains of API-crafting wisdom.
Bonus points if you come up with a solution that will also allow me to use methods for instances of a model's object, such as a tickets_count method on a Project method.
I'm agree with polarblau that you should have multiple controllers for different version of the API. So, I aim for the bonus point of this question.
I think to archive the ability to call #tickets_count, you have to override #as_json and #to_xml methods of the model. I think you'll have to do it like this:
api/v1/projects_controller.rb
def index
respond_with Project.all, :api_version => :v1
end
project.rb
class Project < ActiveRecord::Base
API_FIELDS = {
:v1 => { :only => [:name] },
:v2 => { :only => [:name], :methods => [:tickets_count] }
}
def as_json(options = {})
options.merge! API_FIELDS[options[:api_version]]
super
end
def to_xml(options = {}, &block)
options.merge! API_FIELDS[options[:api_version]]
super
end
end
However, if you don't mind the mess in the controller, I think specifying :only and :methods in respond_with call in the controller might be a good idea too, as you don't have to override those #as_json and #to_xml methods.
Just as a comment:
Have you had a look a these yet?
http://devoh.com/posts/2010/04/simple-api-versioning-in-rails
Best practices for API versioning?
devoh.com suggest to split the versions already at a routing level, which seems like a good idea:
map.namespace(:v1) do |v1|
v1.resources :categories
v1.resources :products
end
map.namespace(:v2) do |v2|
v2.resources :categories, :has_many => :products
end
Then you could use different controllers to return the different fields.
The problem is, as you know, that whatever you expose allows the end-client to create a direct dependency. Having said that, if you directly expose your models to the world, e.g. http://domain.com/products.json, whenever you change your Products model you have a limited number of options:
The end-client must live with it and behave much like a "schemaless database". You say that it's going to change, and voila it's done (reads clients will have to deal with it)!
You add a more enterprise-like versioning to your API. That's meas that at a more advanced level what you expose to the end-client are not your models. Instead you expose public objects, which in turn can be versioned. This is called a Data Transfer Object (http://en.wikipedia.org/wiki/Data_transfer_object)
If we wished to pursue the 2nd approach we could do the following:
class Project < ActiveRecord::Base
end
class PublicProject
def to_json(version = API_VERSION)
self.send("load_#{version}_project").to_json
end
private
def load_v1_project
project = load_v2_project
# logic that transforms a current project in a project that v1 users can understand
end
def load_v2_project
Project.find...
end
end
Hope it helps.
Mount a Sinatra app in routes at /api/v1 to handle your API calls. Makes it easier to add a new API and still be backwards compatible until you deprecate it.

Resources