How to include Concern inside DeviseMailer - strange name_error - ruby-on-rails

I'm using engines inside my Rails 7 app. I've got Core engine where I can find a devise mailer (from devise_invitable gem). Think there is only 1 Devise application mailer possible, and I don't need a copy anything inside devise.rb.
My goal is to to override invitation_instructions method from Core engine into PaymentProcess engine. To do that I'll use concerns, so in a nutshell:
add a devise mailer concern inside payment_engine and include it to devise mailer inside payment_process/engine.rb initializer
overwrite invitation_instructions, but keep super when a user is not a payment_process user.
Here is my code:
engines/core/app/mailers/devise_application_mailer.rb
class DeviseApplicationMailer < Devise::Mailer
def invitation_instructions(recipient, *args)
account, = recipient.accounts
scope = 'devise.mailer.invitation_instructions'
#invited_by_text = invited_by_text(account, recipient.invited_by, scope)
mail = super
mail.subject = I18n.t('subject_with_account', account_name: account.name, scope:) if account.present?
mail
end
# other stuff (...)
end
engines/payment_process/app/mailers/concerns/devise_application_mailer_concern.rb:
module DeviseApplicationMailerConcern
extend ActiveSupport::Concern
included do
def invitation_instructions(resource, *args)
if resource.is_a?(PaymentProcess)
recipient, customer = resource
scope = 'devise.mailer.invitation_instructions'
#invited_by_text = invited_by_text(customer, recipient.invited_by, scope)
mail = super
mail.subject = I18n.t('subject', scope:) if customer.present?
mail
else
super
end
end
end
end
So now I want to include everything inside engine initializer:
engines/payment_process/lib/payment_process/engine.rb
module PaymentProcess
class Engine < ::Rails::Engine
engine_name 'payment_process'
isolate_namespace PaymentProcess
# some other stuff
config.to_prepare do
# some other stuff
Devise::Mailer.include(PaymentProcess::DeviseApplicationMailerConcern)
end
end
end
And all of these produces me an error of:
block in class:Engine': uninitialized constant PaymentProcess::DeviseApplicationMailerConcern (NameError)
Devise::Mailer.include(PaymentProcess::DeviseApplicationMailerConcern)
I've tried to require file path inside the engine but it won't help. I also tried to include concern directly inside the engines/core/app/mailers/devise_application_mailer.rb but I'm getting the same error.
What did I missed? or maybe it's not possible with to include anything to Devise ?

Related

How to extend middleware to custom classes outside the MVC scope (Rails)?

