Increasing the DRYness with a Simple Wrapping Class in Ruby - ruby

There has to be a better way to do this:
require 'log4r'
class PaddedLogger
attr_accessor :logger, :padding
def initialize(args)
#logger = Log4r::Logger.new args[:name]
#padding = args[:padding]
end
def debug(system, message)
system = "[#{system}]"
#logger.debug "#{system.ljust(#padding)}#{message}"
end
def info(system, message)
system = "[#{system}]"
#logger.info "#{system.ljust(#padding)}#{message}"
end
def warn(system, message)
system = "[#{system}]"
#logger.warn "#{system.ljust(#padding)}#{message}"
end
def error(system, message)
system = "[#{system}]"
#logger.error "#{system.ljust(#padding + 3)}#{message}"
end
def fatal(system, message)
system = "[#{system}]"
#logger.fatal "#{system.ljust(#padding + 3)}#{message}"
end
end
It's just a wrapping class to help me get my log4r logs appearing in a specific format, and it works exactly as I need it too, but surely Ruby's meta-programming magic can simplify this so I don't have to constantly repeat myself.

Something like this maybe?
require 'log4r'
class PaddedLogger
attr_accessor :logger, :padding
def initialize(args)
#logger = Log4r::Logger.new args[:name]
#padding = args[:padding]
end
[:debug, :info, :warn, :error, :fatal].each do |reason|
define_method reason do |system, message|
system = "[#{system}]"
#logger.send reason, "#{system.ljust(#padding)}#{message}"
end
end
end
And while you're at it, why not collapse the two lines in the define_method block to one?
#logger.send reason, "[#{system}]".ljust(#padding) + message

Here is and alternative solution using method_missing:
class TestClass
##methods = [:upcase, :other_methods_to_catch]
def initialize
#message = 'hello'
end
def method_missing name, *args
super unless ##methods.include? name
#message.send(name, *args)
end
end
x = TestClass.new
p x.upcase #=> HELLO

Related

How to allow access to only some specific ruby classes?

I'm trying to use jruby as a script language (for writing quests for a game), but I need to prevent usage of potentially dangerous classes like Dir, Process or RubyVM.
I already found a cumbersome way to blacklist single methods and classes:
class Dir
def Dir.[]
end
def Dir.chdir
end
def Dir.chroot
end
def Dir.delete
end
def Dir.entries
end
def Dir.exist?
end
def Dir.exists?
end
def Dir.foreach
end
def Dir.getwd
end
def Dir.glob
end
def Dir.home
end
def Dir.mkdir
end
def Dir.initialitze
end
def Dir.open
end
def Dir.pwd
end
def Dir.rmdir
end
def Dir.unlink
end
def close
end
def each
end
def fileno
end
def inspect
end
def path
end
def pos
end
def read
end
def rewind
end
def seek
end
def tell
end
def to_path
end
end
But I really hope that there is a much easier way to perform this task or even better whitelist the classes that should be able to use.
You can just make specific methods private:
class Dir
private :close, :each ....
private_class_method :glob, :home ...
end
Or you can undefine whole class (bad idea as for me):
Object.send(:remove_const, :Dir)
Or remove methods (also bad idea to remove methods from Ruby core class):
class Dir
remove_method :close, :each ....
end

How can ruby logger set the progname automatically based on the method

I have set up my logger using a module with the following (simplified) version:
module Logging
class << self
def logger
end
def logger=(logger)
#logger = logger
end
end
end
I would like to do something like the following (it didn't work):
Get the name of the currently executing method
def test_method
return __callee__
end
This way without the above, add logger.progname = self.test_method
So it should look like this:
module Logging
class << self
def logger
return #logger if #logger
#logger.progname = self.test_method
end
def logger=(logger)
#logger = logger
end
end
end
The end goal should be such that, with method "MyMethod":
def my_method
logger.debug "hi"
end
I get => "my_method: "hi"

Improving module structure

I created small API library, everything worked fine, until I realized, that I need multiple configurations.
It looks like this:
module Store
class Api
class << self
attr_accessor :configuration
def configure
self.configuration ||= Configuration.new
yield configuration
end
def get options = {}
url = "#{configuration.domain}/#{options[:resource]}"
# ResClient url ...
end
end
end
class Configuration
attr_accessor :domain
def initialize options = {}
#domain = options[:domain]
end
end
class Product
def self.get
sleep 5
Api.get resource: 'products'
end
end
end
When I run it simultaneously, it override module configuration.
Thread.new do
10.times do
Store::Api.configure do |c|
c.domain = "apple2.com"
end
p Store::Product.get
end
end
10.times do
Store::Api.configure do |c|
c.domain = "apple.com"
end
p Store::Product.get
end
I can't figure out, how make this module better. Thanks for your advise
Well, if you don't want multiple threads to compete for one resource, you shouldn't have made it a singleton. Try moving object configuration from class to its instances, then instantiate and configure them separately.
There is more to refactor here, but this solves your problem:
module Store
class API
attr_reader :domain
def initialize(options = {})
#domain = options[:domain]
end
def products
sleep 5
get resource: 'products'
end
private
def get(options = {})
url = "#{configuration.domain}/#{options[:resource]}"
# ResClient url ...
end
end
end
Thread.new do
10.times do
api = Store::API.new(domain: 'apple2.com')
p api.products
end
end
10.times do
api = Store::API.new(domain: 'apple.com')
p api.products
end

Passing blocks into nested method within class_eval in Ruby?

I want to be able to define a block, and later evaluate that block from within a dynamically generated module/class. It seems like I could accomplish this somehow using eval and block.binding, but I haven't figured it out.
I have this as the base:
def define_module(name, &block)
name = name.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
parts = name.split("::")
parts.each_with_index do |part, index|
sub_name = parts[0..index].join("::")
eval("module #{sub_name}; end")
end
clazz = eval(name)
clazz.class_eval(&block) if block_given?
clazz
end
def add_module(name, &block)
module_block = block
define_module(name).class_eval <<-EOF
def self.included(base)
base.class_eval do
# something like this, I'm stuck
instance_eval(&#{module_block})
end
end
EOF
end
And I want to use it like this:
add_module("My::Library") do
def a_method
"added 'a_method'"
end
end
class ::User
include My::Library
end
user = ::User.new
assert_equal "added 'a_method'", user.a_method
Is there any way to do something like that?
This works:
def add_module(name, &block)
define_module(name).class_eval do
class << self; self; end.send(:define_method, :included) { |base|
base.class_eval(&block)
}
end
end
add_module("My::Library") do
def a_method
"added 'a_method'"
end
end
class ::User
include My::Library
end
user = ::User.new
user.a_method #=> "added a_method"
EDIT:
Why don't you just do this instead? Much simpler, and it's actually the job of a module:
def add_module(name, &block)
define_module(name).class_eval(&block)
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

Resources