How to correctly prepend module in a class? - ruby-on-rails

I have got a AccountController class, this controller class is in the application kernel. I dont want to make changes into kernel, therefore Im going to Monkey Patch it.
Controller has method named successful_authentication, which I've rewritten. Inside the new method (in my module) this code calls new method named load_favourite_or_index.
I've read that alias_method_chain is deprecated now and should not be used. I'm trying to prepend my module before AccountController. But nothing happens, I guess my prepending code is not correct, please could you help me? Here's my code.
# frozen_string_literal: true
module RedmineKapDesign
module Patches
module AccountControllerPatch
def self.prepended(base) # :nodoc:
class << base
prepend InstanceMethods
end
end
module InstanceMethods
def successful_authentication(user)
logger.info "Successful authentication for '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}"
# Valid user
self.logged_user = user
logger.info "Setting.autologin? = #{Setting.autologin?}, params[:autologin] = #{params[:autologin]}"
# generate a key and set cookie if autologin
if params[:autologin] && Setting.autologin?
set_autologin_cookie(user)
end
call_hook(:controller_account_success_authentication_after, {:user => user})
load_favourite_page_or_index
#redirect_back_or_default my_page_path
end
def load_favourite_page_or_index
user = User.current
favourite_page_field = CustomField.where(name: ["Favourite page", "favourite page", "favorite page", "Favourite page", "Любимая страница", "любимая страница", "Избранная страница", "избранная страница"]).first
page_url = user.custom_values.where(custom_field_id: favourite_page_field.id).first.value
if page_url.empty?
redirect_back_or_default my_page_path
else
redirect_to(page_url)
end
end
def self.hello
puts "Hello"
end
end
end
end
end
#unless AccountController.included_modules.include?(RedmineKapDesign::Patches::AccountControllerPatch)
# AccountController.send(:prepend, RedmineKapDesign::Patches::AccountControllerPatch)
#end
AccountController.singleton_class.prepend(RedmineKapDesign::Patches::AccountControllerPatch)

You don't need to separate out the instance methods into a seperate module. You can just place them in AccountControllerPatch and prepend whatever class you are monkeypatching with it.
module RedmineKapDesign
module Patches
module AccountControllerPatch
def successful_authentication(user)
logger.info "Successful authentication for '#{user.login}' from #{request.remote_ip} at #{Time.now.utc}"
# Valid user
self.logged_user = user
logger.info "Setting.autologin? = #{Setting.autologin?}, params[:autologin] = #{params[:autologin]}"
# generate a key and set cookie if autologin
if params[:autologin] && Setting.autologin?
set_autologin_cookie(user)
end
call_hook(:controller_account_success_authentication_after, {:user => user})
load_favourite_page_or_index
#redirect_back_or_default my_page_path
end
def load_favourite_page_or_index
user = User.current
favourite_page_field = CustomField.where(name: ["Favourite page", "favourite page", "favorite page", "Favourite page", "Любимая страница", "любимая страница", "Избранная страница", "избранная страница"]).first
page_url = user.custom_values.where(custom_field_id: favourite_page_field.id).first.value
if page_url.empty?
redirect_back_or_default my_page_path
else
redirect_to(page_url)
end
end
def self.hello
puts "Hello"
end
end
end
end
Using a seperate module is only really needed for class methods when using the module mixin pattern. In framework code using a seperate InstanceMethods module can be used for organizational purposes. But in a simple monkeypatch it just makes more of a mess.
# config/initializers/my_monkey_patch.rb
# You should explicity require classes in initializers
require Rails.root.join('path', 'to', 'monkeypatch')
require Rails.root.join('path', 'to', 'target')
::AccountController.prepend RedmineKapDesign::Patches::AccountControllerPatch

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

Unable to access the class in lib from helper module in rails

