"Namespaced" methods in Ruby - ruby

There is a class with "namespaced" methods:
class MyOrder
def add
end
def edit
end
end
class MyCompany
def list
end
end
class MyDriver
def add
end
def edit
end
end
class MyAPI
attr_reader :order, :company, :driver
def initialize
#order = MyOrder.new
#company = MyCompany.new
#driver = MyDriver.new
end
end
The idea is to group methods into sections to call them like:
api = MyApi.new
api.order.add
api.company.list
Is there some way to group methods without creating container classes?

class MyAPI
def initialize
#namespace = []
end
def self.namespace sym
define_method(sym){#namespace.push(sym); self}
end
namespace :order
namespace :company
namespace :driver
def add
case #namespace
when [:order] then ...
when [:driver] then ...
else raise "invalid namespace"
end
#namespace = []
self
end
def edit
case #namespace
when [:order] then ...
when [:driver] then ...
else raise "invalid namespace"
end
#namespace = []
self
end
def list
case #namespace
when [:company] then ...
else raise "invalid namespace"
end
#namespace = []
self
end
end
The self at the end of the methods is to let you be able to do chaining like
MyApi.new.order.add.company.list
If you don't need to do this, then self is unnecessary.

Ok, I see, try the following:
module MyApi
[MyOrder, MyCompany, MyDriver].each do |klass|
names = klass.to_s.scan(/[A-Z][a-z]+/)
funcname = names[1].downcase
define_singleton_method(funcname) do
instance_variable_get("##{funcname}") ||
instance_variable_set("##{funcname}", klass.new)
end
end
end
MyApi.order.add
If you prefer to order_add, then:
module MyApi
[MyOrder, MyCompany, MyDriver].each do |klass|
names = klass.to_s.scan(/[A-Z][a-z]+/)
prefix_name = names[1].downcase
klass.public_instance_methods(false).each do |method|
funcname = "#{prefix_name}_#{method}"
define_singleton_method(funcname) do |*args|
#obj = instance_variable_get("##{prefix_name}") ||
instance_variable_set("##{prefix_name}", klass.new)
#obj.send(method, *args)
end
end
end
end

Related

How to metaprogram routes dinamicaly?

So there should be a class called Registers::Internal::Routes with a class method called draw that takes a block as a parameter:
def self.draw(&block)
...
end
It should be possible to pass the structures of the routes in this block. Example:
Registers::Internal::Routes.draw do
namespace :api do
namespace :internal do
namespace :companies do
resources :registrations, only: [:index, :show] do
post :request
member do
post :cancel
end
resources :bank_accounts, only: :index
end
end
end
end
end
it should be possible to acquire the URL of the routes, for example:
Registers::Internal::Routes.api_internal_companies_registrations_url
# api/internal/companies/registrations
Registers::Internal::Routes.api_internal_companies_registration_url("aef54ea9-239c-42c7-aee7-670a0d454f1d")
# api/internal/companies/registrations/aef54ea9-239c-42c7-aee7-670a0d454f1d
Registers::Internal::Routes.api_internal_companies_registrations_request_url
# api/internal/companies/registrations/request
Registers::Internal::Routes.api_internal_companies_registration_cancel_url("aef54ea9-239c-42c7-aee7-670a0d454f1d")
# api/internal/companies/registrations/aef54ea9-239c-42c7-aee7-670a0d454f1d/cancel
Registers::Internal::Routes.api_internal_companies_registration_bank_accounts_url("aef54ea9-239c-42c7-aee7-670a0d454f1d")
# api/internal/companies/registrations/aef54ea9-239c-42c7-aee7-670a0d454f1d/bank_accounts
So after playing a bit with this, i "managed" to try to generate an "url" everytime the namespace/resources/member/post method is called, the problem is that the "url" variable doesnt reset, therefore resulting in it being overwritten. Is there any other way to do this, simple and cleaner?
module Registers
module Internal
class Routes
def self.draw(&block)
#url = ""
class_eval(&block)
end
def self.namespace(key, &block)
generate_url("namespace", key)
class_eval(&block)
end
def self.resources(key, options, &block)
generate_url("resources", key, options)
class_eval(&block) if block_given?
end
def self.member(&block)
class_eval(&block)
end
def self.post(action)
generate_url("action", action)
end
def self.generate_url(name, key, options = nil)
if name != "action"
#url << "#{key.to_s + '/'}"
else
#url << "/#{key.to_s}"
define_class_method(#url)
end
set_options(options) if options.present?
end
def self.set_options(options)
Array(options[:only]).each do |option|
case option
when :index
#url.chop!
when :show
#url
end
define_class_method(#url)
end
end
def self.define_class_method(url)
method_name = "#{url.gsub('/', '_')}_url".to_sym
self.class.instance_eval do
define_method(method_name) do
url
end
end
end
end
end
end

How to implement an Mongoid Document like class?

I'm building a small script where I'd need to implement a Mongoid Document like class, where I include my base module and then I can build a class which looks like:
class MyClass
include MyBaseModule
field :some_field, :attr => 'attributes'
end
This is my last try:
module Model
def initialize(keys = {})
puts ##keys
end
def method_missing sym, *args
if sym =~ /^(\w+)=$/
if ##keys.has_key?($1)
##keys[$1.to_sym] = args[0]
else
nil
end
else
if ##keys.has_key?($1)
##keys[sym.to_sym]
else
nil
end
end
end
def inspect
puts "#<#{self.class} #keys=#{##keys.each {|k,v| "#{k} => #{v}"}}>"
end
def self.included(base)
base.extend(ClassMethods)
end
def save
##keys.each do |k, v|
SimpleMongo::connection.collection.insert({k => v})
end
end
module ClassMethods
def field(name, args)
if ##keys.nil?
##keys = {}
end
##keys[name.to_sym] = default_value
end
end
end
Mongoid documents look like this:
class StoredFile
include Mongoid::Document
field :name, type: String
field :description, type: String
field :password, type: String
field :public, type: Boolean
field :shortlink, type: String
mount_uploader :stored_file, StoredFileUploader
before_save :gen_shortlink
before_save :hash_password
belongs_to :user
def gen_shortlink
self.shortlink = rand(36**10).to_s(36)
end
def public?
self.public
end
def hash_password
require 'bcrypt'
self.password = BCrypt::Password.create(self.password).to_s
end
def check_pass(password)
BCrypt::Password.new(self.password) == password
end
end
It doesn't works because the ##keys variable inside ClassMethods isn't available anywhere outside that module. What would be the easiest way to implement this? Thanks!
The easiest way to implement it would be to have a class variable getter.
module Model
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def keys
#keys ||= {}
end
def field(name, opts)
#keys ||= {}
#keys[name] = opts
end
end
def initialize(attributes)
# stuff
puts self.class.keys
end
end

Define a class method in a module with a variable name

I created a module which is included in a class. In the module, I am trying to define a method that is the downcased version of a class name without Filter. So ShowFilter would have a method called show that returns the class Show. I get "NoMethodError:
undefined method `show' for ShowFilter:Class"
module Filters
module Base
module ClassMethods
##filters = {}
def filter name, &block
##filters[name] = block
end
def run query = {}
query.each do |name, value|
##filters[name.to_sym].call(value) unless ##filters[name.to_sym].nil?
end
self
end
def self.extended(base)
name = base.class.name.gsub(/filter/i, '')
define_method(name.downcase.to_sym) { Kernel.const_get name }
end
end
def self.included base
base.extend ClassMethods
end
end
end
class ShowFilter
include Filters::Base
filter :name do |name|
self.show.where(:name => name)
end
end
EDIT: Example of use
class ShowController < ApplicationController
def index
ShowFilter.run params[:query]
end
end
When you define Filters::Base::ClassMethods, it evaluates self in that context so the method you'll end up defining is ClassMethods.classmethods (since the gsub won't do anything).
Like the included hook you tapped into in Base, you want to use extended in ClassMethods:
module Filters
module Base
module ClassMethods
##filters = {}
def filter name, &block
##filters[name] = block
end
def run query = {}
query.each do |name, value|
##filters[name.to_sym].call(value) unless ##filters[name.to_sym].nil?
end
Object.const_get(self.to_s.gsub('Filter', ''))
end
def self.extended(base)
define_method(base.to_s.downcase.gsub('filter', '').to_sym) do
Object.const_get(self.to_s.gsub('Filter', ''))
end
end
end
def self.included base
base.extend ClassMethods
end
end
end
class ShowFilter
include Filters::Base
filter :title do |title|
self.show.where(:title => title)
end
end

Customising attr_reader to do lazy instantiation of attributes

(Big edit, I got part of the way there…)
I've been hacking away and I've come up with this as a way to specify things that need to be done before attributes are read:
class Class
def attr_reader(*params)
if block_given?
params.each do |sym|
define_method(sym) do
yield
self.instance_variable_get("##{sym}")
end
end
else
params.each do |sym|
attr sym
end
end
end
end
class Test
attr_reader :normal
attr_reader(:jp,:nope) { changethings if #nope.nil? }
def initialize
#normal = "Normal"
#jp = "JP"
#done = false
end
def changethings
p "doing"
#jp = "Haha!"
#nope = "poop"
end
end
j = Test.new
p j.normal
p j.jp
But changethings isn't being recognised as a method — anyone got any ideas?
You need to evaluate the block in the context of the instance. yield by default will evaluate it in its native context.
class Class
def attr_reader(*params, &blk)
if block_given?
params.each do |sym|
define_method(sym) do
self.instance_eval(&blk)
self.instance_variable_get("##{sym}")
end
end
else
params.each do |sym|
attr sym
end
end
end
end
Here's another alternative approach you can look at. It's not as elegant as what you're trying to do using define_method but it's maybe worth looking at.
Add a new method lazy_attr_reader to Class
class Class
def lazy_attr_reader(*vars)
options = vars.last.is_a?(::Hash) ? vars.pop : {}
# get the name of the method that will populate the attribute from options
# default to 'get_things'
init_method = options[:via] || 'get_things'
vars.each do |var|
class_eval("def #{var}; #{init_method} if !defined? ##{var}; ##{var}; end")
end
end
end
Then use it like this:
class Test
lazy_attr_reader :name, :via => "name_loader"
def name_loader
#name = "Bob"
end
end
In action:
irb(main):145:0> t = Test.new
=> #<Test:0x2d6291c>
irb(main):146:0> t.name
=> "Bob"
IMHO changing the context of the block is pretty counter-intuitive, from a perspective of someone who would use such attr_reader on steroids.
Perhaps you should consider plain ol' "specify method name using optional arguments" approach:
def lazy_attr_reader(*args, params)
args.each do |e|
define_method(e) do
send(params[:init]) if params[:init] && !instance_variable_get("##{e}")
instance_variable_get("##{e}")
end
end
end
class Foo
lazy_attr_reader :foo, :bar, :init => :load
def load
#foo = 'foo'
#bar = 'bar'
end
end
f = Foo.new
puts f.bar
#=> bar

Ruby methods similar to attr_reader

I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class

Resources