How to metaprogram routes dinamicaly? - ruby

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

Related

call before methods in model on ruby

This my implementation to developing way to run code before all method in your model
The call "before_hook :months_used" method need to be on bottom of class to the ExecutionHooks can get the instance_method loaded in the module. I would like to load the instance methods on top
class BalanceChart < BalanceFind
include ExecutionHooks
attr_reader :options
def initialize(options = {})
#options = options
#begin_at = #options[:begin_at]
end
def months_used
range.map{|date| I18n.l date, format: :month_year}.uniq!
end
before_hook :months_used
end
module ExecutionHooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def before
#hooks.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
return if #begin_at.blank? ## the code you can execute before methods
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
before
end
def hooks
#hooks ||= []
end
end
end
You can do this with prepend. prepend is like include in that it adds a module to the ancestors of the class, however instead of adding it after the class it adds it before.
This means that if a method exists both in the prepended module and the class then the module implementation is called first (and it can optionally call super if it wants to call the base class).
This allows you to write a hooks module like so:
module Hooks
def before(*method_names)
to_prepend = Module.new do
method_names.each do |name|
define_method(name) do |*args, &block|
puts "before #{name}"
super(*args,&block)
end
end
end
prepend to_prepend
end
end
class Example
extend Hooks
before :foo, :bar
def foo
puts "in foo"
end
def bar
puts "in bar"
end
end
In real use you would probably want to stash that module somewhere so that each call to before doesn't create a new module but that is just an inplementation detail
#rathrio This is my implementation using method_added that you talked. Thanks
module ExecutionHooks
def validation
p "works1"
end
def self.included(base)
base.send :extend, ClassMethods
end
end
module ClassMethods
attr_writer :hooked
def hooked
#hooked ||= []
end
def method_added(method)
return if #hooks.nil?
return unless #hooks.include?(method)
m = self.instance_method(method)
unless hooked.include?(method)
hooked << method
define_method(method) do |*args, &block|
validation
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
end
def hooks
#hooks ||= []
end
end
end
class BalanceChart < BalanceFind
include ExecutionHooks
before_hook :months_data, :months_used, :debits_amount, :test
def test
"test"
end
end
Instead of redefining the method when calling before_hook, you could override the method_added hook to prepend your before hooks to a method right after it was defined. This way your before_hook calls can be (actually, must be) placed at the top of the class definition.

"Namespaced" methods in 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

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

Rack Routing - NoMethodError

I'm trying to set up a basic routing system in Rack, however.. I can't understand why the first route ('/') works, and the second ('/help') doesn't. What gets returned in the case of '/help' is a NoMethodError. Why is that, and how can I fix it? Thank you.
require 'rack'
class MyApp
def self.call(env)
new.call(env)
end
def self.get(path, &block)
##routes ||= {}
##routes[path] = block
end
def call(env)
dup.call!(env)
end
def call!(env)
#req = Rack::Request.new(env)
#res = Rack::Response.new
#res.write route(#req.path)
#res.finish
end
def route(path)
if ##routes[path]
##routes[path].call
else
'Not Found'
end
end
def to_do(arg)
arg
end
end
MyApp.get '/' do
'index'
end
MyApp.get '/help' do
to_do 'help'
end
run MyApp

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

Resources