Rails: include related object in JSON output - ruby-on-rails

I have a note class that belongs to a user (ie a user can create many notes).
clip from my notes controller
class NotesController < ApplicationController
before_filter :authenticate_user!
respond_to :html, :xml, :json
# GET /notes
# GET /notes.xml
def index
#notes = Note.includes(:user).order("created_at DESC")
respond_with #notes
end
When I ask for the index in json results for example /notes.json, it returns the notes but only returns user_id for the user object. I would like it to also include user.username (and would be curious how to have the whole user object embedded).
Bonus question: I could not find a way to make the column show as author_id and have it relate back to user. If this is easy to do, how do you do it?

I'm not sure the new respond_to/respond_with style is flexible enough to do this. It very well may be, but as far as I understand, it's meant to simplify only the simplest cases.
You can achieve what you are trying to do with the old-style respond_to with a block, however, by passing parameters to to_json. Try something like this:
class NotesController < ApplicationController
def index
#notes = Note.order("created_at desc")
respond_to do |format|
format.json do
render :json => #notes.to_json(:include => { :user => { :only => :username } })
end
end
end
end

You can also use Jbuilder(https://github.com/rails/jbuilder) to response with data very flexible.
#notes = Note.includes(:user).order("created_at DESC")
and in your index.json.jbuilder file, you can
json.extract! #note
json.username #note.user.username

Would it be possible to do it the other way around?
def index
#user = User.includes(:notes).order("created_at DESC")
respond_with #user
end
It would be expensive to include user objects each time the #notes is iterated.

Related

How to render json for all actions from the after_action filter in ApplicationController?

Is it possible to create an after_filter method in the Rails ApplicationController that runs on every action and renders to JSON? I'm scaffolding out an API, and I'd like to render output to JSON for every action in the controller.
clients_controller.rb
def index
#response = Client.all
end
application_controller.rb
...
after_action :render_json
def render_json
render json: #response
end
The after_action is never executed, and the code aborts with:
Template is missing. Missing template clients/index, ...
If the render json: #response is moved into the controller action, it works correctly.
Is there a filter that will allow me to DRY up the controllers and move the render calls to the base controller?
You can't render after_action/ after_filter. The callback after_action is for doing stuff after rendering. So rendering in after_action is too late.
But your exception is just because you miss the JSON template. I recommend using RABL (which offers a lot of flexibility to your JSON responses and there is also a Railscast about it). Then your controller could look like:
class ClientsController < ApplicationController
def index
#clients = Client.all
end
def show
#client = Client.find params[:id]
end
end
And don't forget to create your rabl templates.
e.g. clients/index.rabl:
collection #clients, :object_root => false
attributes :id
node(:fancy_client_name) { |attribute| attribute.client_method_generating_a_fancy_name }
But in the case you still want to be more declarative you can take advantage of the ActionController::MimeResponds.respond_to like:
class ClientsController < ApplicationController
respond_to :json, :html
def index
#clients = Client.all
respond_with(#clients)
end
def show
#client = Client.find params[:id]
respond_with(#client)
end
end
Btw. beware if you put code in an after_action, this will delay the whole request.

Change ruby on rails controller to respond differently based on route nesting

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

Rails: overriding as_json for dynamic value -- is there a smarter way?

I want to output a list of affiliate links, each tagged to identify the current user. It would be simple in HTML, but we're writing an API, so the output is JSON.
I have it working, but it seems overly complicated. Is this the best approach?
My model, AffiliateLink contains a field (the raw HTML of the link) that I'll transform and output on the fly by adding a token. I have a model method that produces the replacement -- it is non-trivial because we use multiple affiliates and each has a special transformation rule that this method knows about:
def link_with_token(user_token)
# some gnarly code that depends on a lot of stuff the model knows
# that returns a proper link
end
To get my correct link html in JSON I have done these things:
add attr_accessor :link_html to model
add an instance method to set the new accessor
...
def set_link_html(token)
self.link_html = link_with_tracking_token(token)
end
override as_json in the model, replacing the original html_code with link_html
...
def as_json(options = {})
super(:methods => :link_html, :except => :html_code)
end
iterate over the collection returned in the controller method to do the transformation
...
def index
#links = Admin::AffiliateLink.all # TODO, pagination, etc.
respond_to do |format|
format.html # index.html.erb
format.json do
#links.each do |link|
link.set_link_html(account_tracking_token)
end
render json: #links
end
end
end
This seems like a lot of stuff to do just to get my teensy-weensy transformation done. Helpful suggestions (relating to this problem and not to other aspects of the code, which is in flux now) are welcome.
1) A quick solution to your problem (as demonstrated here):
affiliate_links_controller.rb
def index
#links = Admin::AffiliateLink.all # TODO, pagination, etc.
respond_to do |format|
format.html # index.html.erb
format.json do
render json: #links.to_json(:account_tracking_token => account_tracking_token)
end
end
end
AffiliateLink.rb
# I advocate reverse_merge so passed-in options overwrite defaults when option
# keys match.
def as_json(options = {})
json = super(options.reverse_merge(:except => :html_code))
json[:link_with_token] = link_with_token(options[:account_tracking_token])
json
end
2) A more hardcore solution, if you're really writing an API:
See this article describing your problem.
See the gem that the authors made as a solution.
See this railscast on using the gem.
3) And lastly, the convenient solution. If you have a convenient model relation, this is clean:
Pretending AffiliateLink belongs_to :user. And assuming user_token is an accessible attribute of User.
AffiliateLink.rb
# have access to user.user_token via relation
def link_with_token
# some gnarly code that depends on a lot of stuff the model knows
# that returns a proper link
end
def as_json(options = {})
super(options.reverse_merge(:methods => :link_with_token, :except => :html_code))
end
affiliate_links_controller.rb
def index
#links = Admin::AffiliateLink.all # TODO, pagination, etc.
respond_to do |format|
format.html # index.html.erb
format.json do
render json: #links
end
end
end

