Difference between redefining self.included and passing block to included - ruby-on-rails

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.

Related

How to access a class defined in module B from module A

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.

ActiveSupport::Concern running method before/after initialize

Does ActiveSupport::Concern support a way to run a method before or after the object's constructed.
e.g. to achieve something like
Module Benchmarker
extend ActiveSupport::Concern
before_initialize
#constructed_at = DateTime.now
end
end
(Note: Not wanting this for ActiveRecord or ActiveController. Just a generic Ruby class within a Rails project.)
NOTE: This would work for models or only those classes which inherit from ActiveRecord.
Rails does not support a before_initialize callback. You could use the after_initialize callback instead. However, when using it in a concern, you need to mention it in an included do..end block. For example, the following code should work for your use case:
Module Benchmarker
extend ActiveSupport::Concern
included do
after_initialize do |subject|
p "I'm in after_initialize"
end
end
end
You could refer to this answer for more details on how the included hook works.

require / extend ClassMethod from a separate file

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?

Rails undefined method for Module

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.

Rails using included helpers inside class methods

Does anybody know why the included method doesn't work inside a class method?
class MyClass
include ActionView::Helpers::NumberHelper
def test
puts "Uploading #{number_to_human_size 123}"
end
def self.test
puts "Uploading #{number_to_human_size 123}"
end
end
ree-1.8.7-2011.03 :004 > MyClass.new.test
Uploading 123 Bytes
=> nil
ree-1.8.7-2011.03 :005 > MyClass.test
NoMethodError: undefined method `number_to_human_size' for MyClass:Class
from /path/to/my/code.rb:9:in `test'
from (irb):5
ree-1.8.7-2011.03 :006 >
For anyone wanting to use some custom helpers in class level lib or model, sometimes it is not worth it to include all helpers. Instead, call it directly:
class MyClass
def test
::ApplicationController.helpers.number_to_human_size(42)
end
end
(Taken from http://makandracards.com/makandra/1307-how-to-use-helper-methods-inside-a-model)
It's hard to tell without seeing your helper code, but include will insert all of the methods in that module into instances of the class you include into. extend is used to bring methods into a class. Therefore, if you just have methods defined in NumberHelper, these are being put onto all instances, but not the class, of MyClass.
The way that lots of Rails extensions work is using techniques that have been consolidated into ActiveSupport::Concern. Here is a good overview.
Essentially, extending ActiveSupport::Concern in your modules will allow you to specify, in sub-modules called ClassMethods and InstanceMethods, what functions you want to be added to classes and instances into which you include your module. For example:
module Foo
extend ActiveSupport::Concern
module ClassMethods
def bar
puts "I'm a Bar!"
end
end
module InstanceMethods
def baz
puts "I'm a Baz!"
end
end
end
class Quox
include Foo
end
Quox.bar
=> "I'm a Bar"
Quox.new.baz
=> "I'm a Baz"
I've used this before to do things like define the bar function in ClassMethods, then also make it available to instances by defining a bar method of the same name that just calls this.class.bar, making it callable from both. There are lots of other helpful things that ActiveSupport::Concern does, like allowing you to define blocks that are called back when the module is included.
Now, this is happening here specifically because you're includeing your helper, which might indicate that this functionality is more general-purpose than a helper - helpers are only automatically included in views, since they are only intended to help with views. If you want to use your helper in a view, you could use the helper_method macro in your class to make that method visible to your views, or, even better, make a module as above and not think about it as a helper, but use include to mix it in to the classes you want to use it in. I think I would go that route - there's nothing that says you can't make a HumanReadableNumber module and include it in NumberHelper to make it easily available across your views.
I faced the same issue. Here's how I solved it,
helper = Object.new.extend(ActionView::Helpers::NumberHelper)
helper.number_to_human_size(1000000)
Thanks to RailsForum.
I was facing the same problem. in my case, replacing the include with extend made it work.
class MyClass
extend ActionView::Helpers::NumberHelper
...
end

Resources