Hi I am working on rails app. In the function makeRequestToAPI1 of ArticlesHelper module, I am calling the API1 class's (this class is present in lib/) method "createRequest(request). But it is giving me "NoMethodError".
articles_helper.rb
require '././lib/ThirdPartyLibs/api1'
require '././lib/ThirdPartyLibs/api2'
module ArticlesHelper
include EnumsHelper
def makeRequestToAPI1(request)
# here as request to API is same as the original request
# so I am directly passing the request to the API
response = API1.createRequest(request)
Rails.logger.debug "makeRequestToAPI1: #{response}"
end
def makeRequestToAPI2(request)
requestToAPI2 = {}
requestToAPI2.merge!({:items => request[:items]})
requestToAPI2.merge!({:pickup => request[:pickup]})
requestToAPI2.merge!({:drop => request[:drop]})
#response = API2.createRequest(requestToAPI2)
#Rails.logger.debug "makeRequestToAPI2: #{response}"
end
end
api1.rb
class API1
#class << self
def self.createRequest(request)
#response = {:etd => 10, :eta => 20}
end
end
What am I missing here?
Can you please try adding following line to applicaion.rb
config.autoload_paths += %W(#{config.root}/lib)

Testing mixin in ruby

I'm trying to write a rspec test for a mixin class. I have the following.
module one
module two
def method
method_details = super
if method_details.a && method_details.b
something
elsif method_details.b
another thing
else
last thing
end
end
end
end
Now I have mocked the "method" object that will be passed to the class.
But I'm struggling to access the super method.
I did,
let(:dummy_class) { Class.new { include one::two } }
How to pass the mocked method object to this dummy class?
How do I go about testing this? New to ruby, can someone show me a direction with this.
Thanks in advance.
UPDATE:
I tried,
let(:dummy_class) {
Class.new { |d|
include one::two
d.method = method_details
}
}
let (:method_details){
'different attributes'
}
still doesn't work. I get undefined local variable or method method_details for #<Class:0x007fc9a49cee18>
I personally test mixing with the class. Because the mixing (module) itself has no meaning unless its attached to a class/object.
Ex:
module SayName
def say_name
p 'say name'
end
end
class User
include SayName
end
So I believe you should test your module with attached to the relevant class / object.
How ever this is a different perspective on testing mixings
HTH
I think that in your specs, you'll need to explicitly provide a super class definition for when super is called in #method as "you can't mock super and you shouldn't".
I've attempted to spec out all three of your scenarios with the following minor changes:
Changed your example code slightly to become valid Ruby
Changed #method to #the_method so it doesn't conflict with Object#method
Used OpenStruct to represent the object that super returns, because all I know is that it's an object that has methods #a and #b. You can change that out as appropriate for your real specs
Copy and paste the class and specs below into a file and give them a try:
module One
module Two
def the_method
method_details = super
if method_details.a && method_details.b
'something'
elsif method_details.b
'another thing'
else
'last thing'
end
end
end
end
RSpec.describe One::Two do
require 'ostruct'
let(:one_twoable) { Class.new(super_class) { include One::Two }.new }
describe '#the_method' do
let(:the_method) { one_twoable.the_method }
context 'when method_details#a && method_details#b' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: true, b: true)
end
end
end
it 'is "something"' do
expect(the_method).to eq('something')
end
end
context 'when just method#b' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: false, b: true)
end
end
end
it 'is "another thing"' do
expect(the_method).to eq('another thing')
end
end
context 'when neither of the above' do
let(:super_class) do
Class.new do
def the_method
OpenStruct.new(a: false, b: false)
end
end
end
it 'is "last thing"' do
expect(the_method).to eq('last thing')
end
end
end
end

Results outputting to console rather then browser.