I am connecting to Shopify's API with the gem omniauth-shopify-oauth2.
In config/initializers/shopify.rb I am setting my OmniAuth::Builder like this:
Rails.application.config.middleware.use OmniAuth::Builder do
provider :shopify, "my_shopify_api_key", "my_shopify_secret",
scope: 'read_orders',
setup: lambda { |env| params = Rack::Utils.parse_query(env['QUERY_STRING'])
env['omniauth.strategy'].options[:client_options][:site] = "http://#{params['shop']}" }
end
Then from any controller file I can access Shopify's API like this:
omniauth = request.env['omniauth.auth']
if omniauth && omniauth[:provider] && omniauth[:provider] == "shopify"
token = omniauth['credentials'].token
session = ShopifyAPI::Session.new("#{username}.myshopify.com", token)
ShopifyAPI::Base.activate_session(session)
# Do stuff with the session
ShopifyAPI::Base.clear_session
end
If, on the other hand, I move the controller functionality to e.g. app/my_custom_folder/my_custom_file.rb I get an error in the very first line of code:
NameError (undefined local variable or method `request' for #<MyCustomClass:0x007fe07ec11f68>):
I assume that the error is raised because my custom classes and files can't access the middleware functionality. So how can I extend the functionality?
Rails auto-requires everything in certain directories, but my_custom_folder isn't one of those.
You could add something like this to config/application.rb
config.autoload_paths += %W(
#{config.root}/my_custom_folder
)
Or, you could drop my_custom_file.rb in a blessed location like app/controllers/concerns
ALSO:
It seems to me that the code you moved to my_custom_file.rb probably also expects to be inside a controller, which has access to request (through ActionController::Base).
Without knowing what my_custom_file.rb looks like, I would propose you set it up as an include like so:
# in app/controllers/concerns/my_custom_file.rb
module MyCustomFile
extend ActiveSupport::Concern
included do
# if necessary to run stuff on include:
# before_action :do_some_oauth_shopify_stuff
end
# This will be treated as part of all controllers now:
def do_some_oauth_shopify_stuff
# your code
end
end
# near top of app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include MyCustomFile
# rest of application_controller.rb
end
And maybe pick a better name for the module/file.

rails activerecord concern, including url helpers

Im trying to create an before_save callback for certain models that will add links and formatting to text and that gets saved in a special field. It won't let me include the URL helpers in the callback.
Here's my code:
module SocialText
extend ActiveSupport::Concern
included do
before_save :action_before_save
end
def action_before_save
self.body_formatted = htmlizeBody(self.body)
end
def htmlizeBody(body)
include Rails.application.routes.url_helpers
include ActionView::Helpers
#replace all \ns with <br>
body = body.gsub(/\n/, ' <br/> ')
words = body.split(/\s/)
words.map! do |word|
if word.first == '#'
username = extractUsernameFromAtSyntax word
user = User.find_by! username: username
if not user.nil?
link_to(word, profile_path(user.username))
else
word
end
else
word
end
end
words.join " "
end
def extractUsernameFromAtSyntax(username)
matchData = username.match(/#(\w+)(['.,]\w*)?/)
if not matchData.nil?
matchData[1]
else
username
end
end
end
I'm getting:
NoMethodError (undefined method `include`)
How do I get the helper? is there a better way to do this?
In your htmlizeBody function:
include Rails.application.routes.url_helpers
include ActionView::Helpers
This is in the wrong scope, moving it underneath extend ActiveSupport::Concern will resolve your error.
Another question you could ask yourself is why you need to use view helpers at the concern level?
include Rails.application.routes.url_helpers is usually used when modifying the default url host options (typically when you need to interface with an external API). In this case, it would make sense to use it in the /lib directory.
See this SO post and this post on using url helpers for more info
include operates on a class instance object and you call it like an instance method.
You should take that include part outside of your methods.
Consider using require at your module scope.

How to add attribute to existing Notifications payload?

In Rails notifications, I am subscribing to "process_action.action_controller", and would like to add more attributes to the payload. How can I do that?
I have tried using append_info_to_payload, but this seems to do nothing.
module AppendExceptionPayload
module ControllerRuntime
extend ActiveSupport::Concern
protected
def append_info_to_payload(payload)
super
payload[:happy] = "HAPPY"
end
end
end
The subscription and above code is in a Rails engine, so this is where I make the call to add it:
require 'append_exception_payload'
module Instrument
class Engine < ::Rails::Engine
ActiveSupport.on_load :action_controller do
include AppendExceptionPayload::ControllerRuntime
end
end
end
After putting up the bounty, I found a solution myself. Rails handles this really cleanly.
Basically, the append_info_to_payload method is meant exactly for this.
So to include session information and signed_in user information I added this to my application_controller.rb:
def append_info_to_payload(payload)
super
payload[:session] = request.session_options[:id] rescue ""
payload[:user_id] = session[:user_id] rescue "unknown"
end
So i jumped in and had a look at the api for the process_action method (private) and the append_info_to_payload instance method (public) and the proccess action method seems to call append_info_to_payload in its code like so:
ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
result = super
payload[:status] = response.status
append_info_to_payload(payload)
result
end
and append_info_to_payload works something like this
def append_info_to_payload(payload) #:nodoc:
payload[:view_runtime] = view_runtime
end
I can suggest trying payload[:view_runtime] instead of payload[:happy] or trying to use payload[:status]
Let me know how you get on and I will try help more, unfortunately there is really no documentation for this stuff.

Ruby - How to access module's methods?

I'm installing a forum using the Forem gem. There's an option that allows avatar personalization, since it's possible to login with Facebook. You just specify your method in the User model and that's it.
# Forem initializer
Forem.avatar_user_method = 'forem_avatar'
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
end
end
But I want a fallback on Gravatar for normal, non-facebook accounts. I've found the method on Forem and in theory, I need to call the avatar_url method:
# User model
def forem_avatar
unless self.user_pic.empty?
self.user_pic
else
Forem::PostsHelper.avatar_url self.email
end
end
However, Forem isn't an instance, but a module and I can't call it nor create a new instance. The easy way is to copy the lines of that method, but that's not the point. Is there a way to do it?
Thanks
Update
Both answers are correct, but when I call the method either way, there's this undefined local variable or method 'request' error, which is the last line of the original avatar_url.
Is there a way to globalize that object like in PHP? Do I have to manually pass it that argument?
perhaps reopen the module like this:
module Forem
module PostsHelper
module_function :avatar_url
end
end
then call Forem::PostsHelper.avatar_url
if avatar_url call other module methods, you'll have to "open" them too via module_function
or just include Forem::PostsHelper in your class and use avatar_url directly, without Forem::PostsHelper namespace
If you want to be able to use those methods in the user class, include them and use
class User < ActiveRecord::Base
include Forem::PostsHelper
def forem_avatar
return user_pic if user_pic.present?
avatar_url email
end
end
Another way would be to set the Forem.avatar_user_method dynamically since the Forem code checks it it exists before using it and defaults to avatar_url if it does not.
class User < ActiveRecord::Base
# This is run after both User.find and User.new
after_initialize :set_avatar_user_method
# Only set avatar_user_method when pic is present
def set_avatar_user_method
unless self.user_pic.empty?
Forem.avatar_user_method = 'forem_avatar'
end
end
def forem_avatar
self.user_pic
end
end
This way you dont pollute your model with unnecessary methods from Forem and don't monkey patch Forem itself.

Can't access model from inside ActionController method added by a Rails engine

I am developing a Rails engine to be packaged as a gem. In my engine's main module file, I have:
module Auditor
require 'engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
require 'application_controller'
end
module ActionController
module Auditor
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def is_audited
include ActionController::Auditor::InstanceMethods
before_filter :audit_request
end
end
module InstanceMethods
def audit_request
a = AuditorLog.new
a.save!
end
end
end
end
ActionController::Base.send(:include, ActionController::Auditor)
where AuditorLog is a model also provided by the engine. (My intent is to have "is_audited" added to the controllers in an application using this engine which will cause audit logging of the details of the request.)
The problem I have is that when this code gets called from an application where the engine is being used, the AuditorLog model isn't accessible. It looks like Ruby thinks it should be a class in ActionController:
NameError (uninitialized constant
ActionController::Auditor::InstanceMethods::AuditorLog)
rather than a model from my engine.
Can anyone point me in the right direction? This is my first time creating an engine and attempting to package it as a gem; I've searched for examples of this and haven't had much luck. My approach to adding this capability to the ActionController class was based on what mobile_fu does, so please let me know if I'm going about this all wrong.
Use ::AuditorLog to access the ActiveRecord class (unless you have it in a module or namespace, in which case you'll need to include the module name).

Resources