I am currently working on a web application built on Rails 3 that heavily uses Ajax/REST for the client side. Thus, I often find myself writing controller actions like this:
def create
if !params[:name]
respond_to do |format|
format.html { render json: {}, status: :not_found }
format.json { render json: {}, status: :not_found }
end
return
end
account = ...
respond_to do |format|
format.html { render json: account }
format.json { render json: account }
end
end
Nearly all of my actions are returning a json object in a success case or an error code. However, I always have to write this verbose respond_to block and a return, if I want the action to return earlier.
Instead I would like to use something like this instead, or a similar alternative:
def create
if !params[:name]
throw :not_found
end
account = ...
return account
end
How can this be done with Rails 3+ ?
Have a look into inherited_resources. This will allow you to rewrite your controller as:
class SomeController < ApplicationController
inherit_resources
respond_to :html, :js, :json
end
That is it. All of your create/read/update/delete methods will be accessible as usual. You can, as I have in the past, inherit from a master resources controller which uses inherited_resources, and then you can tweak the responses in a more general way.
class ResourcesController < ApplicationController
inherit_resources
respond_to :html, :js
def create
create! do |format|
format.js do
# generic code here for managing all create methods initiated via js
# current model is avialbe via 'resource'
# e.g 'resource.errors'
end
end
end
Then simply inherit from that controller:
class SomeController < ResourcesController
end
This abstraction can be overkill for most purposes, but it has come in extremely handy when working 30 or 40 models which all require similar controllers.
Inherited_resources offers many helpers for accessing the current model (referred to as resource) to facilitate dynamic references, so you can, for example, return relevant forms, or partials based on resource/model name.
To give you an idea of how to use this, you could return forms for the current controller by using the controller name in the parameters. Should be noted that malformed controller names will not reach this method (as it will return 404), so it is safe to use:
format.js do
render "#{params[:controller]}/form"
end
Best of all, you can override any of the methods yourself by defining them in a particular controller.
If your are always returning json, you can ommit the respond_to block and write it like :
def create
if !params[:name]
render json: {}, status: :not_found
return
end
account = ...
render json: account
end
Related
I am creating a Rails app to which its users have two ways of interaction.
Through a web interface and through an API (mobile app and other software).
The functions for the web and the api access are the same, for example a user can write a comment via the web interface (views) or through the API.
What I would do now is create all the controllers with views, and then create a namespace /API/ with its own controllers. The problem now is of course that I have to write the function to write write a comment twice. Once in my PostController and once in my API/PostController.
I learned that Rails = DRY, so I guess I am doing something wrong.
How would I make the same functions available for my views and at the same time for my API (JSON response).
And how would the routes and namespaces look like? I think even if I find a way to not repeat myself it would be nice to have API routes like api/v1/...
I've done something similar, both using an API namespaced controller and using a single controller.
Since you seem convinced that you can do everything equally within your Web interface as your API interface it would make sense to merge the two, though note that this is not always true.
If it makes more sense for you to use a single controller, then what you need to do is play with the repond_to format of requests coming to a single controller action.
Ex:
class ArticlesController < ApplicationController
def index
#articles = Article.all
respond_to do |format|
format.html # will render a view by default
format.json { render json: #articles }
end
end
def create
article = current_user.articles.build(article_params)
respond_to do |format|
if article.save
flash[:notice] = "success!"
else
flash[:error] = "uhoh!"
end
format.html # renders a view by default
format.json { render json: { errors: #articles.errors }
end
end
private
def article_params
params.require(:article).permit(:title, :content)
end
end
I have a SwitchesController that inherits from BaseSwitchesController, for the json.
module Api
module V1
class SwitchesController < BaseSwitchesController
respond_to :json
end
end
end
I have another SwitchesController, also inheriting from BaseSwitchesController, for the html.
class SwitchesController < BaseSwitchesController
layout 'application'
end
BaseSwitchesController inherits from ApplicationController.
class BaseSwitchesController < ApplicationController
def update
respond_to do |format|
if #switch.update(switch_params)
format.html { redirect_to #switch, notice: 'Switch was successfully updated.' }
format.json { render json: #switch.as_json.merge(:message => 'set_switch_value') }
else
format.html { render action: 'edit' }
format.json { render json: #switch.errors, status: :unprocessable_entity }
end
end
end
I want to move the json to the SwitchesController in the API module.
The problem is that the methods in SwitchesController are looking for views in views/base_switches, but that folder doesn't exist. It should be looking for views in views/switches, and choose e.g. index.json.rabl.
How can I fix this? Thanks!
I wouldn't personally mix together API controllers with HTML controllers.
I find two scenarios to be standard.
You do not have a traditional API (with it's own restful routes, versioning etc.) and you respond in both html and json. In which case you access the formats with a .format extension on the url or by using request headers. This uses the same views for both formats
You do have a separated API in which case you would not mix the views together and it allows you to use ActiveRecordSerializers, Rabl or whatever templating method you want to use.
Also your example goes against subclassing. Subclasses should be specializations of your superclass and should contain what differentiates them from other subclasses. Your superclass contains specialized behaviour from both subclasses. Since I'm assuming you don't want to respond to json outside of your API controller.
Hope this helps out
It's the first time I'm using this gem and it's driving me crazy with something as simple as authorize the showaction only for the resource owner.
I tried different ways, configuring the controller mapping and actions, but always get the unauthorized message for show, other actions work as they should.
It seems that showis not getting it's way to the ApplicationAuthorizer.
This is how it's configured:
class EnterpriseAuthorizer < ApplicationAuthorizer
# This works
def self.creatable_by?(user)
user.is_enterpriser?
end
# This doesn't
def readable_by?(user)
true # Just for testing
end
end
class EnterprisesController < ApplicationController
authorize_actions_for Enterprise
def show
#enterprise = Enterprise.find(params[:id])
respond_to do |format|
format.html
format.json { render json: #enterprise }
end
end
I have include Authority::UserAbilities in User and include Authority::Abilities in the Enterprise model. And User has_one :enterprise
Any idea? Thinking seriously about rolling back to cancan.
Thanks in advance.
Authority has different ways of checking permissions. For collection-based actions (e.g. new, create, index), you use authorize_actions_for Model.
For instance-based actions (e.g. edit, update, show, delete), you must call authorize_action_for #instance.
Change your code to this and it should work.
class EnterprisesController < ApplicationController
authorize_actions_for Enterprise
def show
#enterprise = Enterprise.find(params[:id])
authorize_action_for #enterprise
respond_to do |format|
format.html
format.json { render json: #enterprise }
end
end
end
If you want a less messy way to do this, put the
#enterprise = Enterprise.find(params[:id])
authorize_action_for #enterprise
into a before filter that's called by each instance action.
I seem to have an authorization hiccup in my Ruby on Rails app. I have been using the following method in my application controller and it has been working beautifully.
def require_owner
obj = instance_variable_get("##{controller_name.singularize.camelize.underscore}") # LineItem becomes #line_item
return true if current_user_is_owner?(obj)
render_error_message("You must be the #{controller_name.singularize.camelize} owner to access this page", root_url)
return false
end
I then filter in the specific controllers by:
before_filter :require_owner, :only => [:destroy, :update, :edit]
I recently created a new controller which has a bit of a different naming convention that seems to be causing a problem. Normally my controllers read messages_controller or posts_controller. In this specific case I named the resource box_wod which generated box_wods_controller.
This is the only controller that seems to be having a problem with this filter so I bet I can tell it is in the naming of it and therefore the application_controller method is not recognizing the owner of the record.
I am not getting an error message but the application is not letting me edit, update or destroy a record because I am not the BoxWod owner. My routes are correct as are my associations and the correct information is getting passed to the box_wod table.
Is there a way to rewrite the application_controller method to recognize the additional underscore in the box_wod resource? Or is this even my problem?
UPDATE:
Here are the three methods in the BoxWodsController:
def edit
#workout_count = Workout.count
#box_wod = BoxWod.find(params[:id])
end
def update
#box_wod = BoxWod.find(params[:id])
respond_to do |format|
if #box_wod.update_attributes(params[:box_wod])
flash[:notice] = 'BoxWod was successfully updated.'
format.html { redirect_to(#box_wod) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #box_wod.errors, :status => :unprocessable_entity }
end
end
end
def destroy
#box_wod = BoxWod.find(params[:id])
#box_wod.destroy
respond_to do |format|
format.html { redirect_to(box_wods_url) }
format.js
end
end
In situations like this, I like to create a controller method that I can override when necessary. For example:
# application_controller.rb
class ApplicationController
def require_owner
obj = instance_variable_get("##{resource_instance_variable_name}")
# Do your authorization stuff
end
private
def resource_instance_variable_name
controller_name.singularize.camelize.underscore
end
end
# box_wods_controller.rb
class BoxWodsController
private
def resource_instance_variable_name
'box_wod' # Or whatever your instance variable is called
end
end
Lastly, please post your BoxWodsController code so we can better diagnose the problem.
It would seem that the #box_wod instance variable is not created until the require_owner method is invoked so current_user_is_owner? is checking a nil value, resulting in it always returning false. Perhaps you need another before_filter to populate the instance variable before require_owner is invoked.
I need to do API version checks and so I've implemented a check in my controller method like:
if request.header["api_version"] != 1
respond_to do |format|
format.json {render :json => expiredMessage}
end
return
end
Works fine, I'm returning in the control statement so Rails doesn't complain about double render. I tried to DRY these up into an application controller method as a before_filter, but then I get the multiple renders complaint from Rails. It seems that the scope has changed such that the 'return' in the control statement no longer returns from the original controller method?
I'm wondering how I can do a render from the application controller method, or if there's a better way how can I implement this functionality? Thanks!
A before filter is called before each controller action, returning from it just allows that action to proceed. I would use exceptions. (following code is untested)
class FrontEndController < ApplicationController
rescue_from Exception do |exception|
if exception.is_a? ExpiredException
respond_to do |format|
format.json {render :json => expiredMessage}
return
end
end
end
before_filter :check_api_version
def check_api_version
raise ExpiredException if request.header["api_version"] != 1
end
end
class ExpiredException < Exception; end