Passing params into MailView or ActionMailer::Preview in Ruby on Rails - ruby-on-rails

Is it possible when using the MailView gem or Rails 4.1 mail previews to pass parameters into the MailView? I would love to be able to use query string parameters in the preview URLs and access them in the MailView to dynamically choose which record to show in the preview.

I stumbled upon the same issue and as far as I understand from reading the Rails code it's not possible to access request params from mailer preview.
Crucial is line 22 in Rails::PreviewsController (email is name of the mailer method)
#email = #preview.call(email)

Still a relevant question and still very few solutions to be found on the web (especially elegant ones). I hacked my way through this one today and came up with this solution and blog post on extending ActionMailer.
# config/initializers/mailer_injection.rb
# This allows `request` to be accessed from ActionMailer Previews
# And #request to be accessed from rendered view templates
# Easy to inject any other variables like current_user here as well
module MailerInjection
def inject(hash)
hash.keys.each do |key|
define_method key.to_sym do
eval " ##{key} = hash[key] "
end
end
end
end
class ActionMailer::Preview
extend MailerInjection
end
class ActionMailer::Base
extend MailerInjection
end
class ActionController::Base
before_filter :inject_request
def inject_request
ActionMailer::Preview.inject({ request: request })
ActionMailer::Base.inject({ request: request })
end
end

Since Rails 5.2, mailer previews now have a params attr reader available to use inside your previews.
Injecting requests into your mailers is not ideal as it might lead to thread safety issues and also means your mailers won't work with ActiveJob & co

Related

Ruby on Rails folder structure with helpers

