I'm writing a series of Rails generators that will share several of the same methods. I would like to abstract these methods into a module or class of their own to be reused (but not automatically fired) within each of my generators.
My latest attempt was to autoload a helper file and later include it:
lib/my_gem/engine.rb
module MyGem
class Engine < Rails::Engine
config.autoload_paths += Dir["#{config.root}/lib/helpers/**"]
end
end
lib/helpers/generators_helper.rb
module MyGem
module GeneratorsHelper
def some_method
# ...
end
end
end
lib/generators/my_gem/my_generator.rb
# ...
include MyGem::GeneratorsHelper
# ...
But I'll see something like Error: uninitialized constant MyGem::GeneratorsHelper.
I was able to accomplish this by manually requiring the file and then including the module. It's a little ugly, but keeps me from duplicating helper methods:
lib/my_gem/generators/my_generator.rb
require "#{Gem::Specification.find_by_name("my_gem").gem_dir}/lib/helpers/generators_helper.rb"
include MyGem::GeneratorsHelper
Related
I'm trying to make a Ruby on Rails engine, and I want the initializer to be able to have access to the helpers and models.
I'll write below an example, part of the code, and the error that I have. It may not be the recommended way, because I can see that in some cases I'm repeating myself, but it's the first engine I make.
file lib/my_engine/engine.rb
module MyEngine
require 'my_engine/functions'
class Engine < ::Rails::Engine
isolate_namespace MyEngine
config.autoload_paths += %W( #{config.root}/lib )
end
class GlobalVars
attr_accessor :foo
def initialize
#foo = MyEngine::Functions.new
end
end
class << self
mattr_accessor :GLOBAL
mattr_accessor :USER_CONFIG
self.GLOBAL = MyEngine::GlobalVars.new
# add default values of more config vars here
self.USER_CONFIG = 'default config'
end
def self.setup(&block)
yield self
end
end
file lib/my_engine/functions.rb
module MyEngine
require '../../app/helpers/my_engine/options_helper'
class Functions
include MyEngine::OptionsHelper
attr_accessor :some_link
def initialize
#some_link = get_option('dummy')
end
end
end
There is also a controller named OptionsController in app/controllers/my_engine, and OptionsHelper in app/helpers/my_engine/options_helper.rb:
module MyEngine
module OptionsHelper
def get_option(name)
MyEngine::Option.new
end
end
end
When I try to run the dummy application, this error occurs:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
If I change to just Option.new, I have this error:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::OptionsHelper::Option (NameError)
For ::MyEngine::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant MyEngine::Option (NameError)
For ::Option.new, I have:
/app/helpers/my_engine/options_helper.rb:4:in `get_option': uninitialized constant Option (NameError)
The dummy application has nothing in it. All helpers and models defined above are in the engine.
Before this, I had other errors because it couldn't access the helper, or the Functions class. I had to add require and include to make it work even if they are placed in the same directory. Also, to work, I had to move GlobalVars from its own file inside engine.rb.
Can somebody show me what I'm doing wrong?
After I used required for every class, I ended with ActiveRecord::ConnectionNotEstablished, and it seems that not everything is loaded and available at that point when the GLOBAL object is created.
So I moved the code that was using the models in a separate init method. Then, I added an after initialize event:
config.after_initialize do
MyEngine.GLOBAL.init
end
I see a possible problem: because you are inside module MyEngine it might be possible that actually rails is looking for MyEngine::MyEngine::Option, so I see two approaches:
just write Option: this will look for MyEngine::Option
write ::MyEngine::Option this will look in the global namespace and find MyEngine::Option
Secondly, if that does not help, even though your path seems correct, but you can always explicitly require "my_engine/option" at the top of the file. I am not entirely sure the autoloading in an engine works in quite the same way, and I tend to, in my engine file, require almost everything (to make sure it works).
In my engine.rb I do
require_relative '../../app/models/my_engine/option'
maybe this will help, but it is not a nice solution.
I'd like to make a function current_order_week that would be available globally throughout my app and could be called similarly to something like current_user. I don't want to have to include it in a specific model / controller, I just want it available everywhere.
I've modified my /lib folder to include a lib_extensions.rb file and added to that file:
class Object
def current_order_week
end
end
I've modified my application.rb to include:
config.autoload_paths << Rails.root.join('lib')
config.eager_load_paths << Rails.root.join('lib')
But when I attempt to call current_order_week from the console or from a test, I still see:
NameError: undefined local variable or method 'current_order_week'
for main:Object
What else do I need to do?
You should add this function in the application_helper.rb file. All controllers extend from ApplicationController and ApplicationController includes the ApplicationHelper.
module ApplicationHelper
def current_order_week
end
end
This will be available to use in views and controllers
Monkey-patching core classes like Object is usually not a good idea, this may interfere with some gems etc. and in general can lead to painful debugging in the future.
If you absolutely want to do this - autoloading will not pick up Object from lib, because it is already defined. Create an initializer in config/initializers, then it will be loaded, but it will not reload on code changes.
But better way is including such code in ApplicationHelper, ApplicationRecord and ApplicationController
autoload_paths and eager_load_paths don't include modules, they only require files in which modules are defined. To use current_order_week you need to specify the name of the module:
module Foo
def current_order_week
.
.
.
end
end
Foo.current_order_week()
In order to use current_order_week without prepending the name of the module to it, you need to include Foo inside your controllers and models:
class ApplicationController < ActionController::Base
include Foo
def some_action
current_order_week()
end
end
class ApplicationRecord < ActiveRecord::Base
include Foo
end
We have a concern we want to use as a mixin for our User classes. This concern is found in our separate rails engine that we use for multiple products.
Everything in this engine, we keep in the same module, which we will call MyEngine.
module MyEngine
module EngineUser
extend ActiveSupport::Concern
end
end
And we are tring to include it like any other concern in our Rails Application:
class User < ActiveRecord::Base
include MyEngine::EngineUser
# ...
end
This causes an error where it says: (formatted some for readability)
/Users/foo/.rvm/gems/ruby-2.1.5/gems/activesupport-4.2.4/lib/active_support/dependencies.rb:495:in `load_missing_constant':
Unable to autoload constant EngineUser,
expected
/Users/foo/Perforce/engine_folder/app/models/concerns/engine_user.rb
to define it
Which..... is the right file... <.<
If I remove the module MyEngine around the class, and form the include in User, it works just fine.
I know rails does so autoloading behind the scenes, but why isn't this working? It knows that file has the class... if I move it to engine/app/concerns it says it cant find it there. So frustrating.
This should solve your problem. I had the same issue recently..
Take a look at the additional module, Concerns, I've added.
module MyEngine
module Concerns
module EngineUser
extend ActiveSupport::Concern
end
end
end
# lib/my_engine/engine.rb
module MyEngine
class Engine < ::Rails::Engine
config.autoload_paths += %W(#{MyEngine::Engine.root}/app/models/my_engine/concerns/engine_user.rb)
isolate_namespace MyEngine
end
end
# app/models/user.rb
class User < ActiveRecord::Base
include MyEngine::Concerns::EngineUser
# ...
end
Here is the RailsGuide post that led me to the answer.
So the answer from Justin Licata is probably the "correct" answer, but the solution that I ended up going with was as follows:
in engine_folder/lib/my_engine.rb, which is what is included as part of loading the engine, I just used the line:
Dir[File.dirname(__FILE__) + "/my_engine/concerns/**/*.rb"].each { |file| require file[0..-4]}
It certainly has some smell to it, I admit, but it lets us follow our own conventions and structure, and allows us to add folder hierarchy as needed without worrying about rails autoloading issues.
Thanks for the answers!
In Rails, how do you use a specific method from a module. For eg,
# ./app/controllers/my_controller.rb
class MyController < ApplicationController
include MyModule
def action
MyModule.a_method
end
private
def a_method
...
end
end
# ------------------------------------------------ #
# ./app/helpers/my_module.rb
module MyModule
def a_method
...
end
end
MyController includes MyModule. And in action ,I want to use MyModule.a_method (Please note I also have a private a_method in MyController and I don't want to use this.)
Things I've tried :
1) Defining the method in the module as self.
def self.a_method
end
2) Using the :: notation in controller (MyModule::a_method)
The error that I keep getting is
Undefined method:a_method for MyModule:module
For now, I've resorted to using a different name for the modules method. But I'd like to know how to namespace the function with either the Module:: or Module. notation
[UPDATE - 11/24/2014]
adding file structure in code, since Rails heavily relies on convention.
So I am not really sure what you are trying to accomplish with your module but a quick solution to get it working is below.
Move my_module.rb out of helpers and into lib/my_module.rb. The helpers directory is for methods that you use in your views. The convention is to utilize helpers that are namespaced after their respective controller or the application_helper.rb for global methods for your views. Not sure if that's what you are trying to accomplish with your module but wanted to throw that out there.
Create an initializer (you can all it whatever) in config/initializers/custom_modules.rb and add require 'my_module'
Update the a_method back to be self.a_method
You can now call MyModule.a_method in your app
Don't forget to restart your server for changes to lib/my_module.rb to take effect.
Also, a lot of people reference this post by Yehuda Katz as guidance on where to store code for your app. Thought it might be a helpful reference.
if you include MyModule into MyController, all the "instance methods" of the first will be mixed-in into the 2nd.
So if you only want to call MyModule.a_method, no need to include your module.
Then you'd want to require (or better autoload) your module before using it. To do so place it in controllers/concerns/my_module.rb, rails (4 at least) should autoload it, otherwise require its file in an intializer
# my_module.rb
module MyModule
def self.a_method
...
end
end
should work, but doing
# my_module.rb
module MyModule
extend self
def a_method
...
end
end
is more clean to me. You'd like to have a look to rails active support concern to understand the "rails way" on this topic.
I have created a simple railtie, adding a bunch of stuff to ActiveRecord:
0 module Searchable
1 class Railtie < Rails::Railtie
2 initializer 'searchable.model_additions' do
3 ActiveSupport.on_load :active_record do
4 extend ModelAdditions
5 end
6 end
7 end
8 end
I require this file (in /lib) by adding the following line to config/environment.rb before the application is called:
require 'searchable'
This works great with my application and there are no major problems.
I have however encountered a problem with rake db:seed.
In my seeds.rb file, I read data in from a csv and populate the database. The problem I am having is that the additions I made to ActiveRecord don't get loaded, and seeds fails with a method_missing error. I am not calling these methods, but I assume that since seeds.rb loads the models, it tries to call some of the methods and that's why it fails.
Can anyone tell me a better place to put the require so that it will be included every time ActiveRecord is loaded (not just when the full application is loaded)? I would prefer to keep the code outside of my models, as it is code shared between most of my models and I want to keep them clean and DRY.
Putting the extend there just adds it to ActiveRecord::Base.
When a model class is referenced, via Rails 3.1 autoloading/constant lookup, it will load the class. At that point, it is pure Ruby (nothing magic) as to what happens, basically. So I think you have at least a few options. The "bad" option that kind of does what you want it to hook into dependency loading. Maybe something like:
module ActiveSupport
module Dependencies
alias_method(:load_missing_constant_renamed_my_app_name_here, :load_missing_constant)
undef_method(:load_missing_constant)
def load_missing_constant(from_mod, const_name)
# your include here if const_name = 'ModelName'
# perhaps you could list the app/models directory, put that in an Array, and do some_array.include?(const_name)
load_missing_constant_renamed_my_app_name_here(from_mod, const_name)
end
end
end
Another way to do it would be to use a Railtie like you were doing and add a class method to ActiveRecord::Base that then includes stuff, like:
module MyModule
class Railtie < Rails::Railtie
initializer "my_name.active_record" do
ActiveSupport.on_load(:active_record) do
# ActiveRecord::Base gets new behavior
include ::MyModule::Something # where you add behavior. consider using an ActiveSupport::Concern
end
end
end
end
If using an ActiveSupport::Concern:
module MyModule
module Something
extend ActiveSupport::Concern
included do
# this area is basically for anything other than class and instance methods
# add class_attribute's, etc.
end
module ClassMethods
# class method definitions go here
def include_some_goodness_in_the_model
# include or extend a module
end
end
# instance method definitions go here
end
end
Then in each model:
class MyModel < ActiveRecord::Base
include_some_goodness_in_the_model
#...
end
However, that isn't much better than just doing an include in each model, which is what I'd recommend.