RoR: Super and Sub classing controllers - ruby-on-rails

So.. i have a method in a super controller that is the same as one in the sub controller.. all except for the redirect_to if the item doesn't save..
subclass method:
def create
some logic
respond_to do |format|
if #template_object.save
format.html { redirect_to({:controller=>:template_objects,:action=>:build,:id=>#template_object}) }
..
end
super method:
def create
some logic
respond_to do |format|
if #template_object.save
format.html { redirect_to({:controller=>:objects,:action=>:build,:id=>#object}) }
..
end
what is the best way to go about this?

Do you have redirect_to in both super and sub class? In that case you might need to use a flag, like a session variable, to decide whether to use or skip redirect_to in the super class.
Devise uses a similar technique, for example, when we need to redirect_to a specific page after signing in using devise.
your super class
def method
...some logic...
if !session[:redirect_var].nil?
session.delete :redirect_var
redirect_to ....
end
end
your sub class
def method
session[:redirect_var] = 'skip_redirect' # or whatever, just create a session variable to use as a flag
super
...some method...
redirect_to ....
end

The usual "object-oriented" approach to this is to create a method that both can call, then define it differently in each of them:
def redirect_to_completed_template(template_object)
redirect_to(...)
end
The idea is to allow sub-classes to re-define only the portion of the functionality they require. This is why you will often see specific features broken out as functions even when what they do isn't especially complicated.

Related

rails refactor model gives an error of Cannot rename instance variable '#search' to local 'rl_search'

I need to refactor my tables so that the table names have a prefix. I.e. searches becomes rl_searches.
When I ran a refactor of searches.rb to rl_searches.rb, I got an error of
Cannot rename instance variable '#search' to local 'rl_search'
If I show the conflicts in a view, one example is:
def destroy
#search.destroy
respond_to do |format|
format.html { redirect_to searches_url, notice: 'Search was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_search
#search = Search.find(params[:id])
end
My initial thought is that I can probably refactor the model not force the refactor of the variables. Will that work?
Yes, you can explicitly set the table name
class Search < ActiveRecord::Base
def self.table_name
"rl_" + super
end
end

Overrule previous redirect_to in controller

Given the following simplified situation (in reality, the scenario is from an ActiveAdmin backed app):
class ShapeController < ApplicationController
def update
(...)
redirect_to
end
end
class CircleController < ShapeController
def update
super
(...)
redirect_to
end
end
Calling CircleController#update will cause the famous "AbstractController::DoubleRenderError" because redirect_to is called twice.
Now, I can't prevent the first call of redirect_to by super, at least not without messing with ActiveAdmin's code. Is there another way to cancel the first redirect_to and overrule it with another one?
Thanks for your hints!
ActiveAdmin is using Inherited Resources to do perform the standard REST actions. The gem provided a way to overwrite the respond_to block. I've never try this before but this might be helpful in your case:
ActiveAdmin.register Circle do
# ...
controller do
def update
update! do |success, failure|
failure.html { redirect_to circle_url(#circle) }
end
end
end
# ...
end
Refer to the IR gem documentation for more options to overwrite the actions(under Overwriting actions section).
I would say it is not possible. The best solution would be to extract the action code in some protected controller method, and call it from the child controller:
class ShapeController < ApplicationController
def update
do_the_update
redirect_to
end
protected
def do_the_update
# your code
end
end
class CircleController < ShapeController
def update
do_the_update
redirect_to
end
end

Rails Redirect_path after save based on view

I currently have two views (new.html.erb and retirement_accounts_new.html.erb) in the Accounts both using the same create and update methods.
Here's how they're defined in the controller:
# GET /accounts/new
def new
#account = current_user.accounts.build
end
# GET /retirement/accounts/new
def retirement_accounts_new
#account = current_user.accounts.build
end
And here's the same create method they share:
def create
#account = current_user.accounts.build(account_params)
if #account.save
redirect_to accounts_path, notice: 'Account was successfully created.'
else
render action: 'new'
end
end
Is there a way to make that redirect_to accounts_path conditional based on which view is rendering the form?
I would like retirement_accounts_new on save/update to redirect_to retirement_accounts
It sounds like this might be a design issue. Are Accounts and RetirementAccounts significantly different? Will they share much of the same logic, but not all? If so, I think I would avoid using conditional logic in the controller and solve it using inheritance.
The idea here is that retirement_accounts would be considered a new resource in your routes file:
resources :retirement_accounts
Then you manually create a new controller for it (skip the rails generate... command). Save this file as app/controllers/retirement_accounts_controller.rb:
class RetirementAccountsController < AccountsController
end
Notice how it inherits from AccountsController instead of ApplicationController. Even in this empty state, RetirementAccountsController shares all of the logic of AccountsController, including the new and create methods, plus all of the view files to which they refer. To make the necessary modifications for the retirement accounts, you simply need to override the appropriate actions and views.
You can delete your retirement_accounts_new action, since it is identical to the new action. Move the view for retirement_accounts_new to app/views/retirement_accounts/new.html.erb, so that template will be rendered when new is called on the RetirementAccountsController.
As for the conditional create method, you can make a private method on both controllers that will determine where the post-create redirect should point:
class AccountsController < ApplicationController
# ...
def create
#account = current_user.accounts.build(account_params)
if #account.save
redirect_to post_create_redirect_path, notice: 'Account was successfully created.'
else
render action: 'new'
end
end
private
def post_create_redirect_path
accounts_path
end
end
class RetirementAccountsController < AccountsController
private
def post_create_redirect_path
retirement_accounts_path
end
end
If RetirementAccount < Account as a single table inheritance model then the thing you are asking would happen by default,
plan B would be to use explicit url_for in the redirect such as:
redirect_to url_for(controller: params[:controller], action: :show, id: #account.id), notice: 'Account was successfully created.'
Looking at the api doc this should work too:
redirect_to :action => "show", :id => #account.id,notice: 'Account was successfully created.'
Check out http://apidock.com/rails/ActionController/Base/redirect_to - there's probably an answer for you there somewhere :)
PS I have assumed that the the retirement account and account actions are in different controllers. If they're not in different controllers and not different model classes then you can put a hidden tag in the new form - but this is bad&ugly
Best solution is probably STI model and 2 separate resources for the 2 classes and everything will work out of the box. If this isn't an option, at least separate the controllers and make things clean that way, it's much better to reuse views then to reuse controllers

How to disable ActionMailer in rails_admin?

I use ActionMailer to send different notifications to user. I use Model callbacks for this.
When I do changes as admin, I don't want any email to be sent to client.
How can I disable ActionMailer in RailsAdmin?
Actually, I'd like to provide an ability to admin to turn on/off emails.
Thanks
Triggering your mailers in the model lifecycle is not recommended IMHO. The recommended approach would be to trigger the mailers from the controller.
If you want to achieve separation of concerns in your controller and not pollute your controller code with mailer calls, you can use a combination of ActiveSupport::Notifications and controller after_filter to extract the mailer logic into its own module.
module MailerCallbacks
module ControllerExtensions
def self.included(base)
base.after_filter do |controller|
ActiveSupport::Notifications.instrument(
"mailer_callbacks.#{controller_path}##{action_name}", controller: controller
)
end
end
end
module Listener
def listen_to(action, &block)
ActiveSupport::Notifications.subscribe("mailer_callbacks.#{action}") do |*args|
event = ActiveSupport::Notifications::Event.new(*args)
controller = event.payload[:controller]
controller.instance_eval(&block)
end
end
end
end
Let's assume you want to refactor the following controller using our module created above:
class PostsController < ApplicationController
def create
#post = Post.new permitted_params
respond_to do |format|
if #post.save
PostMailer.notify(#post).deliver
format.html { redirect_to #post, notice: 'Successfully created Post' }
else
format.html { render action: 'new' }
end
end
end
end
Take the following steps:
Create an initializer to register the controller extension:
# config/initializers/mailer_callbacks.rb
ActiveSupport.on_load(:action_controller) do
include MailerCallbacks::ControllerExtensions
end
In the same or a separate initializer, create a class and extend the Listener module to register your callbacks:
# config/initializers/mailer_callbacks.rb
class MailerListeners
extend MailerCallbacks::Listener
# register as many listeners as you would like here
listen_to 'posts#create' do
PostMailer.notify(#post).deliver if #post.persisted?
end
end
Remove mailer code from your controller.
class PostsController < ApplicationController
def create
#post = Post.new permitted_params
respond_to do |format|
if #post.save
format.html { redirect_to #post, notice: 'Successfully created Post' }
else
format.html { render action: 'new' }
end
end
end
end
Essentially we have created an observer on the controller actions and registered our mailer callbacks with the controller instead of tying it into the model lifecycle. I personally consider this approach cleaner and easier to manage.
You should handle it on callback method.
def call_back_name
if is_rails_admin?
#Do nothing
else
#send mail
end
end
Let's separate this question into 2 parts:
Disable email
Check this answer
Integration with Rails Admin
You can add it to Rails Admin Navigation part and put it into separate controller for admin mailer turn on/off. More info here.

Why would it be better to use an authorization library as opposed to my example?

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.

Resources