undefined class/module X in Rails 3 despite requiring models in initializer - ruby-on-rails

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

Related

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

Uninitialized Constant error Loading a class/module in a Rails initializer

I'm working on integrating Stripe's webhooks into a Rails app using https://github.com/integrallis/stripe_event. I'm struggling to get my code to working according to the example in the gem's docs whereby an initializer is used to dictate which code responds to a particular event. It seems that Rails isn't (auto)loading my module in the initializer.
I'm configuring the autoload path properly:
# config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
The stripe initializer:
#config/initializers/stripe.rb
stripe_config = YAML.load_file(Rails.root.join('config', 'stripe.yml'))[Rails.env]
Stripe.api_key = stripe_config["secret_key"]
STRIPE_PUBLIC_KEY = stripe_config["publishable_key"]
StripeEvent.setup do
# Not sure if I need this to load my module
require 'stripe_event_handlers' # => true
subscribe 'customer.subscription.created' do |event|
StripeEventHanders.handle_customer_subscription_created(event) # Define subscriber behavior
end
end
Here's my custom module (though I've tried it as a class too):
#lib/stripe_event_handlers.rb
module StripeEventHandlers
def handle_customer_subscription_created(event) # Define subscriber behavior
puts event
end
end
This is my test:
require 'test_helper'
# --- Run this in the console to get event response for mocking ---
#serialized_object = YAML::dump(Stripe::Event.retrieve('evt_0Cizt88YP0nCle'))
#filename = Rails.root.join('test/fixtures/stripe_objects', 'customer_subscription_created.yml')
#File.open(filename, 'w') {|f| f.write(serialized_object) }
class StripeEvent::WebhookControllerTest < ActionController::TestCase
def test_mock_event
event_id = 'evt_0Cizt88YP0nCle'
event = YAML.load_file(Rails.root.join('test/fixtures/stripe_objects', 'customer_subscription_created.yml'))
Stripe::Event.expects(:retrieve).with(event_id).returns(event)
assert_equal Stripe::Event.retrieve(event_id), event
end
def test_customer_subscription_created_webhook
event_id = 'evt_0Cizt88YP0nCle'
event = YAML.load_file(Rails.root.join('test/fixtures/stripe_objects', 'customer_subscription_created.yml'))
Stripe::Event.expects(:retrieve).at_most(2).with(event_id).returns(event)
# This should be a raw post request but that doesn't seem to come through
# on the stripe_event / rails side in the params hash. For testing
# purposes, we can just use a get request as the route doesn't specify an
# HTTP method.
get :event, :use_route => :stripe_event, :id => event_id
assert_response :success
end
end
And here's my test result failure:
StripeEvent::WebhookControllerTest
ERROR (0:00:00.043) test_customer_subscription_created_webhook
uninitialized constant StripeEventHanders
# config/initializers/stripe.rb:10:in `block (2 levels) in <top (required)>'
PASS (0:00:00.053) test_mock_event
Finished in 0.055477 seconds.
2 tests, 1 passed, 0 failures, 1 errors, 0 skips, 2 assertions
You are just missing the letter l in StripeEventHandlers.
subscribe 'customer.subscription.created' do |event|
StripeEventHanders.handle_customer_subscription_created(event)
end
Also, handle_customer_subscription_created should be defined as a class method:
module StripeEventHandlers
def self.handle_customer_subscription_created(event) # Define subscriber behavior
puts event
end
end

Rails is re-parsing a ruby file in lib/ for every asset request

Rails version: 3.1
Ruby version: 1.9.2
In one of my current projects, I am getting dozens of "warning: already initialized constant" lines in the log for every request. The constant in question is in a module defined in lib.
I was entirely baffled as to why Rails is parsing this file up to 80 times per request, until I happened to turn on caching in development mode this morning (to test something else) and noticed that Rails is reporting a cache miss between each of those lines. This is the first 4 of ~80 such notifications when loading the homepage:
/Users/evan/Development/Ruby/apps/thefriendex/lib/feeds_mixin.rb:2: warning: already initialized constant DEFAULT_LIMIT
cache: [GET /assets/application.css?body=1] miss
/Users/evan/Development/Ruby/apps/thefriendex/lib/feeds_mixin.rb:2: warning: already initialized constant DEFAULT_LIMIT
cache: [GET /assets/_have_need_bar.css?body=1] miss
/Users/evan/Development/Ruby/apps/thefriendex/lib/feeds_mixin.rb:2: warning: already initialized constant DEFAULT_LIMIT
cache: [GET /assets/admin.css?body=1] miss
/Users/evan/Development/Ruby/apps/thefriendex/lib/feeds_mixin.rb:2: warning: already initialized constant DEFAULT_LIMIT
cache: [GET /assets/blurb_ad.css?body=1] miss
So it looks like it's re-parsing lib/feeds_mixin.rb again for each and every asset request. I used to have a config line adding lib/*.rb to the autoload paths, but I've removed that to no effect.
The module in question is just a straightforward module with one constant and three instance methods. It's mixed in to two controllers via a straightforward include, I can add that code if necessary.
Any thoughts on why this file would be getting reparsed every time an asset is requested?
CODE: The mixin (lib/feeds_mixin.rb)
module FeedsMixin
DEFAULT_LIMIT = 6
def friends_feed_items(opts = {})
# do stuff
end
def friends_of_friends_feed_items(opts = {})
# do stuff
end
def community_feed_items(opts = {})
# do stuff
end
def build_feed_hash(type, items, opts)
# do stuff
end
end
CODE: where it's included (lib/controllers/dashboard_controller.rb)
(one other controller is very similar to this one)
require 'feeds_mixin'
class DashboardController < ApplicationController
extend ActiveSupport::Memoizable
include FeedsMixin
layout 'dashboard'
before_filter :require_user, :set_feeds
LIMIT = 6
DEFAULT_DATE_THRESHHOLD = 14.days
def index
end
def set_feeds
#friends_feed = friends_feed_items(:since => since_date, :limit => LIMIT)
#friends_of_friends_feed = friends_of_friends_feed_items(:since => since_date, :limit => LIMIT)
#community_feed = community_feed_items(:since => since_date, :limit => LIMIT)
end
def since_date
DEFAULT_DATE_THRESHHOLD.ago
end
memoize :since_date
end

How to properly extend Devise Recoverable?

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.

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