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.
Related
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
It's the first time I'm using this gem and it's driving me crazy with something as simple as authorize the showaction only for the resource owner.
I tried different ways, configuring the controller mapping and actions, but always get the unauthorized message for show, other actions work as they should.
It seems that showis not getting it's way to the ApplicationAuthorizer.
This is how it's configured:
class EnterpriseAuthorizer < ApplicationAuthorizer
# This works
def self.creatable_by?(user)
user.is_enterpriser?
end
# This doesn't
def readable_by?(user)
true # Just for testing
end
end
class EnterprisesController < ApplicationController
authorize_actions_for Enterprise
def show
#enterprise = Enterprise.find(params[:id])
respond_to do |format|
format.html
format.json { render json: #enterprise }
end
end
I have include Authority::UserAbilities in User and include Authority::Abilities in the Enterprise model. And User has_one :enterprise
Any idea? Thinking seriously about rolling back to cancan.
Thanks in advance.
Authority has different ways of checking permissions. For collection-based actions (e.g. new, create, index), you use authorize_actions_for Model.
For instance-based actions (e.g. edit, update, show, delete), you must call authorize_action_for #instance.
Change your code to this and it should work.
class EnterprisesController < ApplicationController
authorize_actions_for Enterprise
def show
#enterprise = Enterprise.find(params[:id])
authorize_action_for #enterprise
respond_to do |format|
format.html
format.json { render json: #enterprise }
end
end
end
If you want a less messy way to do this, put the
#enterprise = Enterprise.find(params[:id])
authorize_action_for #enterprise
into a before filter that's called by each instance action.
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.
I am using Devise for authentication, and I only need a simple admin or use check for a few controllers. I'm new to rails, so I'm trying to do this the right way. I've basically added a boolean admin field to the user model and added this method
def is_admin?
admin == 1
end
Then I simply modified the controller action to this
def new
if current_user.nil? || !current_user.is_admin?
flash[:notice] = "You do not have permission to view this page"
redirect_to "/gyms"
else
#gym = Gym.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #gym }
end
end
end
So this solution works, but should I be doing this a different way?
This will work but I probably would not recommend this solution for anything else than a small scale project. Over time, if you perform authorization checks within your controllers, your code is going to become bloated and difficult to manage.
Instead I would consider using an authorization module such as Cancan which centralizes your authorization rules in one place and thus decouples your application logic from your authorization logic. The end result is cleaner and more maintainable code.
With Cancan in place, your code might look like this:
# app/controllers/gyms_controller.rb
class GymsController < ApplicationController
load_and_autorize_resource
def new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #gym }
end
end
end
end
# app/models/Ability.rb
can :create, Gym do |trip|
user.is_admin?
end
I have the following:
def create
#permission = #project.permissions.create(params[:permission])
respond_to do |format|
if #permission.save
format.js
else
format.js { render :js => #permission.errors }
end
end
end
I want to add in a Mailer, to let the user know they have been added to a project, the issue is, if I pu that before the respond_to, the record hasn't been saved yet so it's possible that something could go wrong but the user would still get an email.
UserMailer.xxxxxxxxx_notification(objecthere).deliver
And I'm guessing I can't put a mailer inside the respond_to block. Suggestions?
This is what observers are used for.
create app/models/permission_observer.rb
class PermissionObserver < ActiveRecord::Observer
def after_create(permission)
# put your mailer code here
end
end
in config/application.rb add the observer
config.active_record.observers = :permission_observer
You can read more about observers here.
Also, you should be using #project.permissions.new instead of create. create saves the model immediately, making your #permission.save call redundant.
Once you have this in place, you should look into making your mailer code asynchronous so it doesn't hold up web requests. Here's an example using delayed_job.
Or you can edit your code to:
def create
#permission = #project.permissions.build(params[:permission])
if #permission.save
UserMailer.xxxxxxxxx_notification(objecthere).deliver
respond_to do |format|
format.js
end
else
respond_to do |format|
format.js { render :js => #permission.errors }
end
end
end
Or you can put a callback in your Permission model:
class Permission
after_create :send_mail
def send_mail
UserMailer.xxxxxxxxx_notification(self).deliver
end
end