Rails 4 using controller concern to DRY update methods - ruby-on-rails

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.

Related

Rails' before_filter equivalent in Phoenix

I've just started working on my first Phoenix app, and the issue is that I have some common lines of code in every action in my controller, that I would like to separate out. They fetch data from multiple Ecto Models and save them to variables for use.
In Rails, I could simply define a method and call it using before_filter in my controller. I could access the result from an #variable. I understand that using Plugs is the key but I'm unclear on how to achieve this, more specifically:
Accessing the request params from a Plug
and making the variables accessible in actions
As a reference, this is the rails version of what i'm trying to do:
class ClassController < ApplicationController
before_filter :load_my_models
def action_one
# Do something with #class, #students, #subject and #topics
end
def action_two
# Do something with #class, #students, #subject and #topics
end
def action_three
# Do something with #class, #students, #subject and #topics
end
def load_my_models
#class = Class.find params[:class_id]
#subject = Subject.find params[:subject_id]
#students = #class.students
#topics = #subject.topics
end
end
Thanks!
You can indeed achieve this with a Plug and Plug.Conn.assign.
defmodule TestApp.PageController do
use TestApp.Web, :controller
plug :store_something
# This line is only needed in old phoenix, if your controller doesn't
# have it already, don't add it.
plug :action
def index(conn, _params) do
IO.inspect(conn.assigns[:something]) # => :some_data
render conn, "index.html"
end
defp store_something(conn, _params) do
assign(conn, :something, :some_data)
end
end
Remember to add the plug declaration before your action plug, as they are executed in-order.
This is better as a comment but I lack the rep; with the current version of Phoenix (1.3.4, August of 2018), if you use the top answer's code, you would only want to do plug :store_something: do not use plug :action as it is redundant. The actions will run after the plugs which you listed.
If you include plug :action you will get (Plug.Conn.AlreadySentError) the response was already sent as the action will run twice and Phoenix will be mad at you.

Rails 4 json return on API

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!

Controller best practices: Multiple Methods or Multiple cases in Show

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

How should I structure controller actions that share templates?

Let's say I have a User model, and an Invoice model with a belongs_to :user association.
Now I'm creating a new action for my InvoicesController, and the view that will be rendered. The view will have a select-element for selecting the user that this invoice will belong to.
So I need to fetch those users somewhere; my instinct is to leave this kind of thing out of the view. I end up with this:
def new
#users = User.all
end
The form submit action is create. When the creation fails for some reason, I re-render the new action's view.
def create
invoice = Invoice.new params[:invoice]
if invoice.save
flash[:notice] = 'Invoice created!'
redirect_to :action => 'show', :id => invoice.id
else
#users = User.all
render :action => 'new'
end
end
But as you can see, in order the re-render the new action, I have to fetch the users again.
This is just an example, but consider that I have some forms with several select-elements filled from the database, or similar constructs. That turns into an awful lot of repetition.
So how should I structure my controller in this situation?
Should I simply use User.all from my view?
Should I call new from within create to do the fetching for me?
Or something else?
For this I'd use a before_filter. For example you'd do something like:
before_filter :fetch_all_users, :only => [:new, :create]
protected
def fetch_all_users
#users = User.all
end
For 90% of my controllers I use the inherited resources plugin. It cuts down the amount of controller code you need to write for CRUD controllers, which also means you can cut down on the amount of tests you need to write.
For me:
What's the rails way to load other models collections for new, edit update and create actions?
It's not a good approach for my situation. Where after ".save", I send redirect_to to an another action, if I use before_filter and ".save" returns true, the fetch_all_users is called unnecessary

How can I send a parameter to a before filter?

I'd like to create a before_filter method in my application controller like this...
def check_role(role_name)
unless logged_in_user.has_role? role_name
flash[:notice] = 'Access to that area requires additional privileges.'
redirect_to :back
end
end
However, it doesn't look as though before filters can take parameters.
Is there a way to parameterize this call, or am I trying to drive a screw with a hammer?
You should be able to do this with a block:
before_filter {|controller| controller.check_role('admin') }
You can use a bit of meta-programming. Something like this (completely untested, just something to give you an idea of how it might go):
Module RoleWithIt
Role.all.each do |role|
define_method("check_#{role.name}_role".to_sym) do
check_role(role.name)
end
end
def check_role(role_name)
return if logged_in_user.has_role?(role_name)
flash[:notice] = 'Access to that area requires additional privileges.'
redirect_to :back
end
end
ApplicationController.send :include, RoleWithIt
To have it load when your app initialises, just put it in a file called role_with_it.rb and put it in your lib directory.
am I trying to drive a screw with a
hammer?
Er, possibly ;-)
If I'm reading this correctly, you have a situation where actions within a controller have different access levels, so you want to remove the duplication by creating a single check function?
So you're looking to do something like this?
before_filter :check_role('admin'), :only => [:admin, :debug]
before_filter :check_role('power'), :only => [:edit, :delete]
But the parameter in parens thing is not legal. And anyway, I still see a fair bit of duplication here!
In general, with an area of functionality as well-visited as controller filters, if you can't do something, it's probably because you're looking at something the wrong way. (Remember that Rails is proud to describe itself as "opinionated software"!)
How would it be if you were able to know the action name in your filter method?
Then we'd have
before_filter :check_role
Which is pretty DRY.
We could define permissions in a Hash, perhaps:
Perms = { :admin => ['admin'], :edit => ['admin', 'power'], etc
... which seem to encapsulate the distinct elements of the duplication. If it got complex then the whole thing could move off into a table, although then you're probably duplicating functionality already available in a plugin.
And we'd have
protected
def check_role
for required_role in Perms[params[:action]]
return if logged_in_user.has_role? required_role
end
flash[:notice] = 'Access to that area requires additional privileges.'
redirect_to :back
end
Or something similar. params[:action] works on my current Rails version (2.1.2), although the Rails book (v2) mentions an action_name method that seems to return blank for me.
I don't believe you can pass parameters to filters. So what I have do in the past is made static methods that pass the parameter to the method that needs the params.
So I would have something like this:
def check_role(role_name)
unless logged_in_user.has_role? role_name
flash[:notice] = 'Access to that area requires additional privileges.'
redirect_to :back
end
end
def check_admin_role
check_role('admin')
end
def check_blah_role
check_role('blah')
end
Then in your controller you'd just call
before_filter :check_admin_role
There is probably some way to implement this with meta-programming but I am still quite a n00b and haven't figured that part out yet ;)
it's an old question, but if somebody still wonders, a good asnwer for rails 4 can be found here

Resources