Monkey patching Devise (or any Rails gem) - ruby-on-rails

I'm using the Devise authentication gem in my Rails project, and I want to change the keys it's using in flash alerts. (Devise uses :notice and :alert flash keys, but I want to change them to :success and :error so that I can display nice green/red boxes with Bootstrap.)
So I want to be able to somehow override the set_flash_message method in DeviseController.
Here's the new method:
def set_flash_message(key, kind, options = {})
if key == 'alert'
key = 'error'
elsif key == 'notice'
key = 'success'
end
message = find_message(kind, options)
flash[key] = message if message.present?
end
But I just don't know where to put it.
UPDATE:
Based on an answer I created a config/initializers/overrides.rb file with the following code:
class DeviseController
def set_flash_message(key, kind, options = {})
if key == 'alert'
key = 'error'
elsif key == 'notice'
key = 'success'
end
message = find_message(kind, options)
flash[key] = message if message.present?
end
end
But this causes an error on every Devise action:
Routing Error: undefined method 'prepend_before_filter' for
Devise::SessionsController:Class

If you try to reopen a class, it's the same syntax as declaring a new class:
class DeviseController
end
If this code is executed before the real class declaration, it inherits from Object instead of extending the class declared by Devise. Instead I try to use the following
DeviseController.class_eval do
# Your new methods here
end
This way, you'll get an error if DeviseController has not been declared. As a result, you'll probably end up with
require 'devise/app/controllers/devise_controller'
DeviseController.class_eval do
# Your new methods here
end

Using Rails 4 #aceofspades answer didn't work for me.
I kept getting require': cannot load such file -- devise/app/controllers/devise_controller (LoadError)
Instead of screwing around with load order of initializers I used the to_prepare event hook without a require statement. It ensures that the monkey patching happens before the first request. This effect is similar to after_initialize hook, but ensures that monkey patching is reapplied in development mode after a reload (in prod mode the result is identical).
Rails.application.config.to_prepare do
DeviseController.class_eval do
# Your new methods here
end
end
N.B. the rails documentation on to_prepare is still incorrect: See this Github issue

In your initializer file :
module DeviseControllerFlashMessage
# This method is called when this mixin is included
def self.included klass
# klass here is our DeviseController
klass.class_eval do
remove_method :set_flash_message
end
end
protected
def set_flash_message(key, kind, options = {})
if key == 'alert'
key = 'error'
elsif key == 'notice'
key = 'success'
end
message = find_message(kind, options)
flash[key] = message if message.present?
end
end
DeviseController.send(:include, DeviseControllerFlashMessage)
This is pretty brutal but will do what you want.
The mixin will delete the previous set_flash_message method forcing the subclasses to fall back to the mixin method.
Edit:
self.included is called when the mixin is included in a class. The klass parameter is the Class to which the mixin has been included. In this case, klass is DeviseController, and we call remove_method on it.

What about adding in the override initializer and alias for the attributes of the flash hash, like this:
class ActionDispatch::Flash::FlashHash
alias_attribute :success, :notice
alias_attribute :error, :alert
end
This should allow your application to read flash[:notice] or flash[:success](flash.notice and flash.success)

You need to overwrite DeviseController while keeping around its superclass, in your initializer.
Something like:
class DeviseController < Devise.parent_controller.constantize
def set_flash_message(key, kind, options = {})
if key == 'alert'
key = 'error'
elsif key == 'notice'
key = 'success'
end
message = find_message(kind, options)
flash[key] = message if message.present?
end
end

I know this is an old thread but this might still be helpful. You should be able to require the file from the gem directory using the engine called_from path.
require File.expand_path('../../app/helpers/devise_helper',Devise::Engine.called_from)
require File.expand_path('../../app/controllers/devise_controller',Devise::Engine.called_from)
DeviseController.class_eval do
# Your new methods here
end

This is the kind of thing that you will want to put on initialize rails folder, because it's a custom config for this application in particular, second you should use like so:
class DeviseController
def set_flash_message(key, kind, options = {})
if key == 'alert'
key = 'error'
elsif key == 'notice'
key = 'success'
end
message = find_message(kind, options)
flash[key] = message if message.present?
end
end
then you should get the expected behavior.
hope it helps since i dont tested, of not pls give a feedback and i will help you try something diferent.

Related

How to set up a callback in an initializer?

