How to properly extend Devise Recoverable? - ruby-on-rails

I want Recoverable module to send "invite" emails with reset password links for my users (the app is on invite only), I partially declare methods in initializer:
module Devise
class Mailer
def invite_new_user(record)
devise_mail(record, :invitation_instructions)
end
end
end
module Devise
module Models
module Recoverable
def send_invite_user_instructions!
return unless status == User::STATUS_PENDING
generate_reset_password_token!
::Devise.mailer.invite_new_user(self).deliver
end
end
end
end
And recoverable is extended nicely, but it says that my mailer does not have invite_new_user method (rails console output):
1.9.2p290 :002 > user.send_invite_user_instructions!
User Load (1.4ms) SELECT "users".* FROM "users" WHERE "users"."reset_password_token" = 'zMQK1CEXYupjNKpH8dph' LIMIT 1
(0.3ms) BEGIN
(15.0ms) UPDATE "users" SET "reset_password_token" = 'zMQK1CEXYupjNKpH8dph', "updated_at" = '2012-05-01 17:40:32.085256' WHERE "users"."id" = 59
(4.5ms) COMMIT
NoMethodError: undefined method `invite_new_user' for Devise::Mailer:Class
but calling has method in the same console session:
1.9.2p290 :003 > ::Devise.mailer.method_defined? 'invite_new_user'
=> true
What am I missing?

Devise can be set-up for what you need:
1- Create a Mailer class in app/mailers/auth_mailer.rb file and make it inherit from Devise::Mailer
class AuthMailer < Devise::Mailer
def invite_new_user(record)
devise_mail(record, :invitation_instructions)
end
end
2- Instruct Devise to use your class by editing config/initializers/devise.rb file and adding
config.mailer = 'AuthMailer'
3- (optional) If (and only if) you use a delay email sending such as SideKiq or DelayedJob you may need to eager load in development, or the delayed job may not find your AuthMailer class. In config/environments/development.rb
config.eager_load = true
4- I personally would define your send_invite_user_instructions! method in my User class instead of patching Devise class
Side note: I'm not a big fan or doing a partial declaration of a class in Rails initializer, because depending on how the gem is designed you can have trouble with autoload : There is a gem (Monologue) that reload objects during run time, without running the initializers, so the monkey patch works well on the first call, but not on the next calls.

Related

Rails - conditionally log for a specific hostname

I'm looking for a way to configure a Rails server log only if the client has contacted a specific hostname. e.g. I could make it so that http://public.example.com doesn't get logged, but http://debug.example.com (same underlying Rails app server) does get logged (or ideally gets logged in more detail than the regular host). It would help with production debugging.
You can use gem Lograge to customize your log. This gem will give you much more custom to your log. For example, in your case, I will do this
After install the gem. Create a file at config/initializers/lograge.rb
# config/initializers/lograge.rb
Rails.application.configure do
config.lograge.enabled = true
config.lograge.custom_options = lambda do |event|
# custom log on specific domain
if event.payload[:host] == "debug.example.com"
{:host => event.payload[:host]}
else
{}
end
end
end
And in your Application Controller
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# This will add request's host to lograge so you can use it to filter log later
def append_info_to_payload(payload)
super
payload[:host] = request.host
end
end
Now you can customize your log base on domain, on how to customize it please read at: https://github.com/roidrage/lograge

"Unable to autoload constant User" error when changed code in development

I have a problem with my rails application. After an Update from Rails 3 to 4.
When I surf through the pages after starting the server in development mode everything is fine.
But after a single code change (even adding a space) every page request shows the following error.
Unable to autoload constant User, expected
/path/to/my/rails-app/app/models/user.rb to define it
The file lives exactly there and defines the class:
class User < ActiveRecord::Base
…
I tried many things with config.autoload_paths and config.eager_load_paths in application.rb but with no luck.
Deactivating spring did not help either.
Developing an app and having to restart the server after every single change seems so 90s.
$ rails -v
Rails 4.2.4
$ ruby -v
ruby 2.1.7p400 (2015-08-18 revision 51632) [x86_64-linux]
Some relevant configs:
development.rb
MyApp::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false
# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
config.eager_load = false
# Show full error reports and disable caching
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false
# Print deprecation notices to the Rails logger
config.active_support.deprecation = :log
# Only use best-standards-support built into browsers
config.action_dispatch.best_standards_support = :builtin
# Do not compress assets
config.assets.compress = false
# Expands the lines which load the assets
config.assets.debug = true
config.action_mailer.delivery_method = :test
config.action_mailer.default_url_options = {
host: 'localhost',
port: 3000
}
end
application.rb
module Serviceportal
class Application < Rails::Application
# Enable the asset pipeline
config.assets.enabled = true
# Version of your assets, change this if you want to expire all your assets
config.assets.version = '1.0'
[… some asset precompile stuff …]
# Configure the default encoding used in templates for Ruby 1.9.
config.encoding = 'utf-8'
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += Dir["#{config.root}/app/mailers",
"#{config.root}/app/controllers/concerns",
"#{config.root}/app/models/concerns",
"#{config.root}/app/decorators/concerns",
"#{config.root}/lib",
"#{config.root}/lib/shared"
]
config.eager_load_paths += Dir["#{config.root}/app/mailers",
"#{config.root}/app/controllers/concerns",
"#{config.root}/app/models/concerns",
"#{config.root}/app/decorators/concerns",
"#{config.root}/lib",
"#{config.root}/lib/shared"]
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
config.time_zone = 'Berlin'
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
config.i18n.default_locale = :de
[… some SQL and active support stuff …]
config.action_controller.include_all_helpers = false
config.action_controller.action_on_unpermitted_parameters = :raise
# Do not swallow errors in after_commit/after_rollback callbacks.
config.active_record.raise_in_transactional_callbacks = true
end
end
Edit: The error mostly shows up in lib/auth/user_proxy.rb in the following function. Maybe this helps to narrow the range of possible causes.
def self.usertype_allowed?(type)
[ User, TempCustomer ].include? type.classify.safe_constantize rescue false
end
Edit 2: Stringify the class names in Edit 1 helped (thanks #Benjamin Sinclaire). But only leads to the next errors. I could also avoid using classes. But at the following error in app/controllers/concerns/security.rb there is nothing can change?
Unable to autoload constant User, expected
/path/to/my/rails-app/app/models/user.rb to define it
code:
def set_current_user
User.current = current_user
end
with current user saved in the Thread (code from /path/to/my/rails-app/app/models/user.rb
def self.current
Thread.current['current_user']
end
def self.current=(user)
Thread.current['current_user'] = user
end
Just to make it clear again: It works after server restart in development until I change some code somewhere.
1 See if you have any multiple-level class or module declaration done one one line and change them to be declared in several lines.
Instead of
class Parent::Sub::Child
end
Do
module Parent
module Sub
class Child
end
end
end
2 Check your model association definitions, and ensure you are never using constant. Use string instead.
Instead of
belongs_to :manager, class_name: User
Do
belongs_to :manager, class_name: 'User'
3 Just saw your edit. Can you refactor like this?
# I assume `type` is a string or such, so we can compare classes
# names instead of constants, and get rid of `safe_constantize`
def self.usertype_allowed?(type)
['User', 'TempCustomer'].include? type.classify rescue false
end
4 Not a good idea to serialize an active record object in the Thread storage. Change it to store the user id instead, like this:
def set_current_user
User.current = current_user.id
end
def self.current
Thread.current['current_user_id']
end
def self.current=(user_id)
Thread.current['current_user_id'] = user_id
end
You don't need include app/models/concerns and app/controllers/concerns in your autoload/ eagerload paths as they are included by default in Rails 4: https://signalvnoise.com/posts/3372-put-chubby-models-on-a-diet-with-concerns
Also make sure that your concerns are defined as modules, extend ActiveSupport::Concern and with the appropriate file name
#taggable.rb
module Taggable
extend ActiveSupport::Concern
end
Another cause of your problem might be that some modules/ classes in app/decorators/concerns, lib, lib/shared are using the User class
which is not loaded yet or some of it's dependencies are not loaded so try adding require_relative path_to_user.rb at the top of those files
-----Edit-------
Try adding at the top of lib/auth/user_proxy.rb
require_dependency 'app/models/user'
This way you'll remove any ambiguity in autoloading the User class and you won't mess around with Rails autoloading see more here: http://guides.rubyonrails.org/autoloading_and_reloading_constants.html#require-dependency , http://guides.rubyonrails.org/autoloading_and_reloading_constants.html#common-gotchas
Same problem but in an engine w/ namespaces. No issues in production or in development until a code-change / autoload.
The solution was to
checking for double definitions (there were none)
checking if the module nesting strictly follows rails conventions in the filesystem.
I've had myns under myengine/app/myns/subns/obj.rb but myns is being ignored as it is at the root of the app folder, so moving the myns folder into a subfolder myengine/app/lib/myns solved the issue.
Note: the rails error message was very explicit about the module nesting (while still pointing to the wrong .rb file in the filesystem) so look closely at the error. The error was 'Unable to autoload constant subns/obj.rb in .../myns/subns/obj.rb'. Rails suggesting the incorrect file-location (which exists) is misleading in this case.
During a Rails/Ruby Update I found time to look into this and finally found the cause.
The user class had an unloadable in it for years. That caused the problems since Rails 4. Now I removed this and found no issues after that.

Rendering partials / view in a rake task / background job / model in Rails 4

I've read a lot on rendering Rails partials and views in rake tasks / background jobs / models. The vast majority of things I have found on Stackoverflow and the web describe approaches working in Rails 3, but they seem outdated and I didn't get them to work (even with quite some time spent experimenting).
So, how can I render a partial in a background job in Rails 4?
Here's the best approach I've worked out so far (demonstrated in the console).
c = ApplicationController.new
result = c.render_to_string(partial: 'tweets/tweet', locals: {tweet: Tweet.first})
# =>
# Tweet Load (0.8ms) SELECT "tweets".* FROM "tweets" ORDER BY "tweets"."id" ASC LIMIT 1
# Author Load (0.6ms) SELECT "authors".* FROM "authors" WHERE "authors"."id" = $1 ORDER BY "authors"."id" ASC LIMIT 1 [["id", 1]]
# Status Load (0.6ms) SELECT "statuses".* FROM "statuses" WHERE "statuses"."twitter_id" = 367523226848866304 LIMIT 1
# Rendered tweets/_tweet_body.html.slim (17.5ms)
# Rendered tweets/_resolved_tweet.html.slim (23.7ms)
# Rendered tweets/_tweet.html.slim (28.1ms)
# ActionView::Template::Error: undefined method `tweet_path' for #<#<Class:0x007fb21bf797a0>:0x007fb21cb009e8>
# from /Users/thomasklemm/.rbenv/versions/2.0.0-p195/lib/ruby/gems/2.0.0/gems/actionpack-4.0.0/lib/action_dispatch/routing/polymorphic_routes.rb:129:in `polymorphic_url'
Any ideas? Thanks in advance!
Update: The tweet_path mentioned above is indeed not defined. This error resulted from linking to a path = link_to 'Tweet', [#project, tweet] (slim templates) using an instance variable that would be present in views inheriting from a certain controller, but not when rendered outside of this context. I solved this going through the appropriate association instead = link_to 'Tweet', [tweet.project, tweet].
Here's what I compiled from lots of sources and what works for me in Rails 4.
With this Renderer class, you should be able to render Rails 4 views and partials in any context, like background jobs, service objects, models, workers, you name it.
# app/services/renderer.rb
# Render views and partials in rake tasks,
# background workers, service objects and more
#
# Use:
#
# class MyService
# def render_stuff
# result = renderer.render(partial: 'tweets/tweet', locals: {tweet: Tweet.first})
# # or even
# result = renderer.render(Tweet.first)
# end
#
# private
#
# def renderer
# #renderer ||= Renderer.new.renderer
# end
# end
#
class Renderer
def renderer
controller = ApplicationController.new
controller.request = ActionDispatch::TestRequest.new
ViewRenderer.new(Rails.root.join('app', 'views'), {}, controller)
end
end
# app/services/view_renderer.rb
# A helper class for Renderer
class ViewRenderer < ActionView::Base
include Rails.application.routes.url_helpers
include ApplicationHelper
def default_url_options
{host: Rails.application.routes.default_url_options[:host]}
end
end
Update:
There seems to be an easier solution: http://makandracards.com/makandra/17751-render-a-view-from-a-model-in-rails
ApplicationController.new.render_to_string(
:template => 'users/index',
:locals => { :#users => #users }
)
# Mind the weird syntax to set # variables in :locals.
Update 2:
There's a gem called render_anywhere that allows for calling "render" from anywhere: models, background jobs, rake tasks, etc.
Update 3:
In Rails 5, the renderer has been extracted and can be used standalone from background jobs and other places:
ApplicationController.renderer.render(
partial: 'messages/message',
locals: { message: message }
)
For Rails <= 4.2, this functionality can be backported with the backport_new_renderer gem.
Make sure you're loading the rails environment in the job. If that is already done you can try something like:
include Rails.application.routes.url_helpers

undefined class/module X in Rails 3 despite requiring models in initializer

We use Rails.cache and see an undefined class/module X error when loading pages that invoke class X. We followed the suggestion here to include an initializer that requires all models in the dev environment.
However, we see the same error. Any other suggestions? We included the initializer code below, and from the output to the console, it appears like the code is getting invoked.
We're on Rails 3.2.12 + Ruby 1.9.3.
if Rails.env == "development"
Dir.foreach("#{Rails.root}/app/models") do |model_name|
puts "REQUIRING DEPENDENCY: #{model_name}"
require_dependency model_name unless model_name == "." || model_name == ".."
end
end
Stack trace:
Processing by DandyController#get_apps as JSON
Parameters: {"category"=>"featured", "country_id"=>"143441", "language_id"=>"EN"}
WARNING: Can't verify CSRF token authenticity
Completed 500 Internal Server Error in 2ms
ArgumentError (undefined class/module App):
app/controllers/dandy_controller.rb:66:in `get_featured_apps'
app/controllers/dandy_controller.rb:50:in `get_apps'
Rendered C:/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/middleware/templat
es/rescues/_trace.erb (2.0ms)
Rendered C:/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/middleware/templat
es/rescues/_request_and_response.erb (2.0ms)
Rendered C:/RailsInstaller/Ruby1.9.3/lib/ruby/gems/1.9.1/gems/actionpack-3.2.12/lib/action_dispatch/middleware/templat
es/rescues/diagnostics.erb within rescues/layout (46.0ms)
Code:
def get_featured_apps( country_id )
# Fetch featured apps from cache or from DB
apps = Rails.cache.fetch( 'featured_apps', {:expires_in => 1.days} ) do
logger.info '+++ Cache miss: featured apps'
get_featured_apps_helper country_id
end
# Get sponsored results
apps = get_featured_apps_helper country_id
# Return featured apps
return apps
end
I've overwritten the fetch method which worked wonders for me!
unless Rails.env.production?
module ActiveSupport
module Cache
class Store
def fetch(name, options = nil)
if block_given?
yield
end
end
end
end
end
end
A more elegant solution is to write a wrapper method you can call around your call to Rails.cache.fetch:
def some_func
apps = with_autoload_of(App) do
Rails.cache.fetch( 'featured_apps', {:expires_in => 1.days} ) do
logger.info '+++ Cache miss: featured apps'
get_featured_apps_helper country_id
end
end
end
##
# A simple wrapper around a block that requires the provided class to be
# auto-loaded prior to execution.
#
# This method must be passed a block, which it always yields to.
#
# This is typically used to wrap `Rails.cache.fetch` calls to ensure that
# the autoloader has loaded any class that's about to be referenced.
#
# #param [Class] clazz
# The class to ensure gets autoloaded.
#
def self.with_autoload_of(clazz)
yield
end

Rails dependencies issue when in production mode

Im working on a legacy oracle database with a slightly odd table naming convention where each column name is prefixed with the tables initial letters - eg policy.poli_id.
To make this database easier to work with I have a method set_column_prefix that creates accessors for each column with the prefix removed. ie:
# Taken from wiki.rubyonrails.org/rails/pages/howtouselegacyschemas
class << ActiveRecord::Base
def set_column_prefix(prefix)
column_names.each do |name|
next if name == primary_key
if name[/#{prefix}(.*)/e]
a = $1
define_method(a.to_sym) do
read_attribute(name)
end
define_method("#{a}=".to_sym) do |value|
write_attribute(name, value)
end
define_method("#{a}?".to_sym) do
self.send("#{name}?".to_sym)
end
end
end
end
end
This is in a file (insoft.rb) in my lib/ directory, and required from from my config/environment.rb after the Rails::Initializer.run block.
This has been working fine in development, but when I try to run the application in production mode, I get the following error in all of my models:
dgs#dgs-laptop:~/code/voyager$ RAILS_ENV=production script/server
=> Booting Mongrel
=> Rails 2.3.2 application starting on http://0.0.0.0:3000
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.2/lib/active_record/base.rb:1964:in `method_missing':
undefined method `set_column_prefix' for #<Class:0xb3fb81d8> (NoMethodError)
from /home/dgs/code/voyager/app/models/agent.rb:16
This error is triggered by the 'config.cache_classes = true' line in config/environments/production.rb.
If I set this to false, then rails will start up, but won't be caching classes. I'm guessing this makes rails cache all the models before it runs the Initializer block
If I move the 'require "insoft.rb'" to before the start of the Rails::Initializer.run block, then I get errors because ActiveRecord hasn't been initialized yet:
usr/lib/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/dependencies.rb:443:in `load_missing_constant': uninitialized constant ActiveRecord (NameError)
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/dependencies.rb:80:in `const_missing'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.3.2/lib/active_support/dependencies.rb:92:in `const_missing'
from /home/dgs/code/voyager/lib/insoft.rb:1
Where should I be including this custom lib and set_column_prefix method in order for it to be picked up before the models are cached, but after all the activerecord files have loaded?
Cheers
Dave Smylie
Where should I be including this custom lib and set_column_prefix method in order for it to be picked up before the models are cached, but after all the activerecord files have loaded?
Try setting up an initializer. You can call it config/initializers/insoft.rb with the contents of your monkey patch:
class << ActiveRecord::Base
def set_column_prefix(prefix)
column_names.each do |name|
next if name == primary_key
if name[/#{prefix}(.*)/e]
a = $1
define_method(a.to_sym) do
read_attribute(name)
end
define_method("#{a}=".to_sym) do |value|
write_attribute(name, value)
end
define_method("#{a}?".to_sym) do
self.send("#{name}?".to_sym)
end
end
end
end
end

Resources