This is more a style question than anything.
When writing queries, I always find myself checking if the result of the query is blank, and it seems - I dunno, overly verbose or wrong in some way.
EX.
def some_action
#product = Product.where(:name => params[:name]).first
end
if there is no product with the name = params[:name], I get a nil value that breaks things.
I've taken to then writing something like this
def some_action
product = Product.where(:name -> params[:name])
#product = product if !product.blank?
end
Is there a more succinct way of handling nil and blank values? This becomes more of a headache when things rely on other relationships
EX.
def some_action
#order = Order.where(:id => params[:id]).first
# if order doesn't exist, I get a nil value, and I'll get an error in my app
if !#order.nil?
#products_on_sale = #order.products.where(:on_sale => true).all
end
end
Basically, is there something I haven;t yet learned that makes dealing with nil, blank and potentially view breaking instance variables more efficient?
Thanks
If its just style related, I'd look at Rails' Object#try method or perhaps consider something like andand.
Using your example, try:
def some_action
#order = Order.where(:id => params[:id]).first
#products_on_sale = #order.try(:where, {:onsale => true}).try(:all)
end
or using andand:
def some_action
#order = Order.where(:id => params[:id]).first
#products_on_sale = #order.andand.where(:onsale => true).andand.all
end
Well even if you go around "nil breaking things" in your controller, you'll still have that issue in your views. It is much easier to have one if statement in your controller and redirect view to "not found" page rather than having several ifs in your views.
Alternatively you could add this
protected
def rescue_not_found
render :template => 'application/not_found', :status => :not_found
end
to your application_controller. See more here: https://ariejan.net/2011/10/14/rails-3-customized-exception-handling
Related
I have a rails app where many of the models are editable using best_in_place, so I have a lot of controllers that look partially like this:
before_action :find_objects, except: [:new, :create]
def update
#object.update_attributes(object_params)
respond_to do |format|
format.json { respond_with_bip #object}
end
end
private
def object_params
params.require(:object).permit(:foo, :bar)
end
def find_objects
#object = Object.find(params[:id])
end
How do I move this particular repeated piece into a controller concern, given that the object being updated is going to come in with a particular name in the params hash, and object_params and find_objects should call their proper versions based on the model name? Is there some elegant meta-magic that'll sort this all out?
I think this is a case where your code could be "too DRY". You can certainly accomplish this using meta-magic, but it could make your code confusing in the long run.
If you want to do the meta-magic, one trick is to use params[:controller] to get the name of the model. For example, if you have a PostsController, then:
params[:controller] # => "posts"
params[:controller].classify # => "Post"
Taking this a step further, you could write a generic find_object like this:
def find_object
model_class = params[:controller].classify.constantize
model_instance = model_class.find(params[:id])
instance_variable_set("##{model_class.name.underscore}", model_instance)
end
But as I said at the beginning, I'm not sure I would recommend this amount of abstraction just for the sake of DRY-ing your controller code.
Doing some integration work with another site I've got the unusual requirement of needing to create the layout at runtime.
At the moment I'm having to resort to something like this:
def new
body = render_to_string 'new', :layout => false
page = add_layout(body, db.load_template)
render :text => page
end
This is a bit awkward, I'd rather do something like:
def new
...
render 'new', :layout => db.load_template
end
Is there a cleaner way to do this? Perhaps it's possible to register new layouts at runtime and use the normal syntax?
Ha! I encountered a project that will solve just that. Check out panoramic. It stores rails views in the database instead of the filesystem.
You can extend ActionController::Base (or ApplicationController) with a module and alias_method_chain to make this work.
module Foo
alias_method_chain :render, :dblayout
def render_with_dblayout options = nil, extra_options = {}, &block
if options.include? :dblayout
...
else
render_without_dblayout options, extra_options { yield }
end
end
end
ActionController::Base.send(:include, Foo)
Oddly I'm having a hard time finding good docs about basic error handling in rails. I'd appreciate any good links as well as thoughts on a handling errors in a really basic method like this:
def self.get_record(id)
People.first( :conditions => ["id = ?", id] )
end
1) I could verify that id != nil, and that it's numeric.
2) I could also then verify that a record is found.
Is there anything else?
Are both #1 and #2 recommended practice? In both cases would you simply create a flash message with the error and display it, or is that giving away too much information?
As I'm sure you know, this is just like People.find(id), except that find raises an error.
However, People.find_by_id(id) returns nil if no record is found, which I suspect takes care of all you need. You don't need to check that what you put into ActiveRecord is the correct data type and the like; it handles SQL injection risks, so to check ahead of time would not affect actual behavior.
If we're just looking at the show action, though, there's an even more elegant way: rather than using find_by_id and checking for nil, use find, let an error bubble up, and let the controller catch it with rescue_from. (By default, in production, ActiveRecord::RecordNotFound will be caught and rescued by showing a generic 404, but you can customize this behavior if necessary.)
class UsersController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, :with => :not_found
def show
#user = User.find params[:id]
end
protected
def not_found
flash[:error] = "User not found"
redirect_to users_path
end
end
Code untested, for illustrative purposes only ;)
Don't do flash[:notice]'s insted just say that "Record not found"
The two things required by you can be done as follows:
1) I could verify that id != nil, and that it's numeric.
def self.get_record(id)
People.first( :conditions => ["id = ?", id] ) if id.integer? unless id.blank?
end
2) I could also then verify that a record is found.
def self.get_record(id)
#people = People.first( :conditions => ["id = ?", id] ) if id.integer? unless id.blank?
flash[:notice] = #people.blank? # this will print true/false depending on value in #people
end
Hope it works for you. :D
While writing functional tests for a controller, I came across a scenario where I have a before_filter requesting some information from the database that one of my tests requires. I'm using Factory_girl to generate test data but I want to avoid hitting the database when its not explicitly needed. I'd also like to avoid testing my before_filter method here (I plan to test it in a separate test). As I understand, mocking/stubbing is the way to accomplish this.
My question is, what is the best way to mock/stub this method in this scenario.
My before filter method looks for a site in the db based on a subdomain found in the URL and sets an instance variable to be used in the controller:
#application_controller.rb
def load_site_from_subdomain
#site = Site.first(:conditions => { :subdomain => request.subdomain })
end
My controller that uses this method as a before_filter:
# pages_controller.rb
before_filter :load_site_from_subdomain
def show
#page = #site.pages.find_by_id_or_slug(params[:id]).first
respond_to do |format|
format.html { render_themed_template }
format.xml { render :xml => #page }
end
end
As you can see, it relies on the #site variable to be set (by the before_filter). During testing however, I'd like to have the test assume that #site has been set, and that it has at least 1 associated page (found by #site.pages). I'd like to then test my load_site_from_subdomain method later.
Here is what I have in my test (using Shoulda & Mocha):
context "a GET request to the #show action" do
setup do
#page = Factory(:page)
#site = Factory.build(:site)
# stub out the #page.site method so it doesn't go
# looking in the db for the site record, this is
# used in this test to add a subdomain to the URL
# when requesting the page
#page.stubs(:site).returns(#site)
# this is where I think I should stub the load_site_from_subdomain
# method, so the #site variable will still be set
# in the controller. I'm just not sure how to do that.
#controller.stubs(:load_site_from_subdomain).returns(#site)
#request.host = "#{ #page.site.subdomain }.example.com"
get :show, :id => #page.id
end
should assign_to(:site)
should assign_to(:page)
should respond_with(:success)
end
This leaves me with an error in my test results telling me that #site is nil.
I feel like I'm going about this the wrong way. I know it would be easy to simply just Factory.create the site so it exists in the db, but as I said earlier, I'd like to reduce the db usage to help keep my tests speedy.
Try stubbing out 'Site.first' since it the the setting of the #site var that you need to stub and not the returned var from the before_filter.
The reason why your #site is nil because your load_site_from_subdomain does the value assignment for #site -- it does not return any value hence your stubbing for load_site_from_subdomain simply doesn't assign the value to #site. There are two work-arounds for this:
First way:
Change load_site_from_subdomain to just do a return value:
def load_site_from_subdomain
Site.first(:conditions => { :subdomain => request.subdomain })
end
and then remove the before_filter :load_site_from_subdomain and change your show to:
def show
#site = load_site_from_subdomain
#page = #site.pages.find_by_id_or_slug(params[:id]).first
respond_to do |format|
format.html { render_themed_template }
format.xml { render :xml => #page }
end
end
And then do the stubbing in the test:
#controller.stubs(:load_site_from_subdomain).returns(#site)
that ensure our #site is stubbed indirectly via load_site_from_subdomain
Second way
To stub the Site.first, I do not really like this approach as in functional test, we do not really care about how the model is retrieved but the behaviour of the respond. Anyway, if you feel like going this path, you could stub it out in your test:
Site.stubs(:first).returns(#site)
I have a model, Report, that is polymorphic.
So many itens in my site may have many of it.
And i would like to have a generic controller for posting it.
Its a very simple model, has only a text message and the association.
in my routes, im doing something like
map.resources :users, :has_many => [ :reports ]
map.resources :posts, :has_many => [ :reports ]
but in my reports_controller, i would like to get the relation from with its coming from.
like:
before_filter :get_reportable
def get_reportable
reportable = *reportable_class*.find params[:reportable_id]
end
is this possible?
how could i get the reportable_class and the reportable_id?
I can get the params[:user_id] when it comes from users controller, or params[:post_id] when it comes from posts. I could do a case with all the relations, but it doesnt seem a clean solution at all...
having the polymorphic association would be the best, are there any how?
If you have a single controller that processes requests through two differing paths, then you need to make it aware of the contexts in which it will be called. You often see a lot of code that looks something like this:
before_filter :load_reportable
def load_reportable
if (params[:user_id])
#user = User.find(params[:user_id])
#reportable = #user
elsif (params[:post_id])
#post = Post.find(params[:post_id])
#reportable = #post
end
rescue ActiveRecord::RecordNotFound
render(:partial => 'not_found', :status => :not_found)
return false
end
Since you're using a polymorphic association, you may be able to do something like this instead:
before_filter :load_reportable
def load_reportable
unless (#reportable = #report.reportable)
# No parent record found
render(:partial => 'not_found', :status => :not_found)
return false
end
# Verify that the reportable relationship is expressed properly
# in the path.
if (params[:user_id])
unless (#reportable.to_param == params[:user_id])
render(:partial => 'user_not_found', :status => :not_found)
return false
end
elsif (params[:post_id])
unless (#reportable.to_param == params[:post_id])
render(:partial => 'post_not_found', :status => :not_found)
return false
end
end
end
The trouble with this approach, where you have one controller that serves two entirely different routes, is that generating error messages, such as "user not found" versus "post not found". This can be tricky to get right if you're not inheriting from a Users::BaseController, for instance.
In many cases it's easier to create two independent "reports" controllers, such as users/reports and posts/reports, where any common functionality is imported from a module. These controllers usually inherit from a base controller which performs the loading and error handling. The base controller can also establish layout, page title, etc., without having to re-implement this functionality for each sub-resources controller.
The alternative is to de-couple reports and have it run as its own controller where the relationship to the "reportable" record is mostly irrelevant.
Or try that:
before_filter :get_reportable
def get_reportable
params.each do |name, value|
if name =~ /(.+)_id$/
#reportable = $1.classify.constantize.find(value)
end
end
end
It is going through all the params and tries to find one ending with _id, then grabs that before part and finds relevant record.