I'm creating an API on my application. I currently overrided the as_json method in my model in order to be able to get attached files as well as logo from Paperclip :
def as_json( options = {} )
super.merge(logo_small: self.logo.url(:small), logo_large: self.logo.url(:large), taxe: self.taxe, attachments: self.attachments)
end
Then within my controller, I'm doing :
def index
#products = current_user.products
respond_with #products
end
def show
respond_with #product
end
The problem is that on the index, I don't want get all the attachments. I only need it on the show method. So I tried it :
def index
#products = current_user.products
respond_with #products, except: [:attachments]
end
But unfortunately it's only working on default product attributes (everyting that I merged seems not to be consider). How can I do to not send :attachments?
Thanks
I'd recommend you have a look at active_model_serializers. It will provide a nice and OOP way of handling the kind of object decoration you need - selectively excluding attributes - and much more. There's even a Railscast!
Related
I have a controller action (favorites) in my Rails app that returns a JSON object with two keys (companies and jobs). Each key represents a collection of Company or JobDescription objects. What I want to know is if there is a clean way I can serialize both #companies and #jobs. Here is my code:
def favorites
#companies = current_user.companies
#jobs = current_user.job_descriptions
respond_to do |format|
format.html
format.json { render json: {companies: #companies, jobs: #jobs}, root: false }
end
end
I could always refactor my code into two separate JSON calls (one for jobs, one for companies), but I'd prefer to stick with a single call to favorites.
You can use Rails Presenters here!
So, you can have two presenters: CompaniesPresenter and JobsPresenter which will be responsible for building the #companies and jobs objects respectively.
So, in your controller, you would have something like:
#companies = CompaniesPresenter.new(current_user).companies
#jobs = JobsPresenter.new(current_user).job_descriptions
For example, your CompaniesPresenter would look like this:
class CompaniesPresenter
attr_reader :current_user
def initialize(current_user)
#current_user = current_user
end
def companies
# build the companies JSON here
end
end
Here is a tutorial with Rails Presenter Pattern that might be useful.
And, here is an useful video. Hope this helps.
This example works, are you just trying to change the json format? If so...
In the company or job model, you can add an as_json method and format the output as you want.
def as_json(options = {})
{ :name => name }
end
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.
I used Pull Review for reviewing my app's code and it came back with this:
Consider refactoring, similar code detected.
Occurred at:
SkillsController # index
PagesController # index
So the app/controllers/skills_controller.rb index action code is:
def index
#skill = Skill.new
if params[:search]
#skills = Skill.search(params[:search]).order('created_at DESC')
else
#skills = Skill.all.order('created_at DESC')
end
end
and on app/controllers/pages_controller.rb is:
def index
#users = User.all
if params[:search]
#users = User.search(params[:search]).order('created_at DESC')
else
#users = User.all.order('created_at DESC')
end
end
Am I suppose to somehow refactor these two actions on these two controllers? Also, I am not sure how I refactor this. Do I extract the if params[:search] segment and replace the instance variables with another variable that will be used on both actions?
Thanks for your time.
I don't know where your method search comes from. It seems it comes from a custom module/gem for ActiveRecord.
If so, you can change the method to shorten code in controller
def self.search(args)
return self unless args
original_search_logic args
end
# As well as extract order to a scope
scope :by_time, -> { order('created_at DESC') }
Then in controller:
# Skill
def index
#skills = Skill.search(params[:search]).by_time
end
# User
def index
#users = User.search(params[:search]).by_time
end
These should be dry enough for now.
take a look at the has_scope and inherited_resources. You can extract the params[:search] part with has_scope. And use inherited_resources to extract how to get the collection and do the ordering.
I'm frequently building controllers where i would like multiple methods
(in addition to index, edit, show, etc.). Most of the time the actions i
desire could be lumped into show as they are simple GET operations,
however I don't want to put too much logic in any one controller action.
Here is a quick example of two different ways to achieve the same
thing...
class TwitterFriendController < ApplicationController
## lump everything into show?
def show
if params[:id] == "follow"
users = current_user.following
elsif params[:id] == "follow_me"
users = current_user.users_who_follow_me
elsif params[:id] == "following_follow_me"
users = current_user.following_who_follow_me
elsif params[:id] == "following_who_do_not_follow_me"
users = current_user.following_who_do_not_follow_me
...
end
respond_with do |format|
format.json do {...}
end
end
## or split everything out into separate methods, this requires
additional routing
def following
...
end
def users_who_follow_me
...
end
def following_who_follow_me
...
end
def following_who_do_not_follow_me
...
end
end
Everything in show
a ton of logic in one method
DRY ? # lots of extra code needed for logic
Less routing
Seperate Methods
More routing
not DRY
Easy method lookup
Easier to read individual methods
So again the real question is, which one of those techniques are less
bad.
I would do something like:
FOLLOW_WHITELIST = %w[ follow follow_me following_follow_me following_who_follow_me following_who_do_not_follow_me ]
def show
if FOLLOW_WHITELIST.include? params[:id]
users = current_user.send params[:id].to_sym
end
respond_with do |format|
format.json do {...}
end
end
This will call whatever method is passed in params[:id], as long as it's in the whitelist (to prevent arbitrary code injection).
If having separate routes was a plus to you (nicer urls?), you could also dynamically generate the methods and routes with something like this:
class TwitterFriendController < ApplicationController
FOLLOW_ACTIONS = %w[ follow follow_me following_follow_me following_who_follow_me following_who_do_not_follow_me ]
FOLLOW_ACTIONS.each do |action|
define_method action do
users = current_user.send action.to_sym
respond_with do |format|
format.json do {...}
end
end
end
end
And then in routes.rb:
FOLLOW_ACTIONS.each do |action|
match action.to_sym => "controller##{action}"
end
I have a fairly simple model; Users have_many products. I would like to be able to view a list of all products as well as a list of the products associated with a given user. My routes are set up like this:
/products
/products/:id
/users
/users/:id
/users/:id/products
The catch here is that I'd like to display the product list differently in the product#index view and the user/products#index view.
Is there a 'correct' way to do this? My current solution is to define products as a nested resource inside users, and then to check for params[:user_id] - if its found I render a template called 'index_from_user', otherwise I just render the typical 'index' template.
This is a situation I'm running into a lot - if there's a preferred way to do it I'd love to know...
You can declare two "products" routes - one under users, and one independent of users eg:
map.resources :products
map.resources :users, :has_many => :products
They will both look for "ProductsController#index" but the second will have the "user_id" pre-populated from the route (note: "user_id" not just "id")
So you can test for that in the index method, and display different items depending on whether it is present.
You will need to add a before_filter to the ProductController to actually instantiate the user model before you can use it eg:
before_filter :get_user # put any exceptions here
def index
#products = #user.present? ? #user.products : Product.all
end
# all the other actions here...
# somewhere near the bottom...
private
def get_user
#user = User.find(params[:user_id])
end
If you really want to display completely different views, you can just do it explicitly in the index action eg:
def index
#products = #user.present? ? #user.products : Product.all
if #user.present?
return render(:action => :user_view) # or whatever...
end
# will render the default template...
end