Is it possible to reverse the included module in a class? - ruby-on-rails

You include the modules in classes to extend the class functionality in terms of both adding class methods and instance methods to that particular class.
module M
def self.class_method_from_module
'from class_method_from_module'
end
def instance_method_from_module
'from instance_method_from_module'
end
end
class C
include M
def self.class_method
'from class_method'
end
def instance_method
'from instance_method'
end
end
puts C.class_method => 'from class_method'
puts C.class_method_from_module => 'from class_method_from_module'
puts C.new.instance_method => 'from instance_method'
puts C.new.instance_method_from_module => 'instance_method_from_module'
Now even after removing the module M from the memory with the following:
Object.send(:remove_const, :M) #remove the module M
puts C.class_method_from_module => 'from class_method_from_module'
puts C.new.instance_method_from_module => 'instance_method_from_module'
instead of method missing. Why is that? What is the best way to remove the functionality added by a module to a class?

The details vary by implementation, but I know that at least in JRuby and MRI 1.8 there is a construct called an Include Class that is inserted in to the inheritance chain of a class when a Module is extended or included. The Module therefore won't be garbage collected, since the Include Class still refers to it, and the methods will still be on your class. There are some great articles by Patrick Farley on this and related topics in his blog.
So, to "remove" a module, you could individually undefine each method that came from the module, but that's a pretty unwieldy mechanism for that purpose. If using a gem is acceptable to you, it would probably be better would be to use Mixology, which was designed specifically for the purpose of adding and removing modules dynamically.

When you include a mixin in a class, you are effectively adding the methods defined in the module to the class, or replacing methods in the class with those in the module that have the same name. The class does not have any tie to the module, which is why 'undefining' the M module won't affect class C. All that does is prevent you from mixing in M beyond that point.
You could use undef_method to remove methods from class C, but that will have side effects, potentially -- if a method was overriden by including a module, you will not get the original back, for instance. Undefining a class method is kind of ugly.
C.send(:undef_method, :instance_method_from_module)
class << C
self
end.send(:undef_method, :class_method_from_module)

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

Ambiguity in Ruby class name resolution

I recently did some code refactoring in a Rails codebase in which I converted some code that defined some classes like this (class bodies elided):
foo/user_bar.rb:
module Foo
class UserBar; end
end
foo/sub_user_bar.rb:
module Foo
class SubUserBar < UserBar; end
end
...to:
In foo/bar/user.rb:
module Foo
module Bar
class User; end
end
end
In foo/bar/sub_user.rb:
module Foo
module Bar
class SubUser < User; end
end
end
This introduced a subtle problem in that there was already an existing top-level class called User (a Rails model), and SubUser was being derived from that, not from User in the same module. I'm guess that this is because the order that Rails loads classes is unpredictable...?
Anyway, I found that I could disambiguate by declaring the SubUser class like this:
class SubUser < self::User
...which seems to work, although I couldn't find any examples of it in a modest amount of searching. It does look a little weird, though. And now I'm concerned that I really ought to be doing this for every class in my module, just in case a top-level class with the same name is ever introduced.
There are two styles of in-module class declarations throughout my company's codebase:
class A::B::Foo; end
And:
module A; module B; class Foo; end; end; end
I've always preferred the latter method since it allows for access to other names in the module without fully-qualified names, but now I've found that there's potentially a trap when I use it. So what's the best practice here?
Don't declare classes within a module; always use fully qualified names (class A::B::C::Foo < A::B::C::Bar)
Declare classes in a module but always fully qualify superclass names (module A; module B; module C; class Foo < A::B::C::Bar)
Declare classes in a module and use unqualified names but always use self:: when extending a class in the same module
Declare classes in a module and use unqualified names but stay aware of possible name collisions in the top-level namespace (seems risky)
Or do Ruby and/or Rails offer some other, cleaner way to resolve the issue?
Generally where there's ambiguity you specify full namespace paths:
module Foo
module Bar
class SubUser < Foo::Bar::User; end
end
end
That might seem verbose, but it's also specific and unambiguous.
Where you want to refer to literal top-level User you'd specify ::User.
Modules are identified by constants.1 The resolution of module look-ups is therefore determined by the procedure for looking up constant references. According to "The Ruby Programming Language", by David Flanagan and Yukihero Matsumoto, 1st Ed. 2008 (p. 261-262), constant look-ups are performed in the following order, where mod is the innermost module that encloses the reference to the constant:
mod.
the next enclosing module, continuing until there are no more enclosing modules. (Top-level/global constants are not considered at this step). The class method Module::nesting returns an array of modules that are so searched, in the order in which they are searched.
the elements of mod.ancestors, in index order (i.e., the inheritance hierarchy).
top-level constant definitions.
the value returned by the method Module#const_missing, with mod being the receiver and the constant expressed as a symbol being the argument, provided the method has been defined.
Let's see what we may learn by inserting puts Module.nesting.inspect and puts ancestors statements into the code.
module Foo
class UserBar
puts "nesting for UserBar=#{Module.nesting.inspect}"
puts "#{self}.ancestors=#{ancestors}"
end
end
# nesting for UserBar=[Foo::UserBar, Foo]
# Foo::UserBar.ancestors=[Foo::UserBar, Object, Kernel,
# BasicObject]
module Foo
class SubUserBar < UserBar
puts "nesting for SubUserBar=#{Module.nesting.inspect}"
puts "#{self}.ancestors=#{ancestors}"
end
end
# nesting for SubUserBar=[Foo::SubUserBar, Foo]
# Foo::SubUserBar.ancestors=[Foo::SubUserBar,
# Foo::UserBar, Object, Kernel, BasicObject]
module Foo
module Bar
class User
puts "nesting for User=#{Module.nesting.inspect}"
puts "#{self}.ancestors=#{ancestors}"
end
end
end
# nesting for User=[Foo::Bar::User, Foo::Bar, Foo]
# Foo::Bar::User.ancestors=[Foo::Bar::User, Object, Kernel,
# BasicObject]
module Foo
module Bar
class SubUser < User
puts "nesting for SubBar=#{Module.nesting.inspect}"
puts "#{self}.ancestors=#{ancestors}"
end
end
end
# nesting for SubBar=[Foo::Bar::SubUser, Foo::Bar, Foo]
# Foo::Bar::SubUser.ancestors=[Foo::Bar::SubUser,
# Foo::Bar::User, Object, Kernel, BasicObject]
This tells us that if there is a class User that is not contained within Foo (as in Rails), that class would the subject of references to User in the classes Foo::UserBar and Foo::SubUserBar, but not in Foo::Bar::User or Foo::Bar::SubUser. This understanding should help in organising modules to avoid namespace problems.
1 As classes are modules, whenever I reference "module" below I am referring to classes as well as modules that are not classes.

