How to autoload files in folder under rails app's root - ruby-on-rails-3

I am trying to have files under myapplication/somefolder. Google and Stackoverflow say I should add this:
config.autoload_paths += %W(#{config.root}/somefolder)
in my config/application.rb, so I did.
But the files don't get loaded.
I tried namig somefolder/myclass.rb both class Myclass and class Somefolder::Myclass but still no luck.
I can see that the dir was found in Rails.application.config.autoload_paths in console does indeed include my /path/to/myapplication/somefolder directory, so that should be okay.
All the other questions around this topic use theapp/app/somefolder or theapp/lib/somefolder but not theapp/somefolder so maybe thats where it gets rotten.
So I tried referencing the class with ::Somefolder::MyClass but not even that helped.
I am using Rails 3.2.1

Just ran into this myself today and decided to dive deep.
The reason you don't see in ActiveSupport::Dependencies.autoload_paths the paths you're adding to config.autoload_paths in config/application.rb is that they aren't copied over until the application is initialized. See rails/engine.rb in the railties gem:
module Rails
class Engine < Railtie
…
# Set the paths from which Rails will automatically load source files,
# and the load_once paths.
#
# This needs to be an initializer, since it needs to run once
# per engine and get the engine as a block parameter
initializer :set_autoload_paths, :before => :bootstrap_hook do |app|
ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths)
# Freeze so future modifications will fail rather than do nothing mysteriously
config.autoload_paths.freeze
config.eager_load_paths.freeze
config.autoload_once_paths.freeze
end
…
def _all_autoload_paths
#_all_autoload_paths ||= (config.autoload_paths + config.eager_load_paths + config.autoload_once_paths).uniq
end
…
end
end
Were you by any chance trying to invoke MyClass from within config/application.rb or, even earlier, from a script or module that requires config/application.rb? If so, you'll have to explicitly require the file defining MyClass, e.g.:
require File.expand_path('../../somefolder/my_class', __FILE__)
# now use MyClass

The workaround is to go to ActiveSupport::Dependencies.autoload_paths directly.
ActiveSupport::Dependencies.autoload_paths << "#{config.root}/somefolder"
But I am still looking for the reason why config.autoload_paths didn't work so if you post an aswer to that, I'll accept it!