This code is working correctly. The problem is everything is getting printed to the console. I want to show it on the browser. How to do that? Do I need a template called create.html.erb. How to access the variables and basically the whole controller code in the view? Please help!!
require File.join(Rails.root, 'config/myconfig')
puts Rails.root
class UsersController < ApplicationController
layout 'admin'
#require File.expand_path('././myconfig') #=> C:/ruby/require/expand_path/ok.rb loaded
def list
#users = User.all
end
def new
#user = User.new
end
def create
if EntityList::ENTITIES.include?(params[:user][:entity_name])
puts " Entered entity is: "
#entity = params[:user][:entity_name]
#var = EntityList::RELATION_SHIPS[#entity.to_sym]
puts "Entered entity is related to"
if(#var.nil?)
#do nothing
else
puts #var
checking(#var)
end
##var.split(" ")
##len = #var.length
#puts #var[0]
#puts #var[1]
#puts #var[1]
##var.each {|#var| puts #var}
#for index in 0 ... #var.size
# puts EntityList::RELATION_SHIPS[#var[index].to_sym]
# end
# #var.each_with_index {|val, index| puts "#{val} => #{index}" }
##var2= EntityList::RELATION_SHIPS[#var.to_sym]
#puts "Entity2 is related to"
#puts #var2
flash[:notice] = "The entity you entered is valid!!"
puts "Before Redirection"
redirect_to(:action => "helloworld")
puts "After redirection"
puts "done"
else
redirect_to(:action => "SorryPage")
end
end
def checking(array)
array.split(" ")
for index2 in 0 ... array.size
if EntityList::RELATION_SHIPS[array[index2].to_sym].nil?
# do nothing
else
puts EntityList::RELATION_SHIPS[array[index2].to_sym]
some = EntityList::RELATION_SHIPS[array[index2].to_sym]
checking(some)
end
end
end
end
Yes, you would create an create.html.erb and can access all Controller Instance variables like #entity or #var from there.
I'd recommend looking at the output from a generate scaffold call to get an example how this works, e.g. by calling it in a new rails app:
rails new tryout
cd tryout
rails generate scaffold User name:string email:string
and then look at the generated controller and view templates.

Semantic-Menu root bahaviour

require 'rubygems'
require 'action_view'
require 'active_support'
class MenuItem
include ActionView::Helpers::TagHelper,
ActionView::Helpers::UrlHelper
attr_accessor :children, :link
cattr_accessor :request
def initialize(title, link, level, link_opts={})
#title, #link, #level, #link_opts = title, link, level, link_opts
#children = []
end
def add(title, link, link_opts={}, &block)
returning(MenuItem.new(title, link, #level +1, link_opts)) do |adding|
#children << adding
yield adding if block_given?
end
end
def to_s
content_tag(:li, content_tag(:div, link_to(#title, #link, #link_opts), :class => "menu_header_level_"+#level.to_s) + child_output, ({:class => 'active'} if active?)).html_safe
end
def level_class
"menu_level_#{#level}"
end
def child_output
children.empty? ? '' : content_tag(:ul, #children.collect(&:to_s).join.html_safe, :class => level_class)
end
def active?
children.any?(&:active?) || on_current_page?
end
def on_current_page?
# set it for current_page? defined in UrlHelper
# current_page?(#link)
false
end
# def request
# ##request
# end
end
class SemanticMenu < MenuItem
def initialize(rq, opts={},&block)
##request = rq
#opts = {:class => 'menu'}.merge opts
#level = 0
#children = []
yield self if block_given?
end
def to_s
content_tag(:ul, #children.collect(&:to_s).join.html_safe, #opts).html_safe
end
end
Hello. I am trying to change the behaviour of the Semantic-Menu root. When I click one of the roots, the menu drops down and displays all the children. What I would like is happen is when I click, it goes to a default page and then display the children. Semantic-menu seems to allow links only to lower levels and not the main ones. Roots links only work when they don't have children.
The code below is the one that is in the plug-in in Ruby. and I think is the one that needs to be modified. There the html code but I don't think it has to do with it.
Can you please tell me what need to be added in other to make to father trigger their links?
Thank you.
I don't know the direct answer to your question, but SemanticMenu seems outdated.
Check out the SimpleNavigation gem: https://github.com/andi/simple-navigation/wiki

Resources