I'm using Koudoku for subscriptions. I want to do different things after receiving a Stripe webhook.
In the docs, it shows you can add a callback like so:
Koudoku.setup do |config|
config.subscriptions_owned_by = :user
config.stripe_publishable_key = ENV['STRIPE_PUBLISHABLE_KEY']
config.stripe_secret_key = ENV['STRIPE_SECRET_KEY']
# add webhooks
config.subscribe 'charge.failed', YourChargeFailed
end
What I can't figure out how to write the YourChargeFailed part. I've tried something like:
config.subscribe 'order.payment_succeeded', ActiveRecord::Subscription.after_successful_payment
but I get undefined method after_successful_payment for #<Class:0x007fb845849b30>
How can I successfully subscribe to Stripe events, capture the return data, and initiate a callback function?
Thanks!
UPDATE
Here is what I've tried, and the corresponding errors I'm receiving:
purchases_helper.rb
module PurchasesHelper
require 'stripe'
def stripe_webhook(event)
puts 'Purchases Helper'
puts 'invoice.payment_succeeded'
#customer = Stripe::Customer.retrieve(event[:data][:object][:customer])
#user = User.find_by(email: #customer[:email])
#badge = Badge.find_by(condition: '2019Purchase')
#badges_user = BadgesUser.find_by(user_id: #user.id, badge_id: #badge.id)
# if #badges_user === nil
# BadgesUser.create(user_id: user.id, badge_id: badge.id)
# end
puts 'badge created'
end
end
initializers/koudoku.rb
Koudoku.setup do |config|
include ::PurchasesHelper
config.subscribe 'charge.succeeded' do |event|
puts 'charge created'
::PurchasesHelper.stripe_webhook(event)
end
end
ERROR:
undefined method `stripe_webhook' for PurchasesHelper:Module excluded from capture: Not configured to send/capture in environment 'development'
NoMethodError (undefined method `stripe_webhook' for PurchasesHelper:Module):
Another attempt:
Koudoku.setup do |config|
config.subscribe 'charge.succeeded' do |event|
puts 'charge created'
PurchasesHelper.stripe_webhook(event)
end
end
ERROR:
undefined method `stripe_webhook' for PurchasesHelper:Module excluded from capture: Not configured to send/capture in environment 'development'
NoMethodError (undefined method `stripe_webhook' for PurchasesHelper:Module):
3rd Attempt:
Koudoku.setup do |config|
include PurchasesHelper
config.subscribe 'charge.succeeded' do |event|
puts 'charge created'
stripe_webhook(event)
end
end
ERROR:
A copy of PurchasesHelper has been removed from the module tree but is still active! excluded from capture: Not configured to send/capture in environment 'development'
ArgumentError (A copy of PurchasesHelper has been removed from the module tree but is still active!):
I see only one problem with your code.
module PurchasesHelper
require 'stripe'
def self.stripe_webhook(event) # NB self.
puts 'Purchases Helper'
puts 'invoice.payment_succeeded'
#customer = Stripe::Customer.retrieve(event[:data][:object][:customer])
#user = User.find_by(email: #customer[:email])
#badge = Badge.find_by(condition: '2019Purchase')
#badges_user = BadgesUser.find_by(user_id: #user.id, badge_id: #badge.id)
# if #badges_user === nil
# BadgesUser.create(user_id: user.id, badge_id: badge.id)
# end
puts 'badge created'
end
end
and then you call it by saying
Koudoku.setup do |config|
config.subscribe 'charge.succeeded' do |event|
puts 'charge created'
PurchasesHelper.stripe_webhook(event)
end
end
This should work
Wait but Why?!
Modules are a way of grouping together methods, classes, and constants. Modules give you two major benefits.
provide a namespace and prevent name clashes
implement the mixin facility (when you include them)
You've defined an instance method on the Module that when included it will appear on every instance of the object.
but you are not doing that in this case. You want to call stripe_webhook on the Module itself.
adding self. stripe_webhook in this case = PurchasesHelper. stripe_webhook which is the way to define a methods on the class/module.
You can even do more freaky stuff like:
class Animal
def self.all
%w[dog cat bird]
end
end
def Animal.include?(a)
self.all.include?(a)
end
Animal.include?('cat') # true
Animal.include?('virus') # false
so you can even define methods on the Animal class outside the scope of the class and it will work.
To sum up:
in this example:
module PurchasesHelper
def self.stripe_webhook(event)
#...
end
end
is equal to
module PurchasesHelper
def PurchasesHelper.stripe_webhook(event)
#...
end
end
which is why just adding self allows you to call PurchasesHelper.stripe_webhook
On Koudoku doc's, it says it actually uses stripe_event to handle that https://github.com/integrallis/stripe_event
So, looking on the strip_event examples, you can pass a block and do whatever you need or pass something that respond to the call method https://github.com/integrallis/stripe_event#usage

StandardError redirect to page

