Ruby include/extend Module: a class method - Beginner - ruby-on-rails

I've been reading this article on the difference between include & extend in ruby.
If I have this module, I understand how the first and second methods of the module will be used in the class. What I don't understand is how the class << self will be used by include or extend.
module Direction
def straight
puts "going straight!"
end
def turn
puts "turning!"
end
class << self
def stop
puts "stopping!"
end
end
end
# This will work because `include` brings them in as instance methods
class Car
include Direction
end
Car.new.straight
Car.new.turn
# ---------------------
# Now this will also work because `extend` brings them in as class methods
class Car
extend Direction
end
Car.straight
Car.turn
# ---------------------
Now, the issue is, doing Car.stop or Car.new.stop will always result in an error:
/Users/<name>/Projects/ruby-testing/main.rb:34:in `<main>': undefined method `stop' for Car:Class (NoMethodError)
Why are class methods not carried over via include and extend?
I started thinking about this because of my research into the [forwardable source code at line 119].(https://github.com/ruby/ruby/blob/master/lib/forwardable.rb#L119)
Thank you for any help you may have!
Update from Answer Below
The following was an example given:
module Direction
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def stop
puts 'stopping!'
end
end
def straight
puts "going straight!"
end
def turn
puts "turning!"
end
end
class Car
include Direction
end
This I understand now, and I understand how I can implement class methods from a module into a class using def self.included(base). My question is, if we used extend inside of Car instead of include, would we still be able to get at those class methods using def self.included(base)?

When you define a method with class << self you are defining a class method. It's the same as defining the methed like this:
class Foo
def self.foo
puts 'foo'
end
# the above definition is the same as doing:
class << self
def foo
puts 'foo'
end
end
end
The above shows 2 ways of defining class methods which are called directly on the class and not on instances of the class. You might use the 2nd syntax if you want to define only class methods or several of them inside of the class << self block. But either style has the same result.
Since you've defined a class method on the Direction module, include or extend will not inherit the class method of that module. This is the expected behavior.
If you want to use inheritance with class methods from a module, you should do it like this which is explained further down in the article you've linked
module Direction
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def stop
puts 'stopping!'
end
end
def straight
puts "going straight!"
end
def turn
puts "turning!"
end
end
class Car
include Direction
end
Now calling class methods on Car will inherit as defined in the Direction class.
Car.stop
stopping!
=>nil # calling a method will return nil unless the method returns a value.
However always be careful using inheritance of any kind as Ruby is a dynamic language. So if you do the above code and then later redefine this method:
module Direction
module ClassMethods
def stop
puts 'go!'
end
end
end
Guess what will happen if you do this:
Car.stop
Since the method was defined inside Direction module, when the method gets called on Car it will be calling the method from the Direction module.
Car.stop
go!
=>nil
Updated based on comments:
If you prefer to use extend vs include you would need to do this instead:
module Direction
def self.extended(base)
base.extend(ClassMethods)
end
module ClassMethods
def stop
puts 'stopping!'
end
end
end
class Car
extend Direction
end
In this example, all the methods which were inherited from the module are "copied" to the class extending them. This avoids the problem of possible result of redefining the module method which I warned about when using include previously in my answer.
But you may want to look at answers to this question for ideas about when and why to use either case.

Related

How to move logic out of a Rails model into a service module?

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

Ruby extend & include tracing code

I'm confused about using "include" vs "extend, after searching for hours all I got is that module methods used with instance of the class including the module, and module methods used with the class itself when the class extending the module of those methods.
but this didn't help me to figure out, why this code give error when commenting the extend module line in "#extend Inventoryable"
while work when uncomment it, here's the code
module Inventoryable
def create(attributes)
object = new(attributes)
instances.push(object)
return object
end
def instances
#instances ||= []
end
def stock_count
#stock_count ||= 0
end
def stock_count=(number)
#stock_count = number
end
def in_stock?
stock_count > 0
end
end
class Shirt
#extend Inventoryable
include Inventoryable
attr_accessor :attributes
def initialize(attributes)
#attributes = attributes
end
end
shirt1 = Shirt.create(name: "MTF", size: "L")
shirt2 = Shirt.create(name: "MTF", size: "M")
puts Shirt.instances.inspect
the output is
store2.rb:52:in `<main>': undefined method `create' for Shirt:Class (NoMethodError)
while when uncomment the "extend Inventoryable" to make the code work:
module Inventoryable
def create(attributes)
object = new(attributes)
instances.push(object)
return object
end
def instances
#instances ||= []
end
def stock_count
#stock_count ||= 0
end
def stock_count=(number)
#stock_count = number
end
def in_stock?
stock_count > 0
end
end
class Shirt
extend Inventoryable
include Inventoryable
attr_accessor :attributes
def initialize(attributes)
#attributes = attributes
end
end
shirt1 = Shirt.create(name: "MTF", size: "L")
shirt2 = Shirt.create(name: "MTF", size: "M")
puts Shirt.instances.inspect
makes the code work and output the following
[#<Shirt:0x0055792cb93890 #attributes={:name=>"MTF", :size=>"L"}>, #<Shirt:0x0055792cb937a0 #attributes={:name=>"MTF", :size=>"M"}>]
it's kinda confusing, but all I need to know, is why I need to extend the module in order to avoid the error ?, and how to edit this code to make it work without the extend method ? , what's left in the code that still depends on the extend ?
When you extend a module, the methods in that module become "class methods"**. So, when you extend Inventoryable, create becomes available as a method on the Shirt class.
When you include a module, the methods in that module become "instance methods"**. So, when you include Inventoryable, create is not available on the Shirt class (but is available on an instance of Shirt).
To make create available on the Shirt class when using include, you can use the included hook. That might look something like:
module Inventoryable
module ClassMethods
def create
puts "create!"
end
end
module InstanceMethods
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.include InstanceMethods
end
end
Then if you do:
class Shirt
include Invetoryable
end
You can do:
> Shirt.create
create!
=> nil
** The ruby purists in the crowd will correctly point out that, in ruby, everything is an instance method and that there are no class methods. That is formally 100% correct, but we'll use the colloquial meaning of class and instance methods here.
When you extend a module in a class, you get the module's methods exposed as class methods but if you include the module then you get the module's method as instance methods, in your example for you to be able to call create method of Inventoryable class you need to invoke it using an instance of Shirt class (if you include the module)
shirt1 = Shirt.new(attributes).create(attributes)
Without more info I can't tell what you are trying to do but you need to redesign the initialize and create methods to decide where or what to do in those methods.
I'll try to explain it using a simple example
module A
def test
puts "ok"
end
end
class B
include A
end
class C
extend A
end
puts C.test # here you invoke the method against the class itself
puts B.new.test #here you create an instance to do it
Hope it helps.
At the end of the day, it's really simple:
C.include(M) makes the current superclass of C the superclass of M and M the superclass of C. In other words, it inserts M into C's ancestry chain.
obj.extend(M) is (roughly) the same as obj.singleton_class.include(M).

Reusing code ruby on rails

I've got a module in my project in lib/. it's content is like this :
module Search
module Score
def get_score
return 'something'
end
end
end
This Search has many different modules I need to use Score. I realize I need to add require in my model (I'm trying to use this from model). So here is my code (model) :
require 'search'
class User < ActiveRecord::Base
def get_user_score
#tried this :
p Search::Score.get_score #error
#this as well
score_instance = Score.new #error
score = Search::Score.get_score # error undefined method `get_score'
end
end
So how do I reuse the code I have in other class (module)?
To get it working you can either mix the module into your class:
require 'search'
class User < ActiveRecord::Base
include Search::Score
def get_user_score
p get_score # => "something"
end
end
Or you can define the method inside your module similar to class methods:
module Search
module Score
def self.get_score
return 'something'
end
end
end
If you do that, you can call get_score like expected:
require 'search'
class User < ActiveRecord::Base
def get_user_score
p Search::Score.get_score # => "something"
end
end
See this tutorial for a more in depth explanation about modules in Ruby.
First, see "Best Practices for reusing code between controllers in Ruby on Rails".
About reuse code as a module, take a look at "Rethinking code reuse with Modularity for Ruby".
"Modules are crippled classes"
Modules are like crippled classes in Ruby. If you look into the inheritance chain you see that a Class actually inherits from Module.
Module cannot be instanciated. So the call to .new is not working.
What you CAN do however is to specify your method as a 'class' method (I know I said it is not a class...)
So you would add a self in front like this:
module Search
module Score
def self.get_score
return 'something'
end
end
end
Then you can call this method as a class method like you tried in your code example
Search::Score is a module and not a class, so Score.new will not work.
You can try to change the signature of the get_score function to self.get_score.
In addition to def self.get_score in the above answers, there is also extend self, like so:
module Search
module Score
extend self
def get_score
return 'something'
end
end
end
and module_function:
module Search
module Score
module_function
def get_score
return 'something'
end
end
end
The latter is actually the preferred method in RuboCop (source), though in practice I personally have not seen it so often.

Nested Modules and Method Hooks

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.

Module scope not right

I am trying to be dry by moving common methods into a module or a class and have it included/inherited in new classes that are namespaced under different modules. If I have two classes namespaces under the same module, then I can call them without including module name as long as I am under the same namespace. But if i have a method included from different module than my namespace scope changes and I dont know why or how to avoid it.
For example. this code works and returns 'bar':
module Foo
class Bar
def test_it
Helper.new.foo
end
end
end
module Foo
class Helper
def foo
'bar'
end
end
end
Foo::Bar.new.test_it
but if I move out method test_it into a module, then it doesnt work anymore: NameError: uninitialized constant Mixins::A::Helper.
module Mixins; end
module Mixins::A
def self.included(base)
base.class_eval do
def test_it
Helper.new.foo
end
end
end
end
module Foo
class Bar
include Mixins::A
end
end
module Foo
class Helper
def foo
'bar'
end
end
end
Foo::Bar.new.test_it
Moreover if class_eval is evaling string instead of block, scope becomes Foo::Bar instead of Foo.
module Mixins; end
module Mixins::A
def self.included(base)
base.class_eval %q{
def test_it
Helper.new.foo
end
}
end
end
module Foo
class Bar
include Mixins::A
end
end
module Foo
class Helper
def foo
'bar'
end
end
end
Foo::Bar.new.test_it
anyone got ideas?
EDIT:
Thanks to Wizard and Alex, I ended up with this code which is not beautiful but does the job (note it is using Rails helper constantize):
module Mixins; end
module Mixins::A
def self.included(base)
base.class_eval do
def test_it
_nesting::Helper
end
def _nesting
#_nesting ||= self.class.name.split('::')[0..-2].join('::').constantize
end
end
end
end
module Foo
class Helper
end
class Bar
include Mixins::A
end
end
module Foo2
class Helper
end
class Bar
include Mixins::A
end
end
Foo::Bar.new.test_it #=> returns Foo::Helper
Foo2::Bar.new.test_it #=> returns Foo2::Helper
To understand this problem, you need to understand how constant lookup works in Ruby. It's not the same as method lookup. In this code:
module Mixins::A
def self.included(base)
base.class_eval do
def test_it
Helper.new.foo
end
end
end
end
Helper refers to a constant called "Helper" which is either in A, Mixins, or defined at the top level (ie. in Object), not a constant called "Helper" which is defined in Bar. Just because you class_eval this code with class Bar, doesn't change that. If you know the difference between "lexical binding" and "dynamic binding", then you can say that constant resolution in Ruby uses lexical binding. You are expecting it to use dynamic binding.
Remember that the block which you are passing to base.class_eval is compiled to bytecode once, and subsequently, every time the included hook is called, that same precompiled block (including the reference to Helper) is executed with a different class (base) as self. The interpreter does not parse and compile the block afresh every time you execute base.class_eval.
On the other hand, if you pass a String to class_eval, that string is parsed and compiled afresh every time the included hook runs. IMPORTANT: code which is evaled from a String is evaluated in a null lexical environment. That means that local variables from the surrounding method are not available to the code being evaled from the string. More to the point for you, it also means that the surrounding scope will not affect constant lookup from within the evaled code.
If you do want a constant reference to be resolved dynamically, an explicit constant reference will never work. That's simply not the way the language is intended to work (and for good reason). Think about it: if constant references were resolved dynamically, depending on the class of self, you could never predict how references to things like Array or Hash would be resolved at runtime. If you has code like this in a module...
hash = Hash[array.map { |x| ... }]
...And the module was mixed in to a class with a nested Hash class, Hash.[] would refer to the nested class rather than Hash from the standard library! Clearly, resolving constant references dynamically has just too much potential for name clashes and related bugs.
Now with method lookup, that's a different thing. The whole concept of OOP (at least the Ruby flavor of OOP) is that what a method call (ie. a message) does depends on the class of the receiver.
If you do want to find a constant dynamically, depending on the class of the receiver, you can do it using self.class.const_get. This is arguably cleaner than evaling a String to achieve the same effect.
module Mixins::A
def self.included(base)
base.class_eval do
def test_it
Foo::Helper.new.foo
EDIT:
After getting a chance to play around with code a bit I see more of the problem. I don't think you'll be able to do exactly what you were attempting, but this is close:
module Mixins::A
def self.included(base)
base.class_eval do
def test_it
self.class.const_get(:Helper).new.foo
end
end
end
end
module Foo
class Bar
include Mixins::A
end
end
module Foo
class Bar::Helper
def foo
'bar'
end
end
end
Note that the Helper class needed to be namespaced under Foo::Bar due to way constants get resolved in Ruby.
Constant-lookup in Ruby has changed with respect to #class_eval over the past few major releases. See this post for more information: http://jfire.posterous.com/constant-lookup-in-ruby

Resources