I am using Ruby on Rails 3.2.2. I am implementing a module and including that in a my class by using the RoR ActiveSupport::Concern feature. It makes available the included do ... end block making code stated inside that to be executed in the class context of the class where the module is included.
My doubt is: What should I not include in the included do ... end block? That is, for instance, is it a "common" / "good" practice to make the following?
module MyModule
extend ActiveSupport::Concern
class MyModuleClass
attr_accessor :attr1, :attr2, :attr3
def initialize(attrs)
#attr1 = attrs[:attr1]
#attr2 = attrs[:attr2]
#attr3 = attrs[:attr3]
end
end
included do
#my_module_class = MyModuleClass.new(some_attrs)
end
end
More, will be the #my_module_class variable available as an attribute in the including class of MyModule (BTW: I would like to make the #my_module_class to be "visible" only internally to MyModule since it is intended to be used only in that module)? Are there some "advanced" examples or tutorials on how to handle situations like that I am trying to instantiate in the included do ... end block of the above code? What do you advice about?
#my_class will be an instance of MyClass and not MyModule. If you want to make all instances of MyClass be an instance of MyModule you should write:
include MyModule
inside the class definition.
I think my answer makes sense if you look at the original version of this question before it was edited.
EDIT 1:
Let's add on to your example and say you have a class called Foo:
class Foo
include MyModule
end
You want to make an instance of MyModuleClass that is associated with Foo but it sounds like you don't really want to modify Foo or give it access to the MyModuleClass. I propose that you use a hash table:
module MyModule
# ...
#hash = {}
class << self
attr_accessor :hash
end
included do
MyModule.hash[self] = MyModuleClass.new(some_attrs)
end
end
I think that will work, and it avoids adding an instance variable to to the Foo class object. Technically, any part of the ruby code can access MyModule.hash but you should put a comment in the source code telling people NOT to do that, and don't advertise that the hash exists.
Related
Trying to make available the methods I have stored in a Module which is located in app/models (side note: not sure if this is the correct place for modules?).
Module:
module MyModule
class MyClass
def some_method
# do something
end
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
I am getting a NoMethodError:
NoMethodError (undefined method 'some_method' for #<User:0x00007f6a3ce452c0>
You seem to have missunderstood what what modules and classes do in Ruby. In Ruby a module is simply an object that wraps a set of methods and constants.
A module can extend other modules, classes and objects and can be included in classes thus implementing multiple inheritance. Modules in Ruby fill the role that traits, namespaces and singletons do in other languages.
Classes are actually modules (Module is part of the ancestors chain of Class) with the key difference that you can make instances of a class and that class can inherit from a single other class and cannot extend other objects or be included.
The code example here actually doesn't make sense. If you want to declare a method that will be available to classes that include a module you want to declare it in the module itself:
module MyModule
def some_method
# do something
end
end
When you then call User#another_method it will look in the ancestors chain of the User class until it finds the method which is defined in MyModule.
module MyModule
class MyClass
def some_method
# do something
end
end
end
Will actually definte the class MyClass with an instance method that is only available to instances of MyClass. The only thing that the module does here is change the module nesting so that the class is defined in MyModule instead of the global namespace.
If you want to mix in a method from a method into your class then just put the methods directly in the module (without an intermediate class).
Module:
module MyModule
def some_method
# do something
end
end
User Model:
class User < ApplicationRecord
include MyModule
def another_method
some_method
end
end
Have a look at this answer, you need to instantiate your Class first. Or if you want to
class User < ApplicationRecord
include MyModule
def another_method
my_instance = MyClass.new
my_instance.some_method
end
end
As for a place to store your Module, have a look at this guide about service objects, it gave me some inspiration when it comes to different modules.
I want to move some functionality out of a Rails model into a service module.
A concern isn't the correct thing for this as it's only for one model, I just want to tidy up some code elsewhere.
I can't seem to get basic calls on the model to work, here's the set up I have:
/app/models/account.rb
class Account < ApplicationRecord
include SomeService
end
And in a differen't location:
app/services/some_service.rb
module SomeService
def test_code
"abc"
end
def self.test_code_2
"xyz"
end
end
In this case I would then expect Account.first.test_code to output abc and Account.test_code_2 to output zyx.
How do I move functionaity like this out of a model but not into a concern? I feel like I'm very close to this working.
This code doesn't actually define a class method:
module SomeService
def test_code
"abc"
end
def self.test_code_2
"xyz"
end
end
It declares a method on the module itself which you can verify by running SomeService.test_code_2. Thats because self is not a "class method keyword" like static in other languages - its just a reference to the current lexical scope. In this case the module itself.
When you declare methods in a class:
class Foo
def self.bar
"Hello world"
end
end
self is whats known as the singleton class - an instance of the Class class. So it defines a method on the Foo class.
When you include a module in a class you're adding the module to the ancestors chain of the class. It can thus call the instance methods of the module as if it where its own. You can contrast this with extend which imports the methods of the module into the class (test_code becomes a class method).
Thus the ClassMethods pattern which extends the class with an inner module when its included:
module SomeService
def self.included(base)
base.extend ClassMethods
end
def test_code
"abc"
end
module ClassMethods
def test_code_2
"xyz"
end
end
end
How do I move functionaity like this out of a model but not into a concern?
What you're doing is a concern. The term "concern" in Rails really just vaguely means something like "a module thats mixed into classes". The only real definition is that app/models/concerns and app/controllers/concerns are autoloading roots and ActiveSupport::Concern exists which just simplefies the boilerplate code needed when writing mixins. Like for example you can use its class_methods macro to shorten the above code.
There is no actual definition of what a concern should contain or what its role is in an application nor is there any requirement that it be mixed into multiple classes.
But...
Moving logic out of a model (or any class) and into a module actually accomplishes nothing if you then include it back into the model. You're just obscuring the code by shuffling it into multiple files.
The amount of responsibilites and complexity remains the same.
If you actually want to redestribute the responsibilites you want to create an object that can stand on its own and does a unit of work:
class SomeService
def initialize(thing)
#thing = thing
end
def perform
# do something awesome with #thing
end
def self.perform(thing)
new(thing).perform
end
end
This is commonly known as the service object pattern - service modules are AFAIK not a thing. This has a defined set of responsibilites and offloads the model. ActiveJob is an example of this pattern.
What you are doing is known as method extraction - basically just splitting a god like object into modules because modules are good and big classes are evil. Right? Nope. Its still a god class. This became really popular around the time that Rails introduced the concerns folders.
Another solution that should not be overlooked is to look at if the model is actually doing to much and should be split into multiple models with more clearly defined responsibilites.
You can define a ClassMethods module inside your module and include it in the base class. This way, the normal module methods will be available as instance methods and the methods defined inside ClassMethods will be available as class methods in the base class.
app/services/some_service.rb
module SomeService
def self.included(base)
base.extend ClassMethods
end
def test_code
"abc"
end
module ClassMethods
def test_code_2
"xyz"
end
end
end
I read this post: Ruby modules - included do end block – but still don't get when you would use the self.included do ... end block in a module.
The post says that code in the block will be ran when you include the module, but what's the point of that if a module's sole purpose is to be included? Wouldn't that code need to be run anyway? That block doesn't need to be there in order for that code to be run, right?
What would be the difference between the two below:
module M
def self.included(base)
base.extend ClassMethods
base.class_eval do
scope :disabled, -> { where(disabled: true) }
end
end
module ClassMethods
...
end
end
vs.
module M
def self.some_class_method
...
end
scope :disabled, -> { where(disabled: true) }
end
What's the difference between the two examples?
The first code block adds the class methods in ClassMethods to the including class and calls the scope method on it as well. The second one does neither of these things and will result in a NoMethodError because the module has no scope class method. self.some_class_method will not be available on the including class once the module is included.
For the full story on how module inclusion works in Ruby, read my answer here:
Inheriting class methods from modules / mixins in Ruby
What's the point of self.included if a module's sole purpose is to be included?
Inclusion is not the only purpose of modules. They are also used for other things such as namespacing or simply storing various class methods that are then callable on the module itself.
Why doesn't Ruby include class methods automatically?
Theoretically Ruby could automatically add all class methods defined in a module to the including class, but in practice that would be a bad idea, because you would not get to choose anymore whether you want to include class methods — all class methods would be included every time, whether or not they are intended to be included. Consider this example:
module M
def self.class_method
"foo"
end
def self.configure_module
# add configuration for this module
end
end
class C
include M
end
Here, the configure_module method is obviously not supposed to be added to C, as its purpose is to set the configuration for the module object. Yet, if we had auto-inclusion for class methods, you would not be able to prevent it from being included.
But all instance methods are already included! How is that okay then?
Instance methods in a module are only really useful if they are included into a class, since modules cannot have instances, only classes can. So in a module every instance method is expected to be included somewhere to work.
A "class" method on a module is different, because it can be called on the module itself, so it can be used just fine regardless of whether it's also added to the including class. That is why it is better that you have a choice there.
module M
# self.included is the hook which automatically runs when this module is included
def self.included(base)
puts 'this will be printed when this module will be included inside a class'
base.extend ClassMethods
base.class_eval do
scope :disabled, -> { where(disabled: true) }
end
end
def print_object_class
self.class.name # here self will be the object of class which includes this module
end
module ClassMethods
def print_class_name
self.name # Here self would be the class which is including this module
end
end
end
I tried to modify the above module to help you understand how a module(concern) can be helpful in code reusability
self.included is a hook which runs automatically when a module is included inside a class.
any method declare in the module ClassMethods will become class methods for the class including this module
any method declare outside the module ClassMethods will become instance methods for the class including this module
For Ex suppose there is a class Product and you have included the module in it
class Product < ActiveRecord::Base
include M
puts 'after including M'
end
If you try this example in you rails console you will notice that as soon as the module M gets included in class Product included hook of module run and
this will be printed when this module will be included inside a class this is printed on console
after that after including M this will be printed on the console.
Also you can try following commands
Product.disabled # a scope with name 'disabled' is avaialble because of including the module M
Product.print_class_name # Outputs => 'Product' This method is available to class with the help of module M
Product.new.print_object_class #Outputs => 'Product'
It also offers reusability, include this module M in any class and that class gets access to all those methods described in the module.
Whereas your second example is a mere example of basic module
module N
def self.abc
puts 'basic module'
end
end
Now abc method define in module can be accessible using only this module
N.abc # outputs 'basic module'
class Product < ActiveRecord::Base
include N
end
Product.abc #raises exception, No method found on class Product
Product.new.abc #raises exception, No method found on object of class Product
I hope this may help you understand the concept of module better.
Please let me know if you still have any doubts.
I'm a total newbie at ruby (java background) so I'm sorry if this is a really dumb question.
I'm working through some tutorials on modules and they seem somewhat similar to static classes. The bit I'm having trouble wrapping my head around is why you would do something like the following:
module ExampleModule
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def myMethod
end
end
end
Why wouldn't you just put the methods in ClassMethods into ExampleModule and save adding the method hook. I'm sure I'm missing something really basic but I've been at this for a while now so I feel the need to ask.
It's a ruby idiom. It's useful when you want a module that:
adds some instance methods to a class
adds class methods in the same time /like Java static methods/
in the same time
Example:
module ExampleModule
def self.included(base)
puts 'included'
base.extend(ClassMethods)
end
def foo
puts 'foo!'
end
module ClassMethods
def bar
puts 'bar!'
end
end
end
class ExampleClass
include ExampleModule
end
ExampleClass.bar
ExampleClass.new.foo
If you only want to add class methods, you don't need this idiom, you can just add a method to your module and 'extend' it instead of including it.
When on Rails, this idiom is obsolete and you should use ActiveSupport::Concern instead.
The patter you use here is common when both class methods and instance methods are included via a module in ruby. It gives you the advantage of just having to write
include ExampleModule
for including instance methods and extending class methods instead of
# include instance methods
include ExampleModule
# extend class methods
extend ExampleModule::ClassMethods
So, if used just to extend the class with some methods, my personal preference is to use extend directly.
module ExtensionAtClassLevel
def bla
puts 'foo'
end
end
class A
extend ExtensionAtClassLevel
end
A.bla #=> 'foo'
If both instance and class methods are added, I use the include hook you described.
Some rubyists tend to prefer using extend via the include hook to pure extend, which has no reason if you are just adding class methods like in your example.
I have a model, Show and a module Utilities
class Show < ActiveRecord::Base
include Utilities
...
def self.something
fix_url("www.google.com")
end
end
My Utilities file is in lib/utilities.rb
module Utilities
def fix_url(u)
!!( u !~ /\A(?:http:\/\/|https:\/\/)/i ) ? "http://#{u}" : u
end
end
But Rails is throwing a NoMethodError for "fix_url" when I call it in my show class. Do I have to do something different when including a module in my model?
Thanks!
try injecting that mixin via the extend instead of include. Basically, because you are calling the mixin method from a class method, but including a mixin only makes its instance methods available. You can use the extend style to get class methods.
Search around for Ruby include and extend to learn the differences. A common pattern is to do it like here:
http://www.dcmanges.com/blog/27
Where you use the included hook to mixin both instance and class level methods.
#Tony - this works for me
class User < ActiveRecord::Base
extend Utilities
def self.test
go()
end
end
module Utilities
def go
puts "hello"
end
end
From console:
>> User.test
hello
=> nil
At no point do I have to explicitly call a method with self.
It worked for me. Have you tried restarting your server/console session?
Edit: If you want to just call Utilities.fix_url you can do that - no include/extend necessary.