I'd like to patch parts of an existing library with a config object:
module Library
class A
end
class B
end
end
module Config
def config(&block)
#config ||= block&.call
end
end
module Patch
refine Library::A.singleton_class do
extend Config
config { "my for A" }
end
refine Library::B.singleton_class do
extend Config
config { "my for B" }
end
end
Library::A should have some predefined config and Library::B should have its own config. Unfortunately, this throws an error:
using Patch
Library::A.config
=> undefined method `config' for Library::A:Class (NoMethodError)
I guess I don't understand how refinements work in this case. But is there a way to achieve something like this?
here is my solution, basically i include Config to the Patch to be able to define configurations at class level, then include the Patch itself to the refined classes (Library::A and Library::B) to be able to access the defined configurations.
module Config
def self.included(base)
class << base
def refinement(clazz, patch=self, &block)
$predefined_configs ||= Hash.new
$predefined_configs[clazz] ||= Hash.new
block&.call($predefined_configs[clazz])
refine clazz.singleton_class do
include patch
end
end
end
end
def config
$predefined_configs[self].tap do |_config|
# reuse common configs
_config.merge!({
lang: "ruby"
})
end
end
end
then
module Library
class A; end
class B; end
end
module Patch
include Config
refinement(Library::A) do |_config|
_config[:name] = "my for A"
end
refinement(Library::B) do |_config|
_config[:name] = "my for B"
end
end
class LoadConfig
using Patch
puts Library::A.config # {:name=>"my for A", :lang=>"ruby"}
end
puts Library::A.config # error
using Patch
puts Library::B.config # {:name=>"my for B", :lang=>"ruby"}
Related
Can anybody explain def self.extended(base), what does it mean here or any idea?
module Paperclip
module Storage
module Dropbox
def self.extended(base)
base.instance_eval do
#options[:dropbox_options] ||= {}
#options[:path] = nil if #options[:path] ==
self.class.default_options[:path]
#options[:dropbox_visibility] ||= "public"
#path_generator = PathGenerator.new(self, #options)
#dropbox_client # Force creation of dropbox_client
end
end
end
end
end
The self.extended method is called when the module is extended. It allows methods to be executed in the context of the base (where the module is extended).
You can try it yourself and understand this with a simple example code. Just paste in a file ruby file and run it.
Example for self.extended
module A
def self.extended(base)
puts "#{self} extended in #{base}"
end
end
class Apple
extend A
end
# This should print: "A extended in Apple"
Example for self.included
module A
def self.included(base)
puts "#{self} included in #{base}"
end
end
class Apple
include A
end
# This should print: "A included in Apple"
You can read more here: http://juixe.com/techknow/index.php/2006/06/15/mixins-in-ruby/
I have Ruby class into which I want to include both class and instance methods. Following the pattern described here, I'm currently using the following:
class SomeObject
include SomeObject::Ability
def self.some_builder_method(params)
# use some_class_method ...
end
end
module SomeObject::Ability
module ClassMethods
def some_class_method(param)
# ...
end
end
def self.included(klass)
klass.extend(ClassMethods)
end
def some_instance_method
# ...
end
end
I'd rather not make two separate modules (one being included and the other being extended), because all the methods in my module logically fit together. On the other hand, this pattern a) requires me to define an additional ClassMethods module and b) requires me to write a boilerplate self.included method for every module.
Is there a better way to do this?
Edit 1: I've found another way, but I'm unsure if this is better than the first.
module Concern
def included(base)
# Define instance methods.
instance_methods.each do |m|
defn = instance_method(m)
base.class_eval { define_method(m, defn) }
end
# Define class methods.
(self.methods - Module.methods).each do |m|
unless m == __method__
base.define_singleton_method(m, &method(m))
end
end
end
end
module SomeModule
extend Concern
def self.class_m
puts "Class"
end
def instance_m
puts "Instance"
end
end
class Allo
include SomeModule
end
Allo.class_m # => "Class"
Allo.new.instance_m # => "Instance"
If I understand you correctly, you really just want to use ActiveSupport::Concern:
module PetWorthy
extend ActiveSupport::Concern
included do
validates :was_pet, inclusion: [true, 'yes']
end
def pet #instance method
end
module ClassMethods
def find_petworthy_animal
# ...
end
end
end
class Kitty
include PetWorthy
end
Kitty.find_petworthy_animal.pet
You (hopefully obviously) don't need to use the included method if you don't have any behavior to trigger on include, but I put it in just to demonstrate.
I need to access the config variables from inside another class of a module.
In test.rb, how can I get the config values from client.rb? #config gives me an uninitialized var. It's in the same module but a different class.
Is the best bet to create a new instance of config? If so how do I get the argument passed in through run.rb?
Or, am I just structuring this all wrong or should I be using attr_accessor?
client.rb
module Cli
class Client
def initialize(config_file)
#config_file = config_file
#debug = false
end
def config
#config ||= Config.new(#config_file)
end
def startitup
Cli::Easy.test
end
end
end
config.rb
module Cli
class Config
def initialize(config_path)
#config_path = config_path
#config = {}
load
end
def load
begin
#config = YAML.load_file(#config_path)
rescue
nil
end
end
end
end
test.rb
module Cli
class Easy
def self.test
puts #config
end
end
end
run.rb
client = Cli::Client.new("path/to/my/config.yaml")
client.startitup
#config is a instance variable, if you want get it from outside you need to provide accessor, and give to Easy class self object.
client.rb:
attr_reader :config
#...
def startitup
Cli::Easy.test(self)
end
test.rb
def self.test(klass)
puts klass.config
end
If you use ##config, then you can acces to this variable without giving a self object, with class_variable_get.
class Lol
##lold = 0
def initialize(a)
##lold = a
end
end
x = Lol.new(4)
puts Lol.class_variable_get("##lold")
I recommend to you read metaprogramming ruby book.
Have a look at the code below
initshared.rb
module InitShared
def init_shared
#shared_obj = "foobar"
end
end
myclass.rb
class MyClass
def initialize()
end
def init
file_name = Dir.pwd+"/initshared.rb"
if File.file?(file_name)
require file_name
include InitShared
if self.respond_to?'init_shared'
init_shared
puts #shared_obj
end
end
end
end
The include InitShared dosn't work since its inside the method .
I want to check for the file and then include the module and then access the variables in that module.
Instead of using Samnang's
singleton_class.send(:include, InitShared)
you can also use
extend InitShared
It does the same, but is version independent. It will include the module only into the objects own singleton class.
module InitShared
def init_shared
#shared_obj = "foobar"
end
end
class MyClass
def init
if true
self.class.send(:include, InitShared)
if self.respond_to?'init_shared'
init_shared
puts #shared_obj
end
end
end
end
MyClass.new.init
:include is a private class method, so you can't call it in instance level method. Another solution if you want to include that module only for specific instance you can replace the line with :include with this line:
# Ruby 1.9.2
self.singleton_class.send(:include, InitShared)
# Ruby 1.8.x
singleton_class = class << self; self; end
singleton_class.send(:include, InitShared)
I need to define the constant in the module that use the method from the class that includes this module:
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
But the compiler gives the error on the 4th line.
Is there any other way to define the constant?
The more idiomatic way to achieve this in Ruby is:
module B
def self.included(klass)
klass.class_eval <<-ruby_eval
CONST = find
ruby_eval
# note that the block form of class_eval won't work
# because you can't assign a constant inside a method
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
What you were doing (class << base) actually puts you into the context of A's metaclass, not A itself. The find method is on A itself, not its metaclass. The thing to keep in mind is that classes are themselves objects, and so have their own metaclasses.
To try to make it clearer:
class Human
def parent
# this method is on the Human class and available
# to all instances of Human.
end
class << self
def build
# this method is on the Human metaclass, and
# available to its instance, Human itself.
end
# the "self" here is Human's metaclass, so build
# cannot be called.
end
def self.build
# exactly the same as the above
end
build # the "self" here is Human itself, so build can
# be called
end
Not sure if that helps, but if you don't understand it, you can still use the class_eval idiom above.
In your specific case.
module B
def self.included(base)
base.const_set("CONST", base.find)
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
Despite it works, it's a little bit messy. Are you sure you can't follow a different way to achieve your goal?
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
class << self
def self.find
"AAA"
end
end
include B
end
then the compiler error is fixed, pls try.