Cannot access attr_accessor defined variables

I am using Thinking Sphinx to run searches and I get the appropriate ActiveRecord Models fine. The problem is, I want to create an appropriate link path and text on each model, then send the info to the browser in the form of JSON, via AJAX. I am using the following to build those link attributes:
In the controller:
class FindController < ApplicationController
def tag_results
#results = ThinkingSphinx.search(params[:terms])
#results.each do |result|
result.build_ajax_response
end
respond_to do |format|
format.html
format.json { render :json => #results }
end
end
end
In the model:
class TaggedItem < ActiveRecord::Base
attr_accessible :name
attr_accessor :search_link, :search_text
def build_ajax_response
self.search_link = Rails.application.routes.url_helpers.tagged_item_path(self.id)
self.search_text = self.name
end
end
The resulting json object doesn't have either of the search_* attributes listed, much less have a value for them. I've tried using #search_link as well as just search_link in the build_ajax_response method.
Am I doing this wrong? Could there be something else interfering?
Rails' default to_json doesn't know about those extra non active record attributes you've added. The easiest possible thing is probably to specify them as extra methods to include:
format.json { render :json => #results.to_json(:methods => [:search_link, :search_text]) }

Is there any way to order the results of a Cancan accessible_by call

I am using the Cancan accessible_by to retrieve a ActiveRecord::Relation result (example code below). Is there any way to order the results during the accessible_by call?
UPDATE: Srdjan's was correct. #attributes was already being set using accessible_by. I have updated the example to show the sort by User's login. Attribute has a belongs_to relationship with User.
class AttributesController < ApplicationController
load_and_authorize_resource
def index
#attributes = #attributes.includes(:user).order("#{User.table_name}.login")
end
# GET /attribute/1
# GET /attribute/1.xml
def show
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #attribute }
end
end
end
From the horse's mouth, as it were.
#articles = Article.accessible_by(current_ability, :update)
This is an Active Record scope so other scopes and pagination can be chained onto it.
Source: https://github.com/ryanb/cancan/wiki/Fetching-Records
Also, on the top of that page, you'll note that as of CanCan 1.4, this is done automatically when you call load_resource. Are you on that version?

Resources