How to inherit class variables from parents included module in Ruby

I am trying to develop a reusable module for Active Record models to be used shared in models. It works well except child classes can't find the variables.
This is easier demonstrated with code
This is the module:
module Scanner
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def scan(*fields)
#scan = fields if fields.present?
#scan
end
end
end
Usage:
class User < ActiveRecord::Base
include Scanner
scan :user_name
end
Calling User.scan returns :user_name. Perfect!
However. This is an issue:
class Admin < User
end
Calling Admin.scan returns nil
How come :user_name is not being set in the parent? I am following the source for AR for methods like primary_key and table_name, they seem to be inherting values from the parent
class_attribute takes care of the required inheritance semantics for you. As you've seen, it's not quite as simple as putting an instance variable on the class, because that leaves it nil on subclasses: they're instance variables, and that's a separate instance.
The examples you mention do other, more specialised and more complicated, things... but you'll find plenty of straightforward examples in the Rails source that do use class_attribute.

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

Can I have a summarize of what Ruby module do?

I do know it is a namespace thing ... can anyone give me some sample code ... i understand things fast with sample code ... thanks!
A module in ruby can be used for 3 possible things
1. Namespacing
This is pretty straight forward.
module Foo
class Bar
end
end
f = Foo::Bar.new
2. Collection of functions
Sometimes, you will have some functions that don't really fit in any class. In something like java, you would just put them as a bunch of static methods on a class. In ruby, you would put them on a module, since having them on a class implies the class is intended to be instanciated
module FooHelper
def self.bar
puts 'hi'
end
end
FooHelper.bar # => hi
3. Mixins
This is the hardest to understand of all 3. Basically, it is rubys answer to multiple inheritance in C, or interfaces in java.
Sometimes you have logic which belongs in several classes, but at the same time doesn't fit as a parent class. A mixin describes "mixing" a modules methods into a class. There is actually a lot more to this, but at an extremely high level, it would look like this
module CanFoo
def foo
puts 'bar'
end
end
class Baz
include CanFoo
end
class Bar
include CanFoo
end
baz.new.foo # => bar
bar.new.foo # => bar
mixins are a fairly advanced topic, and it takes a bit of time to understand when you would use one over a super class. IMO they are one of the coolest features of ruby though, and handles the multiple inheritance problem with a great deal more elegance then any other OO language I have looked at.
A module is a collection of constants, class and functions inside a namespace. Here is an example:
module Payments
CARD_TYPES = ["visa", "mastercard"]
class CreditCard
attr_accessor :number
attr_accessor :type
end
extend self
def process_payment
...
end
end
I now have a Payments module. I can call Payments::CARD_TYPES to get an array; Payments::CreditCard.new to create an object and Payments::process_payment to call a function.

Resources