You should name somefolder/my_class.rb in order to autoload MyClass. You also should keep that config.autoload_paths += %W(#{config.root}/somefolder) line in your config/application.rb.

Related

How do I include types from a gem with sigs?

Let's say I have a shared gem called thing that gets used in multiple other projects, like example-project. I want to use sorbet in my thing gem to take advantage of the type checking. How do I get my consuming projects to check against the types in my gem?
For example, the code might look like this
thing gem
# typed: strict
require 'sorbet-runtime'
class Thing
extend T::Sig
sig { params(phrase: String).returns(String) }
def say(phrase)
"Thing said: '#{phrase}'"
end
end
example-project
# typed: strict
require 'thing'
Thing.new.say(1) # Should cause a type error
What happens
After running srb rbi update, I get an sorbet/rbi/gems/thing.rbi
class Thing
def say(*args, &blk); end
extend T::Private::Methods::MethodHooks
extend T::Private::Methods::SingletonMethodHooks
extend T::Sig
end
This doesn't have the sigs from the gem that I'm expecting, and srb tc doesn't raise the error I'm expecting.
Other things I've tried
Including rbi/thing.rbi in the gem
This works, but I'd rather write my sigs inline in the gem code than maintain separate rbi files. Is there any tooling that could take the .rb files with sigs and output the .rbi files (and maybe strip the sigs from the .rb files so them gem can be distributed without sorbet). I'm thinking of a workflow similar to typescript's tsc --declaration, which goes from .ts => .d.ts + .js.
https://github.com/AaronC81/brain_freeze
is a new project that aims to make it possible to generate an RBI file from RB files with inline declarations. It's still early, but might be worth trying out

Resque and Resque_mailer with Devise

I am implementing background email processing with Resque using the resque_mailer gem (https://github.com/zapnap/resque_mailer). I was able to get it to work for all my emails except the ones sent by Devise.
I went through a bunch of SO questions, and blog posts (for instance http://teeparham.posterous.com/send-devise-emails-with-resque) but could not find a way to get it to work.
What are the precise steps to follow to get resque_mailer to work with Devise?
I went through tee's answer and several resources online, but couldn't find a working solution.
After a few days of reading through resque-mailer and devise code, a solution that worked for me. Thanks to tee for gist which put me in right direction.
Assuming your app/mailers/application_mailer.rb looks similar to
class ApplicationMailer < ActionMailer::Base
include Resque::Mailer # This will add a `self.perform` class method, which we will overwrite in DeviseResqueMailer
end
In config/initializers/devise.rb
Devise.parent_mailer = "ApplicationMailer"
Devise.setup do |config|
config.mailer = 'DeviseResqueMailer'
end
In the resource class which uses devise, overwrite the send_devise_notification method to send resource class and id instead of object to prevent marshalling
# app/models/user.rb
protected
def send_devise_notification(notification, *args)
# Based on https://github.com/zapnap/resque_mailer/blob/64d2be9687e320de4295c1bd1b645f42bd547743/lib/resque_mailer.rb#L81
# Mailer may completely skip Resque::Mailer in certain cases - and will fail as we write custom handle in DeviseResqueMailer assuming mails are handled via resque
# So in those cases, don't retain original devise_mailer so things work properly
if ActionMailer::Base.perform_deliveries && Resque::Mailer.excluded_environments.exclude?(Rails.env.to_sym)
# Originally devise_mailer.send(notification, self, *args).deliver
# Modified to ensure devise mails are safely sent via resque
resource_id, resource_class = self.id, self.class.name
devise_mailer.send(notification, {resource_id: resource_id, resource_class: resource_class}, *args).deliver
else
super
end
end
Finally, in app/mailers/devise_resque_mailer.rb, fetch the record again from the database and continue
class DeviseResqueMailer < Devise::Mailer
def self.perform(action, *args)
# Hack to prevent RuntimeError - Could not find a valid mapping for admin.attributes
record_hash = args.shift
record = record_hash["resource_class"].constantize.find(record_hash["resource_id"])
args.unshift(record)
super # From resque-mailer
end
end
I feel this approach is a better than using devise-async as all the mails go through same code path. Its easier to control and overwrite if needed.
I'd take a look at devise-async. Looks like it fits your use case. Devise Async

undefined method 'path' for nil:NilClass using chargify_api_ares gem

I feel like this should be a simple problem, but I'm pulling my hair out trying to track it down. I'm installed the chargify_api_ares gem, but can't do even basic things such as
Chargify::Subscription.create
As I get this path error. I feel like this must be a gem issue somehow but don't know where to go from here.
UPDATE: bundle show chargify_api_ares shows the correct path, I just somehow can't access it. Still trying random environment related things.
Looks like this is the source of the problem, in active_resource\base.rb:
# Gets the \prefix for a resource's nested URL (e.g., <tt>prefix/collectionname/1.json</tt>)
# This method is regenerated at runtime based on what the \prefix is set to.
def prefix(options={})
default = site.path
default << '/' unless default[-1..-1] == '/'
# generate the actual method based on the current site path
self.prefix = default
prefix(options)
end
As I understand it, Chargify.subdomain should be setting the site.path, but I don't understand activeresource well enough yet to know what's happening and will continue to dig.
I too had the same issue.
I executed the following on the console
Chargify.configure do |c|
c.api_key = "<<api_key>>"
c.subdomain = "<<subdomain>>"
end
After that performing any Chargify console commands went through fine.

Extend a module in Rails 3

I want to define a function available_translations which lists the translations I have made for my application into the I18n module.
I tried putting the following into the file lib/i18n.rb, but it doesn't work when I try to use it from the rails console:
module I18n
# Return the translations available for this application.
def self.available_translations
languages = []
Dir.glob(Rails.root.to_s + '/config/locales/*.yml') do |filename|
if md = filename.match #^.+/(\w+).yml$#
languages << md[1]
end
end
languages
end
end
Console:
ruby-1.9.2-p290 :003 > require Rails.root.to_s + '/lib/i18n.rb'
=> false
ruby-1.9.2-p290 :004 > I18n.available_translations
NoMethodError: undefined method `available_translations' for I18n:Module
...
Besides solving my concrete problem, I would be very pleased to learn how this whole module thing in Ruby on Rails works because it still confuses me, so I would appreciate links to the docs or source code very much.
Either of these will solve your problem:
move the code to config/initializers/i18n.rb, or
require your file from config/application.rb, or
name your class otherwise (to trigger autoload)
The code in lib/i18n.rb wil not be loaded by autoload since I18n name will be already loaded, so either you load it yourself or change the class name (and file name) so the new name will trigger autoload behavior.
BTW, the I18n.available_locales() method is presented in rails.

How can I use mixins or modules in my controllers in Rails 3?

I have some behavior in my controller that I pulled out into a module in order to test better and re-use it in a few places. Two questions about this:
Where is a good place to put my modules? They need to run in order to be available to the controllers, so I was thinking the config/initializers/ directory. That seems a little suspect to me though. lib/?
How do I ensure the code gets run so the modules are available to include in my controllers?
Thank you kindly sirs.
lib/ is an excellent place for modules; much better than config/initializers/--at least in my opinion. If it's several modules, or one large one, you can also consider making it a plugin and placing it in vendor/plugins.
If you put it in lib/, you'll need to manually require the file. Rails, by default, does not autoload files in the lib/ directory. You can place the require in one of your config files.
I usually put my additional autoloads in config/application.rb. Something like this should do the trick (assuming that your .rb file is in a directory called lib/my_module):
config.autoload_paths += Dir["#{Rails.root}/lib/my_module"]
You have to make sure that your module is an actual module and not a class. Then, you can simply include it:
# lib/my_module/foobar.rb
module Foobar
def foobar
"Hello world!"
end
end
# app/models/my_model.rb
class MyModel < ActiveRecord::Base
include Foobar
end
# rails console
>> obj = MyModel.first
=> #<MyModel id: 1, ...>
>> obj.id
=> 1
>> obj.foobar
=> "Hello world!"
1) I like to put:
my class extentions under app/extentions
my modules under /app/mixins
my services under /app/services
2) You can configure your application to load all of these in config/application.rb:
class extentions should be required right way
and the mixins and services can be added to your autoload path
class Application < Rails::Application
# require class extentions right now
Dir[Rails.root.join('app', 'extentions', "*.rb")].each {|l| require l }
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += Dir[Rails.root.join('app', 'mixins', '{**}')]
config.autoload_paths += Dir[Rails.root.join('app', 'services', '{**}')]
(I'm using rails 3)
Try putting controller specific modules in app/controllers. No require required.