I have to make a rails API only i.e. input is POST request and output will be an JSON response. I have to interact with mysql database with my own table names i.e. mysql tables are already created.
Below is the folder structure with "helpers" even though we are not using any "views". We are accessing the helper methods from our controllers. Please confirm if I am correct or not. Thanks in advance.
1) app/controllers/application_controller.rb
class ApplicationController < ActionController::API
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
end
2) app/controllers/feature_management_controller.rb
class FeatureManagementController < ApplicationController
def populate_bean
#json = OrionCountryList.new.sample_function
end
def send_response(bean)
helper = FeatureManagementHelper.new
if (bean.method.eql?"get_feature_list") && (!bean.app_key.blank?) && (bean.app_key!=nil) && (bean.app_key.casecmp("NULL")!=0)
logger.info bean.print_bean "Request for fetching featureList by app_key : " + bean.app_key.to_s + " And userID: " + bean.user_id.to_s
##json_response = helper.get_feature_list bean
else
logger.error "METHOD NOT FOUND. method during feature management :"+bean.method+" app_key :"+bean.app_key
##json_response = {:message => "API not avaliable"}
end
logger.info("Final json_response sent to app : "+##json_response.to_json)
render :json => ##json_response
end
end
3) app/helpers/application_helper.rb
class ApplicationHelper
APP_CONFIG = YAML.load(File.read(File.expand_path('../../../config/app_config.yml', __FILE__)))
end
4) app/helpers/feature/feature_management_helper.rb
class FeatureManagementHelper
def get_feature_list(bean)
response = Hash.new
response = {:success_code => "1"}
return response
end
end
Here we are using "class" key word inside the helpers. But on searching, it seems "module" key word is needed. But we couldn't find the way to access module methods of helpers inside controllers.
Any help is appreciated.Thanks!!!
UPDATE
#Ekkerhard, Thanks for the suggestion,
I have refrained from using helpers in the above way mentioned and instead used PORO for implementing my business logic as suggested by #spikermann using
this_link
Upon implementing the changes, my code structure looks something like this:
1) app/controllers/feature_management_controller/feature_management.rb
class FeatureManagementController
class FeatureManagement
def get_feature_list(bean)
response = Hash.new
response = {:success_code => "1"}
return response
end
end
end
Similarly for any controller "test_controller" I have a folder named "test_controller" at the location /app/controllers/
and I am keeping the business logic inside a test.rb file inside this "test_controller" folder.
2) We have all the controllers inside the /app/controllers
3) We have all the models inside the /app/models
4) We are reading the configuration file inside /config/application.rb
class Application < Rails::Application
config.autoload_paths += Dir["#{config.root}/lib/**/"]
APP_CONFIG = YAML.load(File.read(File.expand_path('app_config.yml', __FILE__)))
config.time_zone = "New Delhi"
config.active_record.default_timezone = :local
config.autoload_paths += Dir["#{config.root}/app/**/"]
end
Though if I read the config file from the feature_management.rb file things are working just fine i.e. adding this line to the feature_management.rb file :
/app/controllers/feature_management_controller/feature_management.rb
APP_CONFIG = YAML.load(File.read(File.expand_path('../../../../config/app_config.yml',
__FILE__)))
but upon trying to read the configuration from the application.rb file I am getting an error :
NameError (uninitialized constant FeatureManagementController::FeatureManagement::APP_CONFIG):
I was wondering whether this is the correct way to proceed and is there a better way to do it.
Appreciate your inputs..!!!
If you need, you can include the module inside your controller and access the methods. Like:
include HelperModuleName
Frankly, to me it seems that you are trying to just "get by" with Rails here and are using other paradigms in a Rails environment. I don't think you're going to be happy with that in the long (or even short) term; if you use Rails that way it will just get in your way.
First of all, I would not use helpers in this way, at all, ever. In my opinion, helpers are there solely for cutting out "stupid" code from view templates, to cut down on "programming" clutter inside otherwise HTML'ish/JSON'ish templates. And they are definitely never domain methods. They do domain-agnostic stuff like render form elements, complex tables or things like that.
Just because you are not outputting HTML but JSON does not mean you have to ditch the MVC paradigm completely, like you are doing. There are JSON templates, see In Rails, how do you render JSON using a view? .
You are using a helper to load configuration. Configuration lives in config/application.rb, config/environments/*.rb, config/initializers/*.rb etc. - see http://guides.rubyonrails.org/configuring.html . You should never need to load the YAML in a helper.
Your controller code suggests that you do not use routes.rb to structure your requests. Branching like you have in your controller is a big smell for me. See http://guides.rubyonrails.org/routing.html .
Maybe not the answers you are looking for, but that's my 2€. :)

Class and module scope in rails helpers

I'm trying to write a rails app that creates an object in the controller based on a helper module, which is written below:
module StockPricesHelper
require 'net/http'
class Stock
attr_accessor(:data)
def initialize(stock)
#url = "http://finance.yahoo.com/d/quotes.csv?s=#{stock}&f=sb2b3jk"
end
def download_data
#data = NET::HTTP.get_response(URI.parse(#url)).body
end
def clean_string
#data = #data.strip
end
def db_format
1
end
end
end
I get an error uninitialized constant StockPricesHelper::Stock::NET from the rails server.
Am I correctly putting this in a helper module?
What am I doing wrong? I think I'm off on the scope but I don't know where.
You have misspelled the "NET" module. It is Net. (Ruby is case sensitive)
Rails helpers are intended to be view helpers, i.e. aid in generating HTML.
It looks like you are performing something which would be better placed in a controller or background job.

Rails - Send all emails with delayed_job asynchronously

I'm using delayed_job and I'm very happy with it (especially with the workless extension).
But I'd like to set that ALL mails from my app are sent asynchronously.
Indeed, the solution offered for mailers
# without delayed_job
Notifier.signup(#user).deliver
# with delayed_job
Notifier.delay.signup(#user)
doesn't suit me because:
it is not easily maintainable
mails sent from gems are not sent asynchronously (devise, mailboxer)
I could use this kind of extension https://github.com/mhfs/devise-async but I'd rather figure out a solution for the whole app at once.
Can't I extend ActionMailer to override the .deliver method (like here https://stackoverflow.com/a/4316543/1620081 but it is 4 years old, like pretty much all the doc I found on the topic)?
I'm using Ruby 1.9 and Rails 3.2 with activerecord.
Thanks for support
A simple solution would be to write a utility method on the Notifier object as follows:
class Notifier
def self.deliver(message_type, *args)
self.delay.send(message_type, *args)
end
end
Send the sign up email as follows:
Notifier.deliver(:signup, #user)
The utility method provides a single point where if needed you could replace delayed job with resque or sidekiq solutions.
If you have your ActiveJob and concurrency library set-up is done documentation here.The most simple solution is to override you device send_devise_notification instance methods involved with the transactions mails like shown here
class User < ApplicationRecord
# whatever association you have here
devise :database_authenticatable, :confirmable
after_commit :send_pending_devise_notifications
# whatever methods you have here
protected
def send_devise_notification(notification, *args)
if new_record? || changed?
pending_devise_notifications << [notification, args]
else
render_and_send_devise_message(notification, *args)
end
end
private
def send_pending_devise_notifications
pending_devise_notifications.each do |notification, args|
render_and_send_devise_message(notification, *args)
end
pending_devise_notifications.clear
end
def pending_devise_notifications
#pending_devise_notifications ||= []
end
def render_and_send_devise_message(notification, *args)
message = devise_mailer.send(notification, self, *args)
# Deliver later with Active Job's `deliver_later`
if message.respond_to?(:deliver_later)
message.deliver_later
# Remove once we move to Rails 4.2+ only, as `deliver` is deprecated.
elsif message.respond_to?(:deliver_now)
message.deliver_now
else
message.deliver
end
end
end

how do I test that an instance variable is set in my my mailer with rspec?

How do I test that a certain instance variable is set in my my mailer with rspec? assigns is coming back undefined..
require File.dirname(__FILE__) + '/../../spec_helper'
describe UserMailer do
it "should send the member user password to a User" do
user = FG.create :user
user.create_reset_code
mail = UserMailer.reset_notification(user).deliver
ActionMailer::Base.deliveries.size.should == 1
user.login.should be_present
assigns[:person].should == user
assigns(:person).should == user #both assigns types fail
end
end
The error returned is:
undefined local variable or method `assigns' for #<RSpec::Core::ExampleGroup::Nested_1:0x007fe2b88e2928>
assigns is only defined for controller specs and that's done via the rspec-rails gem. There is no general mechanism to test instance variables in RSpec, but you can use Kernel's instance_variable_get to access any instance variable you want.
So in your case, if object were the object whose instance variable you were interested in checking, you could write:
expect(object.instance_variable_get(:#person)).to eql(user)
As for getting ahold of the UserMailer instance, I can't see any way to do that. Looking at the method_missing definition inside https://github.com/rails/rails/blob/master/actionmailer/lib/action_mailer/base.rb, a new mailer instance will be created whenever an undefined class method is called with the same name as an instance method. But that instance isn't saved anywhere that I can see and only the value of .message is returned. Here is the relevant code as currently defined on github:
Class methods:
def respond_to?(method, include_private = false) #:nodoc:
super || action_methods.include?(method.to_s)
end
def method_missing(method_name, *args) # :nodoc:
if respond_to?(method_name)
new(method_name, *args).message
else
super
end
end
Instance methods:
attr_internal :message
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
# will be initialized according to the named method. If not, the mailer will
# remain uninitialized (useful when you only need to invoke the "receive"
# method, for instance).
def initialize(method_name=nil, *args)
super()
#_mail_was_called = false
#_message = Mail.new
process(method_name, *args) if method_name
end
def process(method_name, *args) #:nodoc:
payload = {
mailer: self.class.name,
action: method_name
}
ActiveSupport::Notifications.instrument("process.action_mailer", payload) do
lookup_context.skip_default_locale!
super
#_message = NullMail.new unless #_mail_was_called
end
end
I don't think this is possible to test unless Rails changes its implementation so that it actually provides access to the ActionMailer (controller) object and not just the Mail object that is generated.
As Peter Alfvin pointed out, the problem is that it returns the 'message' here:
new(method_name, *args).message
instead of just returning the mailer (controller) like this:
new(method_name, *args)
This post on the rspec-rails list might also be helpful:
Seems reasonable, but unlikely to change. Here's why. rspec-rails
provides wrappers around test classes provided by rails. Rails
functional tests support the three questions you pose above, but rails
mailer tests are different. From
http://guides.rubyonrails.org/action_mailer_basics.html: "Testing
mailers normally involves two things: One is that the mail was queued,
and the other one that the email is correct."
To support what you'd like to see in mailer specs, rspec-rails would
have to provide it's own ExampleGroup (rather than wrap the rails
class), which would have to be tightly bound to rails' internals. I
took great pains in rspec-rails-2 to constrain coupling to public
APIs, and this has had a big payoff: we've only had one case where a
rails 3.x release required a release of rspec-rails (i.e. there was a
breaking change). With rails-2, pretty much every release broke
rspec-rails because rspec-rails was tied to internals (rspec-rails'
fault, not rails).
If you really want to see this change, you'll need to get it changed
in rails itself, at which point rspec-rails will happily wrap the new
and improved MailerTestCase.

ActiveModel based class does not create the same results as an ActiveRecord equivilent

I am developing a Rails 3 app in a largely tabless capacity. I am using savon_model and ActiveModel to generate similar behaviour to ActiveRecord equivalents. Below is my code:
class TestClass
include Savon::Model
include ActiveModel::Validations
# Configuration
endpoint "http://localhost:8080/app/TestService"
namespace "http://wsns.test.com/"
actions :getObjectById, :getAllObjects
attr_accessor :id, :name
def initialize(hash)
#id = hash[:id]
#name = hash[:name]
end
client do
http.headers["Pragma"] = "no-cache"
end
def self.all
h = getAllObjects(nil).to_array
return convert_array_hash_to_obj(h, :get_all_objects_response)
end
def self.find(id)
h = getObjectById(:arg0 => id).to_hash
return convert_hash_to_obj(h, :get_object_by_id_response)
end
private
def self.convert_array_hash_to_obj(arrayhash, returnlabel)
results = Array.new
arrayhash.each do |hash|
results << convert_hash_to_obj(hash, returnlabel)
end
return results
end
def self.convert_hash_to_obj(hash, returnlabel)
return TestClass.new(hash[returnlabel][:return])
end
end
OK, so everything works as expected; values are pulled from the web service and onto the page. Unfortunately, when I look at the html produced at the client side there are some issues. The Show links are along the following lines:
/testclasses/%23%3CTestClass:0xa814cb4%3E
instead of...
/testclasses/1
So, I did a print of the object (hash?) to the console to compare the outputs.
[#<System:0xa814cb4 #id="1", #name="CIS">]
instead of what I believe it should be...
[#<System id="1", name="CIS">]
I have three questions:
1: What is the hex suffix on my class name when it is printed out
2: How can I modify my class to match the desired output when printed to the console?
3: Why are the frontend links (Show, Edit, Delete) broken and is there an easy fix?
Thanks so much for your time and apologies for rubbish code / stupid questions. This is my first Ruby or Rails app!
Gareth
The hex suffix is the object id of your instance of System
You can manipulate the output on the console by implementing an inspect instance method
The Rails url helpers use the to_param instance method to build these links. You should implement this if you are going to use your class as an ActiveRecord substitute.
Generally speaking, if you want to use all the Rails goodies with an own implementation of a model class, you should use ActiveModel:Lint::Test to verify which parts of the ActiveModel APIs are working as expected.
More information can be found here: http://api.rubyonrails.org/classes/ActiveModel/Lint/Tests.html

Resources