I have a module with some "class" or "DSL" methods which at the moment looks like:
module Things
module ClassMethods
def thing id, title, **options
# ... create and register a thing ...
end
# ... more DSL methods ...
end
extend ClassMethods
# define project things
thing :ruby, "Ruby programming language"
thing :rails, "Ruby on Rails web framework"
thing :active_support, "ActiveSupport library"
end
So I define the DSL methods in the ClassMethods, extend the Things with ClassMethods then use the DSL in the module body. I've chosen the name ClassMethods following the convention I see though out rails, although the module is not an active record Concern.
Now I would like to separate the DSL definition from its usage, so I split the code into
lib/things/class_methods.rb:
module Things
module ClassMethods
def thing id, title, **options
# ... create and register a thing ...
end
# ... more DSL methods ...
end
end
and lib/things.rb:
require_relative "things/class_methods"
module Things
extend ClassMethods
# define project things
thing :ruby, "Ruby programming language"
thing :rails, "Ruby on Rails web framework"
thing :active_support, "ActiveSupport library"
end
But after this something breaks: the code works as intended the first time after restarting the rails server, but subsequent requests that use this module raise obscure errors or complain that DSL methods aren't defined or otherwise behave as if the DSL calls didn't do what they were supposed to. It feels as if the extend ClassMethods line picks up a wrong module ClassMethod, but then I don't understand why i works initially.
Any idea what exactly goes wrong with my approach and what's the best solution to the problem? Should I rename the DSL module so it doesn't clash with ClassMethods defined elsewhere in the framework? Or is there anything else I can do to keep using the name ClassMethods without a conflict?
Rails sometimes loses track of constants during autoloading and reloading and needs some help.
This can either be an explicit dependency:
# lib/things.rb
require_dependency 'things/class_methods' # <- tells Rails that we depend on this
require_relative 'things/class_methods'
module Things
extend ClassMethods
# ...
end
Or by using a fully qualified contant:
# lib/things.rb
require_relative 'things/class_methods'
module Things
extend ::Things::ClassMethods
# ...
end
It might be enough to use extend Things::ClassMethods, provided that you don't have another Things module nested under Things.
Which approach works, seems to depend on your class structure and autoloading configuration.
Have you tried to add the /lib directory to your application.rb file using the .autoload_paths method?
Related
Error message:
NameError: uninitialized constant FinancialFunctions::ComputationSteps::TwoBranchesPerPeriod::Strategy1
I'd like Rails to automatically look through the included Strategies module and see the class defined within it instead of just looking within TwoBranchesPerPeriod.
This is not a one-time problem and is part of a broader architecture restructuring. Solving this problem will help in many places throughout the codebase.
There are two specific solutions I'm not looking for:
Having a require or load statement at the top of the file, as I want Rails autoloading to be the core solution for this problem and not have to require several files.
Explicitly state the scope when initializing the class. I'm aware that simply calling Strategies::Strategy1.new will work, but this solution does not work for me because of the length of several classes. I'd like to simply define the dependencies at the top of the file with include statements, and have access to the methods/classes defined in the included modules.
I'm open to autoload statements in a file such as strategies.rb but from what I've test this does not work either.
app/lib/financial_functions/computation_steps/two_branches_per_period.rb:
module FinancialFunctions
module ComputationSteps
module TwoBranchesPerPeriod
include Strategies
def a_method
#The below line is resulting in the error
# NameError: uninitialized constant FinancialFunctions::ComputationSteps::TwoBranchesPerPeriod::Strategy1
a=Strategy1.new
end
end
end
end
/app/lib/financial_functions/computation_steps/strategies/strategy1.rb:
module FinancialFunctions
module ComputationSteps
module Strategies
class Strategy1
def initialize
puts "this should work"
end
end
end
end
I'd like to simply define the dependencies at the top of the file with
include statements, and have access to the methods/classes defined in
the included modules.
Your expectations are not realistic in Ruby. Ruby does not have a package system and includes is not equivalent to imports in languages such as Python or ES6.
As stated in the comments by #maxpleaner includes only copies the methods of the module.
So to do this you would have to define new constants in the consuming class/module:
module FinancialFunctions
module ComputationSteps
module TwoBranchesPerPeriod
include Strategies
Strategy1 = ComputationSteps::Strategies::Strategy1
def a_method
Strategy1.new
end
end
end
end
You can do this dynamically through the Module#included hook:
module Foo
module Bar
end
module Baz
end
def self.included(base)
self.constants.each do |c|
base.constant_set(c, self.constant_get(c))
end
end
end
module X
includes Foo
end
irb(main):002:0> X::Bar
=> Foo::Bar
But aside from being an fun example of meta-programming I don't really think its that wise as the there is no way to handle namespace conflicts. You could also construct a makeshift equivalent of an imports construct:
module Importer
def import(*constants, from:)
constants.each do |c|
const_set(c, from.const_get(c))
end
end
end
module Foo
module Bar
module A
end
module B
end
end
end
class Thing
extend Importer
import :A, :B, from: Foo::Bar
end
irb(main):001:0> Thing::A
=> Foo::Bar::A
irb(main):002:0> Thing::B
=> Foo::Bar::B
But this also smells like an example of fighting the language and will probably introduce more problems then it solves.
I am fairly new to RoR. I have spent the afternoon reading about modules (used as concerns). I have yet to find a good article which describes the file path that using include or extend methods looks up (if include and extend are methods?).
The most specific example I have found was here: Ruby On Rails - Using concerns in controllers. This makes me feel that if I wanted to include the 'Bar' module in my Foo model I would create a concerns/ directory in my models/ directory, and create a 'Bar' module file in this folder.
# in models/concerns/bar.rb
modlue Bar
# do I need this???
extend ActiveSupport::Concern
def speak_bar
puts "model module bar!"
end
end
# in models/foo.rb
class Foo < ApplicationRecord
include Bar
end
# I could then perform:
Foo.new.speak_bar
=> "model module bar!"
And if I wanted to include a Bar module in my Foo controller I would do:
# in controllers/concerns/bar.rb
modlue Bar
# Again, do I need this???
extend ActiveSupport::Concern
def speak_bar
return "controller module bar!"
end
end
# in controllers/foo.rb
class FoosController < ApplicationController
include Bar
def make_bar
#bar = speak_bar
end
end
# I could then use #bar in my views (anywhere else?) as <%= #bar %> and get it to output
=> "controller module bar!"
Summary of questions:
Is this understanding set out above correct in terms of the file paths?
And do I need to use the extend ActiveSupport::Concern line in order to use this path system?
Are include and extend methods?
Thank you for your help.
You should always extend your concerns module with the supplied concerns base from Rails.
Pathing is usually app/models/concerns/file.rb for model concerns and app/controllers/file.rb for controllers and so on.
If you specifically have logic that crosses the controller and models separation, consider placing that in lib, and adding lib to your autoload path.
include and extend are methods. Most things (almost all) are objects in ruby. So almost all operations are methods on objects.
the file path that using include or extend Rails does some magic when starting to autoload a lot of things so you don't have to worry later when you call "Bar". This talk is really helpfull to understand WHY you can just do include Bar inside a rails model without much thinking https://www.youtube.com/watch?v=I0a5zv7uBHw
Usually, you want model related concerns inside /app/models/concerns and controller related concerns inside /app/controllers/concerns, but that's just for organization purposes, rails will autoload them even if you use /app/whatever/concerns, so be carefull about name collisions.
You DO need to extend ActiveSupport::Concern if you want to use the syntax sugar that Concerns provide, but at the end they are just modules that can be included. https://api.rubyonrails.org/classes/ActiveSupport/Concern.html check this examples, concerns are just a way to write modules to share behaviour with a more friendly syntax for common rails patterns.
extend is a method of Object https://docs.ruby-lang.org/en/2.6.0/Object.html#method-i-extend
include is a method of Module https://docs.ruby-lang.org/en/2.6.0/Module.html#method-i-include (and Module inherits extend from Object)
concerns are auto-loaded by rails by default starting from rails v4+. You can read the article written by DHH to get a fair idea of what concern does and what does it try to solve.
However, it gets pretty complicated in determining which scope you are in and what self is in the method. Check out this video by Ryan Bates regarding the problems with concerns.
To solve some parts of the problem, I generally nest the concern inside a folder and refer it by giving a class. For example
# app/models/concerns/user/authentication.rb
class User
module Authentication
extend ActiveSupport::Concern
# stuff
end
end
and include in the model like
# app/models/user.rb
include Authentication
In my opinion, the separation of concerns helps in isolating your methods. For example, you can create a Filterable concern in a similar way, and isolate it from your other models.
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.
What's the difference between
module A
def self.included(klass)
puts klass
end
end
and
module A
include ActiveSupport::Concern
included do
puts self
end
end
Which one is a better and which one to use when?
Both snippets produce the same result. However, there is a small but important difference.
The first code is pure Ruby. It means it will work without any dependency.
The second piece of code depends on ActiveSupport that is an external dependency. If you want to use it you need to include the gem in your project. In a Rails application, there is almost no overhead because the application already depends on ActiveSupport. But in a non-Rails application, it may not be convenient.
Moreover, ActiveSupport::Concern does a lot more than simply adding some syntactic sugar for the Ruby included hook. In fact, the primary scope of such module was to manage multiple dependencies between modules.
In the Rails codebase it's very common to define small piece of features into separate modules. A good example is ActiveSupport itself. However, you may have that the feature A may require some feature defined in the module B, so if you include B in your code, then the module should make sure to require and mix also A, otherwise your code will crash.
It also implements a very common pattern based on the included hook: class-level extensions. The following Ruby code
module A
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def bar
"bar"
end
end
end
class Foo
include A
end
Foo.bar
# => "bar"
becomes
module A
extend ActiveSupport::Concern
module ClassMethods
def bar
"bar"
end
end
end
class Foo
include A
end
Foo.bar
# => "bar"
My personal advice is to avoid using the ActiveSupport::Concern if
You don't need to use its advanced features
The code you are writing, can be easily written with a few code with no dependency on Concern
The app does not already include ActiveSupport as dependency
In your second piece of code, included is called once during when A is defined. It does not do anything unless you had defined included to do something. The block passed to included would have no effect unless you had overwritten included to take a block and do something with it. In short, your second piece of code does not make sense.
I'm writing a Rails plugin (lets call it Foo).
I want it to provide 'bar' class function in controllers, so that I can do:
class ApplicationController
bar ...
end
bar is defined from withing the plugin loading file vendor/plugins/foo/init.rb. Something like
class ActionController::Base
def self.bar
...
end
end
The problem is that some other plugins (in my case ResourceController) might load before foo and access ApplicationController.
So what happens is that ApplicationController is loaded before the plugin 'foo' and fails since there is no 'bar' defined YET.
So ... how do I properly make it work works?
I noticed that many other plugins that extend ActionController (for example inherited_resources, resource_controller, ) are doing exactly the same, so it seems its a matter of who loads first to decide if it fails or works.
I know that I can put code in some module and manually add the module to ApplicationController code before calling 'foo'. I'd rather not, I like the cleanliness of just 'foo'.
I also don't want to do a manual 'require'. Plugins are supposed to be auto-loaded all by themselves :)
What you have is a classic plugin load order problem. Ryan Daigle had a nice article on this back in 2007. I'll sum up the recommendation here:
# in RAILS_ROOT/config/environment.rb:
...
Rails::Initializer.run do |config|
# load Bar before Foo, then everything else:
config.plugins = [ :bar, :foo, :all ]
...
end
From what I understand,
ResourceController loads before plugin foo and tries to use the bar method you have defined in foo.
Usually, gems and plugins are loaded before application classes. (Take a look at rails/railties/lib/initializer.rb). Could you provide a stack-trace of the error so that one can debug this.
Also, for extending the classes, this seems a better alternative to me:
module ActionController
class Base
class << self
... # Class methods here
end
... # Instance methods here
end
end