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
Related
I'm trying to build a little todo app with rails 4 and react. Nothing really hard, it's just a beginning to learn how to use react with rails, but I have difficulties on how to code my rails controllers.
The two urls reachable at the moment are "mysite.local" to display all the todos, and to "mysite.local/todos/1" to display a specific todo with more details.
I have a Todos controller that I use to respond to the AJAX calls related to the todos.
The view that contains the react app is not a "Todos" view because I will add more than just todos in the futur and the react app will have to handle everything.
What I want to do is simple: I want rails to always render the view with the react app regardless of the url used to reach the website. All the controllers in my app (like the Todos controller) are just used to retrieve JSON datas that will be manipulated by react.
The solution I found is to put the react app in the application layout. That way, it will always be present when someone go on the website for the first time. Since it's in the layout it will not be rendered again, the react app can do its job.
My controllers then looks like this:
class TodosController < ApplicationController
def index
respond_to do |format|
format.html
format.json { Todo.all }
end
end
def create
respond_to do |format|
format.html
format.json { Todo.create(todo_params) }
end
end
def show
respond_to do |format|
format.html
format.json { Todo.find(params[:id]) }
end
end
private
def todo_params
params.require(:todo).permit(:content)
end
end
With a controller like this, I can make my AJAX calls and get a JSON, which is the only datas my controller will send, and at the same time I can still reach "mysite.local/todos/1" and rails will do nothing except rendering an empty view.
This method works, but I don't like it because the controller render a view for each action. When someone reach the website for the first time, an empty view will be rendered. Even if the view is empty and it will not display anything on the browser, rails still have to do all the process to render the view. I don't know if this process really cost something, but I don't like the fact that my application is doing something useless.
Is there any way to tell rails to literally do nothing if the format action is html? (= not rendering the "index", or "show" view?)
Or is there a better way to do what I want to do?
Thanks
EDIT
Thanks to gobluego, I modified my application a little. I created a Front controller to handle the client part. Then I moved my Todos controller in an api folder.
Here is my routes.rb file now:
root "front#index"
namespace :api, constaints: { format: 'json' } do
resources :todos, only: [:index, :create, :delete, :show]
end
get '*path' => "front#index", via: :all
and my new Todos controller:
class Api::TodosController < ApplicationController
respond_to :json
before_action :ensure_json_request
def index
respond_with Todo.all
end
def create
respond_with Todo.create(todo_params)
end
def show
respond_with Todo.find(params[:id])
end
private
def todo_params
params.require(:todo).permit(:content)
end
def ensure_json_request
return if request.format == :json
render :nothing => true, :status => 406
end
end
That way, any url is handled by front#index, except all the apis urls, which is what I want. To ensure that nothing is rendered if, for example, someone tries to reach mysite.local/api/todos in the browser, a before_action is used and it render nothing if the format is not json.
Why not use pages#index instead as the root path? That way, TodosController is only responsible for being the back end service for your app.
As an additional measure you can enforce that both requests and responses are only of JSON format. As for the implementation of that, this is a a good starting point.
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
Is it possible to have a controller that interacts in a standard way at both the top level and also the nested level? Or will static routes need to be configured?
When I visit the first address /list/:list_id/items I want it to follow the nested_index method to display only a subset of the listed items (The items that belong to the list).
http://localhost:3000/list/:list_id/items
When I visit the below (/items) address I want it to show the whole list of items.
http://localhost:3000/items
/app/controllers/items_controller.rb
def index
#Item = Item.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #Item }
end
end
def nested_index
#list = List.find(params[:list_id])
#items = #list.items.paginate(page: params[:page], per_page: 5)
respond_to do |format|
format.html # index.html.erb
format.json { render json: #list }
end
end
/config/routes.rb
AppName::Application.routes.draw do
resources :list do
resources :items
end
end
# Do I need to add further routes here?
Personally, I think you should split this out into two separate controllers.
The index method of your controller should be designed to do just one thing. In the case of your nested route it should be fetching all the items appropriate for the selected list and passing them to the appropriate view. In the other instance it is fetching all items and (probably) passing them to a completely different view.
It seems you're trying to get one controller to do the job of two, simply for the sake of the controller's name.
I'd suggest creating an apps_controller and use that to collect all your items and display them, and leave your items_controller for its nested use.
Remember you don't need to name a controller after the model it interacts with ... rather, you should name it after the function it is responsible for. A controller which receives an activation code for a user account might update an is_active boolean on a User model, but you would call this controller Activations since that is what it does.
If you have lots of overlap between controllers you can move their code into modules and then include those modules in both controllers. This way you can DRY up your code whilst keeping the logic separate where necessary.
Take a look at these links for some ideas on code extraction:
http://railscasts.com/episodes/398-service-objects
http://railscasts.com/episodes/416-form-objects
But before you start refactoring all of your code into modules ... consider whether it adds anything to your codebase. Does it make things simpler? Does it make things more readable? Does it save you anything other than typing out a few more lines? If there's no benefit to refactoring ... just don't do it.
#Jon is right. This should be split into several different controllers:
# app/controllers/items_controller.rb
class ItemsController < ApplicationController
# default RESTful actions to operate on lists, for example #index
def index
#Item = Item.all
respond_to do |format|
format.html
format.json { render json: #item }
end
end
end
# app/controllers/lists_controller.rb
class ListsController < ApplicationController
# default RESTful actions to operate on lists
end
# app/controllers/lists/items_controllers.rb
class Lists::ItemsController < ApplicationController
def show
#list = List.find(params[:list_id])
#items = #list.items.paginate(page: params[:page], per_page: 5)
respond_to do |format|
format.html
format.json { render json: #items }
end
end
end
Routes:
AppName::Application.routes.draw do
resources :items
resources :lists do
resources :items
end
end
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
I am working on adding authorizations to an app I am building and I have a question. I have added an :admin column to my User table and set it as a boolean. In my controller I have added this code:
class ShipsController < ApplicationController
def index
ships = Ship.all
#ships = ships.sort_by { |v| [v[:empire_image], v[:cost]] }
if current_user.admin == true
respond_to do |format|
format.html # index.html.erb
format.json { render json: #ships }
end
else
respond_to do |format|
format.html { redirect_to root_path }
end
end
end
It looks like I will have to add this to all of my actions and this seems wrong. My question is, is doing it this way insecure or just more work for myself but fine.
Also I am using the authentication from railstutorial.org and am wondering if a library like cancan would work well with that.
Thanks for your time,
Nick
This way is not insecure, it's just clutter your controllers, at least consider to use a before_filter to authorize your actions.
Maybe for a simple application use a 3rd party authorization gem could seems overkill but move the authorization rules in a single place is a very good thing (the ability.rb file in the case of CanCan).
You can use CanCan with that authentication system, CanCan just expects a current_user method to exist in the controller.