I was handed a project that another developer worked on, without leaving any documentation behind. The code fetches some purchases from a shopping website, looks for a price and notifies the user.
The app may encounter errors like "no results found" and then I raise a standarderror.
I want to redirect the user to the error page and notify them about it but I can't do that because it isn't a controller, so the redirect_to option doesn't work.
services/purchase_checker.rb is called once an hour:
def call
user.transaction do
store_purchase
if better_purchase?
update_purchase
end
end
rescue MyError=> e
store_error(e)
end
def store_error(error)
user.check_errors.create!(error_type: error.class.name, message: error.message)
end
services/my_error.rb:
class MyError< StandardError
def initialize(error_type, error_message)
super(error_message)
#error_type = error_type
end
attr_reader :error_type
end
services/purchase_fetcher.rb:
def parse_result_page
raise purchase_form_page.error if purchase_form_page.error.present?
offer = purchase_page.map{|proposal_section|
propose(proposal_section, purchase) }
.min_by(&:price)
offer or raise MyError.new("No results", "No results could be found")
end
you should create another err class, eg NotFoundError:
offer or raise NotFoundError.new("No results", "No results could be found")
then in your controller:
begin
parse_result_page
rescue NotFoundError => e
redirect_to err_page, :notice => e.message
end
Since this is running in a job, the best way to notify the user would be by email, or some other async notification method. When an error is detected, an email is sent.
If that's not an option for some reason, you can check if a user has check_errors in any relevant controllers. Looking at the store_error(error) method that is called when an error is found, it seems it's creating a new record in the Database to log the error. You should be able to check if a user has any error logged via the user.check_errors relationship.
You could do it like this, for example:
class SomeController < ActionController::Base
# ...
before_action :redirect_if_check_errors
# ...
def redirect_if_check_errors
# Assuming you're using Devise or something similar
if current_user && current_user.check_errors.exists?
redirect_to some_error_page_you_create_for_this_path
end
end
end
This will check for these errors in every action of SomeController and redirect the user to an error page you should create, where you render the errors in the user.check_errors relationship.
There are multiple ways to do this, but I still think sending an email from the Job is a better option if you want to actively notify the user. Or perhaps add an interface element that warns the user whenever user.check_errors has stuff there, for example.
I propose that you do this synchronously so that the response can happen directly in the request/response cycle. Perhaps something like this:
# controller
def search
# do your searching
# ...
if search_results.blank?
# call model method, but do it synchrously
purchase_check = PurchaseChecker.call
end
if purchase_check.is_a?(MyError) # Check if it's your error
redirect_to(some_path, flash: { warning: "Warn them"})
end
end
# model, say PurchaseChecker
def call
# do your code
rescue MyError => e
store_error(e)
e # return the error so that the controller can do something with it
end

Access a controller's instance variable from a block using instance_eval

