I've been reading the rails_admin wiki section about customizing actions but I am totally new to rails_admin and this documentation is a bit confusing for me.
What I need to achieve is that, when an admin user clicks the options to delete a specific Employee (Employee is a model in my app), the code to actually delete the given employee cannot be the default way in which rails_admin deletes, but a given block of code that I need to provide to rails_admin somehow.
Note that I still want the rest of the data models accessible through rails_admin to be deleted in the usual fashion. Only the employee model needs to have the custom delete routine.
I just encountered the same problem because I need users to be marked for deletion and not deleted right away.
After hacking around a bit I've finally found a way : overriding RailsAdmin's default delete action.
Here's the delete action after I added my own custom code (not deleting but marking for deletion with a custom notice, hiding the delete button if the user is already marked for deletion). :
# config/initializers/rails_admin_delete_override.rb
module RailsAdmin
module Config
module Actions
class Delete < RailsAdmin::Config::Actions::Base
RailsAdmin::Config::Actions.register(self)
register_instance_option :member do
true
end
register_instance_option :route_fragment do
'delete'
end
register_instance_option :http_methods do
[:get, :delete]
end
register_instance_option :authorization_key do
:destroy
end
register_instance_option :visible? do
bindings[:object].class.base_class.name != 'User' || !bindings[:object].to_destroy?
end
register_instance_option :controller do
Proc.new do
if request.get? # DELETE
respond_to do |format|
format.html { render #action.template_name }
format.js { render #action.template_name, :layout => false }
end
elsif request.delete? # DESTROY
redirect_path = nil
#auditing_adapter && #auditing_adapter.delete_object(#object, #abstract_model, _current_user)
if #object.class.base_class.name == 'User'
#object.to_destroy!
flash[:success] = t("admin.flash.user_destroy_successful", :name => #model_config.label)
redirect_path = index_path
else
if #object.destroy
flash[:success] = t("admin.flash.successful", :name => #model_config.label, :action => t("admin.actions.delete.done"))
redirect_path = index_path
else
flash[:error] = t("admin.flash.error", :name => #model_config.label, :action => t("admin.actions.delete.done"))
redirect_path = back_or_index
end
end
redirect_to redirect_path
end
end
end
register_instance_option :link_icon do
'icon-remove'
end
end
end
end
end
You can find the original action code here : https://github.com/sferik/rails_admin/blob/master/lib/rails_admin/config/actions/delete.rb
After hours of hacking, I have found a simpler way of customizing an action.
All you need is to create a different module name (In my case is 'MyRailsAdmin'), then extends the original action class, eg. 'Delete'
# config/initializers/rails_admin_delete.rb
require 'rails_admin/config/actions'
require 'rails_admin/config/actions/base'
module MyRailsAdmin
module Config
module Actions
class Delete < RailsAdmin::Config::Actions::Delete
RailsAdmin::Config::Actions::register(self)
register_instance_option :controller do
proc do
if request.get? # DELETE
respond_to do |format|
format.html { render #action.template_name }
format.js { render #action.template_name, layout: false }
end
elsif request.delete? # DESTROY
redirect_path = nil
#auditing_adapter && #auditing_adapter.delete_object(#object, #abstract_model, _current_user)
# [CUSTOM DELETE] mark as deleted (status = 0) rather than actually hard delete it
#object.status = 0
if #object.save
flash[:success] = t('admin.flash.successful', name: #model_config.label, action: t('admin.actions.delete.done'))
redirect_path = index_path
else
flash[:error] = t('admin.flash.error', name: #model_config.label, action: t('admin.actions.delete.done'))
redirect_path = back_or_index
end
redirect_to redirect_path
end
end
end
end
end
end
end
Related
I have a Rails 3.2.18 app where I want to ship details of a call (includes name, age, and other information) to a recipient's phone that is already a field in the database.
So for instance a call has a unit assigned, and each unit has a medic (employee) assigned. In the medic model there's a phone field 281-444-555 (example number). What I want to be able to do in the calls controller is to send a SMS on create and update with the details of that call so it arrives on their phone as SMS.
I'm currently doing notifications to phones by using Email to SMS gateway 2813334444#vtext.com (example) using ActionMailer and it works "ok". But I really want to leverage Twilio.
Here's how I'm doing the mailer action to notify the medics of calls on create/update
calls_controller
def create
parse_times!
#call = Call.new(params[:call])
#call.dispatched_by = current_user.username
if #call.save
#call.send_mail(:new_call)
redirect_to calls_path, notice: "Call #{#call.incident_number} was successfully created.".html_safe
else
render :new
end
end
def update
parse_times!
#call = Call.find(params[:id])
respond_to do |format|
if #call.update_attributes(params[:call])
unless #call.call_status == "close"
#call.send_mail(:update_call)
end
format.html { redirect_to #call, notice: "Call #{#call.incident_number} was successfully updated.".html_safe }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #call.errors, status: :unprocessable_entity }
end
end
end
call_mailer
def new_call(medic, call)
#call = call
#medic = medic
mail to: [#medic.medic_sms, #medic.medic_email], :cc => "noreply#company.com", subject: "New Call: #{#call.incident_number}"
end
def update_call(medic, call)
#call = call
#medic = medic
mail to: [#medic.medic_sms, #medic.medic_email], subject: "Updated Call: #{#call.incident_number}"
end
call model (mailer method)
def send_mail(mail_type)
units.each do |unit|
CallMailer.send(mail_type, unit.incharge, self).deliver
CallMailer.send(mail_type, unit.attendant, self).deliver
end
end
end
This is working just fine for mailing the phones and emails of the medics, but I would like to add something similar using Twilio where I can ship the call details to them via SMS in the create and update action.
If anyone can point me in the right direction, I'd appreciate it. I have a Twilio account already and would like to put it to good use.
Update 08/03/14
I think I figured this out and got it working in a basic fashion. But I'd like to see if there's someway to cleanly pass #call object data into the :body => section. Right now I'm having to iterate over the specific fields I want to send (which are about 10 different fields). It would be nice if I could create a partial or template and pass it to :body => similar to how ActionMailer works. Any thoughts?
calls_controller.rb (working)
def update
parse_times!
#call = Call.find(params[:id])
respond_to do |format|
if #call.update_attributes(params[:call])
unless #call.call_status == "close"
unless #call.unit_ids.empty?
send_sms
end
#call.send_mail(:update_call)
end
format.html { redirect_to #call, notice: "Call #{#call.incident_number} was successfully updated.".html_safe }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #call.errors, status: :unprocessable_entity }
end
end
end
private
def send_sms
account_sid = 'AC5CCCCC'
auth_token = 'ATTTTT'
#client = Twilio::REST::Client.new account_sid, auth_token
#client.account.messages.create(
:from => '2814084444',
:to => #call.units.first.incharge.medic_phone,
:body => "incident_number #{#call.incident_number} patient name #{#call.patient_name}"
)
#client.account.messages.create(
:from => '2814084444',
:to => #call.units.first.attendant.medic_phone,
:body => "incident_number #{#call.incident_number} patient name #{#call.patient_name}"
)
end
end
Ok, I have this figured out now. I needed to do string interpolation in the :body element for Twilio to send the information out. Here is my final code and it's working and updated with a conditional to only fire Twilio if certain conditions are met.
calls_controller.rb
def update
parse_times!
#call = Call.find(params[:id])
respond_to do |format|
if #call.update_attributes(params[:call])
if !(#call.call_status == "close") && !(#call.unit_ids.empty?)
send_update_sms
#call.send_mail(:update_call)
end
format.html { redirect_to #call, notice: "Call #{#call.incident_number} was successfully updated.".html_safe }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #call.errors, status: :unprocessable_entity }
end
end
end
def send_update_sms
account_sid = 'AC5CCCC'
auth_token = 'ATTTT'
#client = Twilio::REST::Client.new account_sid, auth_token
#client.account.messages.create(
:from => '28140844444',
:to => #call.units.first.incharge.medic_phone,
:body => "Updated: #{#call.incident_number}/#{#call.units.map(&:unit_name).join(", ")}/#{#call.patient_name}/#{#call.patient_age}/#{#call.patient_sex.try(:sex)}/#{#call.nature.try(:determinant)}/#{#call.special_equipments.map(&:name).join(", ")}/#{#call.traffic_type}/#{transfer_from_address}/#{transfer_to_address} CHECK EMAIL FOR FULL CALL INFO"
)
#client.account.messages.create(
:from => '2814084444',
:to => #call.units.first.attendant.medic_phone,
:body => "Updated: #{#call.incident_number}/#{#call.units.map(&:unit_name).join(", ")}/#{#call.patient_name}/#{#call.patient_age}/#{#call.patient_sex.try(:sex)}/#{#call.nature.try(:determinant)}/#{#call.special_equipments.map(&:name).join(", ")}/#{#call.traffic_type}/#{transfer_from_address}/#{transfer_to_address} CHECK EMAIL FOR FULL CALL INFO"
)
end
def transfer_from_address
if #call.transferred_from.nil?
#call.transfer_from_other
else
#call.transferred_from.try(:facility_name) + ' ' + #call.transferred_from.try(:facility_address)
end
end
def transfer_to_address
if #call.transferred_to.nil?
#call.transfer_to_other
else
#call.transferred_to.try(:facility_name) + ' ' + #call.transferred_to.try(:facility_address)
end
end
how to get session in helper file?
UserHelper.rb
module UsersHelper
def self.auth login, password
user = Users.where("firstname = :firstname AND password = :password", {:firstname => login, :password => password})
if user != []
return true
else
return false
end
end
def self.is_auth? level
puts #session
user = Users.where("firstname = :firstname AND password = :password", {:firstname => #session[:firstname], :password => #session[:password]})
if user != []
return true
else
return false
end
end
end
Admin_controller.rb
class AdminController < ApplicationController
include Rails.application.routes.url_helpers
def initialization
#session = session
end
def index
#session = session
if UsersHelper.is_auth?(2)
render :text => "ssssss"
end
end
def auth
if params[:send] != nil
if UsersHelper.auth params[:firstname], params[:password]
session[:firstname] = params[:firstname]
session[:password] = params[:password]
redirect_to :action => "index"
else
#error = 1
end
end
end
def exit
session.delete(:firstname)
session.delete(:password)
render :json => session
end
end
Error
undefined method `[]' for nil:NilClass
app/helpers/users_helper.rb:13:in `is_auth?'
app/controllers/admin_controller.rb:8:in `index'
Only Controller can access session.
So, in a nutshell, if you are going to use this method in Controllers only like what is you case, you can define it as ApplicationController's method. Or define it a module and include it in AppplicationController.
class ApplicationController < ActionController::Base
def auth
end
def is_auth?
end
end
If you want to use the method in both controller and view, just declare them as helper_method
class ApplicationController < ActionController::Base
helper_method :auth, :is_auth?
def auth
end
def is_auth?
end
end
Ref: http://apidock.com/rails/ActionController/Helpers/ClassMethods/helper_method
Another note: In my opinion it's really not worth the time to build auth system from scratch by yourself. The functionalities are not easy but quite general. There are well baked gems such as Devise, Authlogic. Better to use them.
I need to be able to save a record without running validations on itself or its nested attributes. I'm stuck in Rails 3.0, and I cannot update to a newer version.
I have a report, each report has many responses (answers to questions). The responses are nested in the report form.
There are two ways the user should be able to save the report: Submit for review, where all validations are run, and Save And Finish Later, where no validations are run for the report or the nested responses. This needs to work for both create and update actions.
I am currently trying to use conditional validations. This works for update but not create. The problem is this line:
validate :has_answer_if_required, :if => Proc.new { |response| !response.report.finish_later? }
The report doesn't exist yet, so active record can't find this responses's report. That's where it crashes.
There are a lot some suggested solutions for this problem, but I couldn't get them working in Rails 3.0. update_attributes(attributes, :validate => false), for instance, is not available in Rails 3.0.
So, how do I skip the validations in the nested attributes?
class Report < ActiveRecord::Base
has_many :responses, :order => "created_at asc", :autosave => true
accepts_nested_attributes_for :responses
...
end
class Response < ActiveRecord::Base
belongs_to :report
validates_associated :report
validate :has_answer_if_required, :if => Proc.new { |response| !response.report.finish_later? }
validate :correct_answer_or_comment, :if => Proc.new { |response| !response.report.finish_later? }
end
class ReportsController < BaseController
def update
#report = Report.find(params[:id])
#report.attributes = params[:report]
if params[:finish_later].nil?
#report.update_attribute(:finish_later, false)
if #report.save!
redirect_to :action => :index
else
render :template => "reports/edit"
end
else
#report.finish_later = true
#report.save(:validate => false)
redirect_to :action => :index
end
end
def create
#report = Report.new(params[:report])
if params[:finish_later].nil?
#report.finish_later = false
if #report.save!
redirect_to :action => :index
else
render :template => "reports/edit"
end
else
#report.finish_later = true
#report.save!(:validate => false)
redirect_to :action => :index
end
end
end
Not sure if it will work with nested attributes, though I think it should... but give ValidationSkipper a try:
https://github.com/npearson72/validation_skipper
Just make sure you call skip_validation_for on the object you want to skip. Since nested attributes pass behavior to their children, you might be able to call this method directly on the parent object. Give it a try.
I use Devise and I want to do my logout action.
What I want to do is, that when I log out, I want to create a own JSON object to return. At this time, after I logt out, I get all my root articles.
How can I write my own destory action like I have found the create action?
class SessionsController < Devise::SessionsController
def create
resource = warden.authenticate!(:scope => resource_name, :recall => :failure)
return sign_in_and_redirect(resource_name, resource)
end
def sign_in_and_redirect(resource_or_scope, resource=nil)
scope = Devise::Mapping.find_scope!(resource_or_scope)
resource ||= resource_or_scope
sign_in(scope, resource) unless warden.user(scope) == resource
return render :json => {:success => true, :redirect => stored_location_for(scope) || after_sign_in_path_for(resource)}
end
def failure
return render:json => {:success => false, :errors => ["Login failed."]}
end
end
And my Routes in routes.rb
devise_for :users, :controllers => {:session => "sessions"} do
get "/users/sing_out" => "devise/sessions#destroy"
end
this is the destroy method of the sessions-controller.
you should be able to customize it to your needs. i think that it would be wiser to add another action and implementing your custom behavior there, as this will be less likely to cause unexpected errors with upgrading devise in the future.
# DELETE /resource/sign_out
def destroy
redirect_path = after_sign_out_path_for(resource_name)
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message :notice, :signed_out if signed_out
# We actually need to hardcode this as Rails default responder doesn't
# support returning empty response on GET request
respond_to do |format|
format.any(*navigational_formats) { redirect_to redirect_path }
format.all do
method = "to_#{request_format}"
text = {}.respond_to?(method) ? {}.send(method) : ""
render :text => text, :status => :ok
end
end
end
I am testing a controller in RSpec2 and for both my create and update actions, when passed invalid params, the controller should render either the "new" or "edit" templates respectively. It is doing that, but my test never passes.
describe "with invalid params" do
before(:each) do
User.stub(:new) { mock_user(:valid? => false, :save => false) }
end
it "re-renders the 'new' template" do
post :create, :company_id => mock_company.id
response.should render_template("new")
end
end
Results in this:
re-renders the 'new' template
expecting <"new"> but rendering with <"">
Here is the controller action:
respond_to do |format|
if #user.save
format.html {
flash[:notice] = "#{#user.full_name} was added to #{#company.name}."
redirect_to company_users_url(#company)
}
else
logger.debug #user.errors
format.html{
render :new
}
end
end
This problem also seems to be isolated to this controller. I have almost identical code running another controller and it is fine. I am not sure where the problem could be.
Update:
Here are the two mock methods
def mock_user(stubs={})
#mock_user ||= mock_model(User, stubs).as_null_object
end
def mock_company(stubs={})
(#mock_company ||= mock_model(Company).as_null_object).tap do |company|
company.stub(stubs) unless stubs.empty?
end
end
Turned out it was a problem with stubbing and CanCan. CanCan was loading the resources and uses some different methods than what I thought.