Declaring class methods in class_methods or included - ruby-on-rails

require 'active_support/concern'
module M
extend ActiveSupport::Concern
included do
def self.b
puts 'b'
end
end
class_methods do
def a
puts 'a'
end
end
end
class H
include M
end
Though class_methods is the standard way to define class methods in ActiveSupport::Concern, I do find a few examples where people declare class methods in included by using self.
Both works, so are there edgecases if class methods are defined inside included?

Related

How do I use an ActiveSupport Concern to add a refinement to a class

I have a concern that adds some helper methods to the including class. How can I automatically add a refinement to the including class?
The following example works, however, I'd like not explicitly have to state using MyConcern in MyClass
module MyConcern
extend ActiveSupport::Concern
class_methods do
def test_method
'1'
end
end
refine String do
def refined_method
self
end
end
end
class MyClass
include MyConcern
using MyConcern
def self.my_method
test_method.refined_method
end
end

What does class_methods do in concerns?

I am reading some codes which use the concerns in Rails 4.
I read some articles to say, if we would like to include class methods
using module ClassMethods, but the code I read using something like:
class_methods do
def ****
end
end
ActiveSupport::Concern provides syntactic sugar for common Ruby patterns for module mixins.
When you are using modules as mixins you can't just use self to declare class methods like you would from a class:
module Foo
def self.bar
"Hello World"
end
def instance_method
"Hello World"
end
end
class Baz
include Foo
end
irb(main):010:0> Baz.bar
NoMethodError: undefined method `bar' for Baz:Class
from (irb):10
irb(main):011:0> Foo.bar
=> "Hello World"
irb(main):012:0>
As you can see from the example that actually creates a module method - thats because self is the module. You can use extend instead:
module Foo
def a_class_method
"Hello World"
end
end
class Bar
extend Foo
end
irb(main):049:0> Bar.a_class_method
=> "Hello World"
But that does not let you declare instance methods in the module. Which is not really that useful.
So the solution is to create an inner module which is commonly named ClassMethods and extend the class when the module is included:
module Foo
# this is a method thats called when you include the module in a class.
def self.included(base)
base.extend ClassMethods
end
def an_instance_method
end
# the name ClassMethods is just convention.
module ClassMethods
def a_class_method
"Hello World"
end
end
end
class Bar
include Foo
end
irb(main):071:0> Bar.a_class_method
=> "Hello World"
This boilerplate code is found in almost every Ruby gem/library.
By extending your module with ActiveSupport::Concern you can shorten this to just:
module Foo
extend ActiveSupport::Concern
class_methods do
def a_class_method
"Hello World"
end
end
end
Under the hood ActiveSupport::Concern creates a ClassMethods module and evaluates the block in the context of the module. Dig into the source if you curious about how it actually does this.
It's just for convenience. module ClassMethods is pure Ruby, but class_methods is defined in ActiveSupport::Concern for convenience. If you look at a source code you'll find that class_methods does exactly the same thing
# activesupport/lib/concern.rb
def class_methods(&class_methods_module_definition)
mod = const_defined?(:ClassMethods, false) ?
const_get(:ClassMethods) :
const_set(:ClassMethods, Module.new)
mod.module_eval(&class_methods_module_definition)
end
class_methods is used to add class methods to the model used by the concern.
A typical module looks like this:
module M
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
...
end
end
By using ActiveSupport::Concern the above module could instead be written as:
require 'active_support/concern'
module M
extend ActiveSupport::Concern
class_methods do
...
end
end
As Oleg Antonyan pointed out, from the source code, we know that it's going to use ClassMethods module under the hood.
Reference: http://api.rubyonrails.org/classes/ActiveSupport/Concern.html

Ruby on Rails module include module

I want to include a module in a rails helper(is also a module).
The helper is:
module SportHelper
.....
end
And the module is:
module Formula
def say()
....
end
end
Now, I want to use the method say in SportHelper. What should I do?
If I write like this:
module SportHelper
def speak1()
require 'formula'
extend Formula
say()
end
def speak2()
require 'formula'
extend Formula
say()
end
end
This will work, but I don't want to do so, I just want to add the methods on the helper module,not every methods.
You need just to include this module in your helper:
require 'formula'
module SportHelper
include Formula
def speak1
say
end
def speak2
say
end
end
Maybe you don't need this line require 'formula', if it's already in the load path. For check this you can inspect $LOAD_PATH variable. For more information see this answer.
Basic difference between extend and include is that include is for adding methods to an instance of a class and extend is for adding class methods.
module Foo
def foo
puts 'heyyyyoooo!'
end
end
class Bar
include Foo
end
Bar.new.foo # heyyyyoooo!
Bar.foo # NoMethodError: undefined method ‘foo’ for Bar:Class
class Baz
extend Foo
end
Baz.foo # heyyyyoooo!
Baz.new.foo # NoMethodError: undefined method ‘foo’ for #<Baz:0x1e708>
And if you use extend inside the object method, it will adding methods to an instance of a class, but they would be available only inside this one method.
I think directly include should work
module SportHelper
include SportHelper
.........
end
end
I tested like below:
module A
def test
puts "aaaa"
end
end
module B
include A
def test1
test
end
end
class C
include B
end
c = C.new()
c.test1 #=> aaaa
It should work.

Rails: dynamically define class method based on parent class name within module/concern

I want to dynamically generate a class method in a Mixin, based on the class name that include this Mixin.
Here is my current code:
module MyModule
extend ActiveSupport::Concern
# def some_methods
# ...
# end
module ClassMethods
# Here is where I'm stuck...
define_method "#{self.name.downcase}_status" do
# do something...
end
end
end
class MyClass < ActiveRecord::Base
include MyModule
end
# What I'm trying to achieve:
MyClass.myclass_status
But this give me the following method name:
MyClass.mymodule::classmethods_status
Getting the base class name inside the method definition works (self, self.name...) but I can't make it works for the method name...
So far, I've tried
define_method "#{self}"
define_method "#{self.name"
define_method "#{self.class}"
define_method "#{self.class.name}"
define_method "#{self.model_name}"
define_method "#{self.parent.name}"
But none of this seems to do the trick :/
Is there any way I can retrieve the base class name (not sure what to call the class that include my module). I've been struggling with this problem for hours now and I can't seem to figure out a clean solution :(
Thanks!
I found a clean solution: using define_singleton_method (available in ruby v1.9.3)
module MyModule
extend ActiveSupport::Concern
included do
define_singleton_method "#{self.name}_status" do
# do stuff
end
end
# def some_methods
# ...
# end
module ClassMethods
# Not needed anymore!
end
end
You can't do it like that - at this point it is not yet known which class (or classes) are including the module.
If you define a self.included method it will be called each time the module is included and the thing doing the including will be passed as an argument. Alternatively since you are using AS::Concern you can do
included do
#code here is executed in the context of the including class
end
You can do something like this:
module MyModule
def self.included(base)
(class << base; self; end).send(:define_method, "#{base.name.downcase}_status") do
puts "Hey!"
end
base.extend(ClassMethods)
end
module ClassMethods
def other_method
puts "Hi!"
end
end
end
class MyClass
include MyModule
end
MyClass.myclass_status
MyClass.other_method
Works for extend:
module MyModule
def self.extended who
define_method "#{who.name.downcase}_status" do
p "Inside"
end
end
end
class MyClass
extend MyModule
end
MyClass.myclass_status

Make ClassMethods also available as module function with ActiveSupport::Concern

Given the following code:
module Foo
extend ActiveSupport::Concern
module ClassMethods
def foo
puts 'foo'
end
end
end
class Bar
include Foo
end
What I'd like to do is call Foo.foo instead of Bar.foo. Sometimes it feels more natural to call a class method on the original module, especially when the functionality has nothing to do with the included class and is better described along with the original module name.
This seems like a code smell. Having said that, you can just have the Foo module extend itself with the class methods:
module Foo
extend ActiveSupport::Concern
module ClassMethods
def foo
puts 'foo'
end
end
extend ClassMethods
end
class Bar
include Foo
end
Bar.foo
Foo.foo

Resources