I'm making a breadcrumb module for my Ruby on Rails application, but I wanted a specific syntax - which I thought was good looking and more intuitive for Rails developers.
Here's the deal:
class WelcomeController < ApplicationController
breadcrumb_for :index, :text => 'Home', :href => -> { root_path }
def index
end
end
See, it's neat.
You can safely ignore the everything else but that proc - what I assign to the :href key.
I use instance_eval so that when the proc is evaluated it has access to the root_path helper.
And it worked. The example above is okay. BUT then I wanted to use an instance variable and that didn't work.
Like this:
class WelcomeController < ApplicationController
breadcrumb_for :index, :text => 'Home', :href => -> { #path }
def index
#path = root_path
end
end
Now, in that proc context #path is nil.
What should I do so I can access the instance variables from the block ?
Below is all the code of my module. Note that when I "process" the blocks and use instance_eval (aka call my module's #breadcrumb) the action should already be evaluated so the instance variable #path should already exist.
module Breadcrumb
extend ActiveSupport::Concern
included do
cattr_accessor(:_breadcrumb) { [] }
helper_method :breadcrumb
def self.breadcrumb_for(*args)
options = args.pop
_breadcrumb.push([args, options])
end
end
def breadcrumb
#breadcrumb ||= self._breadcrumb.map do |item|
puts item
if item[0].include?(params[:action]) || item[0][0] == '*'
text, href = item[1].values_at(:text, :href)
if text.respond_to?(:call)
text = instance_eval(&text)
end
if href.respond_to?(:call)
href = instance_eval(&href)
end
[text, href]
end
end
end
end
Oh no. I'm ashamed to say but it was my mistake. The code above works just fine, I was using different variable names in my application, not shown in the excerpt I used in the question.
Thanks anyway, I'll left it here for reference.

Passing params in Rails helper class

I am trying to refactor my Rails helpers and move breadcrumbs and navigation menu logic into separate classes. But in these classes I don't have access to params, cookies hashes etc. I think that passing params on and on between different classes is a bad idea. How can I avoid that?
For example I have:
module NavigationHelper
def nav_item(name, path, inactive = false)
NavItem.new(params, name, path, inactive ).render
end
class NavItem
include ActionView::Helpers
include Haml::Helpers
def initialize(params, name, path, inactive )
init_haml_helpers
#params = params
#name = name
#path = path
#inactive = inactive
end
def render
capture_haml do
haml_tag :li, item_class do
haml_concat link_to #name, #path
end
end
end
def item_class
klass = {class: 'active'} if active?
klass = {class: 'inactive'} if #inactive
klass
end
# Class of the current page
def active?
slug = #path.gsub /\//, ''
#params[:page] == slug || #params[:category] == slug
end
end
end
I don't think Rails provide any mechanism to access Params out of ActionPack. The way you have done is seems correct to me. You have to pass on params, cookies atleast once to initialize your classes.

Pass url params hash in controller to another method, becomes nil

I have a route that matches /edit_account => accounts#edit since the account id isn't provided it's supposed to use the current user id and the account#edit method is shared with /accounts/[:id]/edit.
class AccountController < ApplicationController
...
def edit
# This doesn't work:
params = retrieve_id_if_missing(params)
# This works:
# aHash = params
# params = retrieve_id_if_missing(aHash)
end
def retrieve_id_if_missing(params)
# raise params.inpect => returns nil at this point
if params[:id].nil? and !logged_in?
redirect_to root_path
else params[:id].nil?
params[:id] = current_user.id
end
params
end
end
The problem I am having is that params, when passed to the class method, retrieve_id_if_missing, is becoming nil. However, if I assign params to another variable. e.g., aHash, before passing it to retrieve_id_if_missing it will contain the expected data, {"action" => "edit", "controller" => "account"}.
I've tried to search for a reason but have come up short, can someone explain to me why this is happening?
Have you tried
class AccountController < ApplicationController
...
def edit
retrieve_id_if_missing
end
def retrieve_id_if_missing()
if params[:id].nil? and !logged_in?
redirect_to root_path
else params[:id].nil?
params[:id] = current_user.id
end
params
end
end
I am fairly sure params will be in scope in the method.
Anyhow, check out gem devise for this. it should have everything you want and more
With devise you can just use
before_filer :authenticate_user!
At the top of your controller
https://github.com/plataformatec/devise
The Ruby interpreter is treating params as a local variable and initializing it with nil when it sees the assignment. This happens before it executes the retrieve_id_if_missing.
This is why explicitly assigning a value to the local variable before calling the method avoids the error, because the initialization to nil by Ruby doesn't happen.
The following examples demonstrate this:
Example #1
def foo(bar)
puts "foo bar: #{bar.class}"
end
bar = foo(bar) # => nil
puts "bar: #{bar.class}"
# Outputs:
# foo bar: NilClass
# bar: bar: NilClass
Example #2
a = a # => nil
puts "a: #{a.class}"
# Outputs:
# a: NilClass
Example #3
a = 123 if a # => nil
puts "a: #{a.class}"
# Outputs:
# a: NilClass
References:
Why is a = a nil in Ruby?
Ruby interpreter initializes a local variable with nil when it sees an
assignment to it. It initializes the local variable before it executes
the assignment expression or even when the assignment is not reachable
(as in the example below). This means your code initializes a with nil
and then the expression a = nil will evaluate to the right hand value.
a = 1 if false a.nil? # => true The first assignment expression is not
executed, but a is initialized with nil.
Ruby: method inexplicably overwritten and set to nil
Here's another example:
a = 123 if a # => nil a # => nil We shouldn't be able to say if a
because we never set a, but Ruby sees the a = 123 and initializes a,
then gets to if a at which point a is nil
I'd consider it a quirk of the interpreter, really. Gary Bernhardt
makes fun of it in wat (https://www.destroyallsoftware.com/talks/wat)
with a = a
While I cannot answer why your params object would be overridden with the code provided, here are some thoughts.
class AccountController < ApplicationController
before_filter :retrieve_id_if_missing, only: :edit
def edit
# You'll find params[:id] prepopulated if it comes here,
# else the request has been redirect
end
protected
# There should be no need to pass the params object around, it should be accessible everywhere
def retrieve_id_if_missing
if logged_in?
params[:id] ||= current_user.id # conditional assignment will only happen if params[:id] is nil
end
# Redirect to root if params[:id] is still blank,
# i.e. user is not logged in and :id was not provided through route
if params[:id].blank?
flash[:alert] = 'You need to be logged in to access this resource.'
return redirect_to root_url # early return!
end
end
end

Resources