Dynamic registering classes in factory - ruby-on-rails

I'm currently trying to achieve something similar to what is proposed in the chosen answer of this question: Ruby design pattern: How to make an extensible factory class?
class LogFileReader
##subclasses = { }
def self.create type
c = ##subclasses[type]
if c
c.new
else
raise "Bad log file type: #{type}"
end
end
def self.register_reader name
##subclasses[name] = self
end
end
class GitLogFileReader < LogFileReader
def display
puts "I'm a git log file reader!"
end
register_reader :git
end
class BzrLogFileReader < LogFileReader
def display
puts "A bzr log file reader..."
end
register_reader :bzr
end
LogFileReader.create(:git).display
LogFileReader.create(:bzr).display
class SvnLogFileReader < LogFileReader
def display
puts "Subersion reader, at your service."
end
register_reader :svn
end
LogFileReader.create(:svn).display
The unit tests work flawlessly, but when I start the server no class is being registered. May I be missing something about how the static method call is working? When is the register_reader call made by each subclass?

To answer the OP's question about when the classes call register_reader, it happens when the file is loaded. Add this code to an initializer to load the files yourself.
Dir[Rails.root.join('path', 'to', 'log_file_readers', '**', '*.rb').to_s].each { |log_file_reader| require log_file_reader }

Related

Pass in ruby variable to a class factory ruby script

My intention is to create custom error classes in various places for my Rails application since most of the error classes have the same methods. I have decided to create a YAML file to contain all the information from various error classes, and use a class factory script to generate all the classes in runtime. Here is what I have:
chat_policy.rb
class ChatPolicy; ... end
class ChatPolicy::Error < StandardError
ERROR_CLASSES = GLOBAL_ERROR_CLASSES['chat_policy']
ERROR_CLASSES.each do |cls|
const_set(cls['class_name'], Class.new(ChatPolicy::Error) {
attr_reader :object
def initialize(object)
#object = object
end
define_method(:message) do
cls['message']
end
define_method(:code) do
cls['code']
end
})
end
the GLOBAL_ERROR_CLASSES is loaded from YAML.load and turned to an object.
error_classes.yml
chat_policy:
- class_name: UserBlacklisted
message: You are not allowed to do this
code: ECP01
- class_name: UserSuspended
message: You are not allowed to do this
code: ECP02
- class_name: UserNotEligibleToRent
message: You are not allowed to do this
code: ECP03
- class_name: MembershipTierNotAllowed
message: You are not allowed to do this
code: ECP04
* __ Question is __ *
Now I have other files like register_policy, checkout_policy, discount_policy ..etc. It would be very duplicated if I have to do the class generation in every policy file. I wonder if I can shorten the code to something like this:
chat_policy_intended.rb
class ChatPolicy::Error < StandardError
ERROR_CLASSES = GLOBAL_ERROR_CLASSES['chat_policy']
error_class_factory(ChatPolicy::Error, ERROR_CLASSES)
end
discount_policy_intended.rb
class DiscountPolicy::Error < StandardError
ERROR_CLASSES = GLOBAL_ERROR_CLASSES['discount_policy']
error_class_factory(DiscountPolicy::Error, ERROR_CLASSES)
end
error_clas_factory.rb
ERROR_CLASSES.each do |cls|
const_set(cls['class_name'], Class.new(/*class_variable*/) {
attr_reader :object
def initialize(object)
#object = object
end
define_method(:message) do
cls['message']
end
define_method(:code) do
cls['code']
end
})
end
What I tried
I tried to create a .rb file basically copying the class factory script. And use eval method to eval it in runtime, but it seems I can pass in variables into the script
eval File.read(File.join(Rails.root, 'lib', 'evals', 'error_class_generator.rb'))
What should I do?
I appreciate the effort of avoiding to repeat yourself at all costs, but I find your code quite complex for the problem you're trying to solve, namely send errors to your app users.
How about sticking to the a simpler < MyAppError inheritance hierarchy to avoid the duplicated code?
class MyAppError < StandardError
attr_reader :object
def message(message)
# does stuff
end
def code(code)
# also does stuff
end
end
class ChatPolicyError < MyAppError
def message(message)
'[CHAT POLICY]' + super
end
end
class UserBlacklisted < ChatPolicyError
def message(message)
# Does stuff too
super
end
end
[...] # You get the idea

Inherit a class from a gem and add local methods

I use a gem to manage certain attributes of a gmail api integration, and I'm pretty happy with the way it works.
I want to add some local methods to act on the Gmail::Message class that is used in that gem.
i.e. I want to do something like this.
models/GmailMessage.rb
class GmailMessage < Gmail::Message
def initialize(gmail)
#create a Gmail::Message instance as a GmailMessage instance
self = gmail
end
def something_clever
#do something clever utilising the Gmail::Message methods
end
end
I don't want to persist it. But obviously I can't define self in that way.
To clarify, I want to take an instance of Gmail::Message and create a GmailMessage instance which is a straight copy of that other message.
I can then run methods like #gmail.subject and #gmail.html, but also run #gmail.something_clever... and save local attributes if necessary.
Am I completely crazy?
You can use concept of mixin, wherein you include a Module in another class to enhance it with additional functions.
Here is how to do it. To create a complete working example, I have created modules that resemble what you may have in your code base.
# Assumed to be present in 3rd party gem, dummy implementation used for demonstration
module Gmail
class Message
def initialize
#some_var = "there"
end
def subject
"Hi"
end
end
end
# Your code
module GmailMessage
# You can code this method assuming as if it is an instance method
# of Gmail::Message. Once we include this module in that class, it
# will be able to call instance methods and access instance variables.
def something_clever
puts "Subject is #{subject} and #some_var = #{#some_var}"
end
end
# Enhance 3rd party class with your code by including your module
Gmail::Message.include(GmailMessage)
# Below gmail object will actually be obtained by reading the user inbox
# Lets create it explicitly for demonstration purposes.
gmail = Gmail::Message.new
# Method can access methods and instance variables of gmail object
p gmail.something_clever
#=> Subject is Hi and #some_var = there
# You can call the methods of original class as well on same object
p gmail.subject
#=> "Hi"
Following should work:
class GmailMessage < Gmail::Message
def initialize(extra)
super
# some additional stuff
#extra = extra
end
def something_clever
#do something clever utilising the Gmail::Message methods
end
end
GmailMessage.new # => will call first the initializer of Gmail::Message class..
Building upon what the other posters have said, you can use built-in class SimpleDelegator in ruby to wrap an existing message:
require 'delegate'
class MyMessage < SimpleDelegator
def my_clever_method
some_method_on_the_original_message + "woohoo"
end
end
class OriginalMessage
def some_method_on_the_original_message
"hey"
end
def another_original_method
"zoink"
end
end
original = OriginalMessage.new
wrapper = MyMessage.new(original)
puts wrapper.my_clever_method
# => "heywoohoo"
puts wrapper.another_original_method
# => "zoink"
As you can see, the wrapper automatically forwards method calls to the wrapped object.
I'm not sure why you can't just have a simple wrapper class...
class GmailMessage
def initialize(message)
#message = message
end
def something_clever
# do something clever here
end
def method_missing(m, *args, &block)
if #message.class.instance_methods.include?(m)
#message.send(m, *args, &block)
else
super
end
end
end
Then you can do...
#my_message = GmailMessage.new(#original_message)
#my_message will correctly respond to all the methods that were supported with #original_message and you can add your own methods to the class.
EDIT - changed thanks to #jeeper's observations in the comments
It's not the prettiest, but it works...
class GmailMessage < Gmail::Message
def initialize(message)
message.instance_variables.each do |variable|
self.instance_variable_set(
variable,
message.instance_variable_get(variable)
)
end
end
def something_clever
# do something clever here
end
end
Thanks for all your help guys.

Reusing code ruby on rails

I've got a module in my project in lib/. it's content is like this :
module Search
module Score
def get_score
return 'something'
end
end
end
This Search has many different modules I need to use Score. I realize I need to add require in my model (I'm trying to use this from model). So here is my code (model) :
require 'search'
class User < ActiveRecord::Base
def get_user_score
#tried this :
p Search::Score.get_score #error
#this as well
score_instance = Score.new #error
score = Search::Score.get_score # error undefined method `get_score'
end
end
So how do I reuse the code I have in other class (module)?
To get it working you can either mix the module into your class:
require 'search'
class User < ActiveRecord::Base
include Search::Score
def get_user_score
p get_score # => "something"
end
end
Or you can define the method inside your module similar to class methods:
module Search
module Score
def self.get_score
return 'something'
end
end
end
If you do that, you can call get_score like expected:
require 'search'
class User < ActiveRecord::Base
def get_user_score
p Search::Score.get_score # => "something"
end
end
See this tutorial for a more in depth explanation about modules in Ruby.
First, see "Best Practices for reusing code between controllers in Ruby on Rails".
About reuse code as a module, take a look at "Rethinking code reuse with Modularity for Ruby".
"Modules are crippled classes"
Modules are like crippled classes in Ruby. If you look into the inheritance chain you see that a Class actually inherits from Module.
Module cannot be instanciated. So the call to .new is not working.
What you CAN do however is to specify your method as a 'class' method (I know I said it is not a class...)
So you would add a self in front like this:
module Search
module Score
def self.get_score
return 'something'
end
end
end
Then you can call this method as a class method like you tried in your code example
Search::Score is a module and not a class, so Score.new will not work.
You can try to change the signature of the get_score function to self.get_score.
In addition to def self.get_score in the above answers, there is also extend self, like so:
module Search
module Score
extend self
def get_score
return 'something'
end
end
end
and module_function:
module Search
module Score
module_function
def get_score
return 'something'
end
end
end
The latter is actually the preferred method in RuboCop (source), though in practice I personally have not seen it so often.

Simulating abstract classes in Ruby (Rails)

I want to simulate an abstract class in Ruby on Rails. I.e. I want to raise an exception if someone tries to call Abstract.new, but he should be able to call Child.new (while Child < Abstract).
How to do this? Overwriting both new and initialize does not work.
In another comment, the OP mentions that the purpose of the abstract class is to share behavior (methods) needed by its children. In Ruby, that's often best done with a module used to "mix in" methods where needed. For example, instead of:
class Abstract
def foo
puts "foo!"
end
end
class Concrete
end
Concrete.new.foo # => "foo!"
this:
module Foo
def foo
puts "foo!"
end
end
class Concrete
include Foo
end
Concrete.new.foo # => "foo!"
But here's how the original request might be satisfied:
#!/usr/bin/ruby1.8
class Abstract
def initialize(*args)
raise if self.class == Abstract
super
end
end
class Concrete < Abstract
end
Concrete.new # OK
Abstract.new # Raises an exception
Why would you want to do this? The point of abstract/interfaced classes are to hack Strongly typed languages into a dynamic paradigm. If you need your class to fit in the signature, name your methods according to the original class or make a facade and plug it in, no need to trick a compiler into allowing it, it just works.
def my_printer obj
p obj.name
end
So I defined the interface as any object with a name property
class person
attr_accessor :name
def initialize
#name = "Person"
end
end
class Employee
attr_accessor :name
def initialize
#name = "Employee"
#wage = 23
end
end
so nothing stops us from calling our printer method with either of these
my_printer Person.new
my_printer Employee.new
both print there names without a hitch :D
You almost always need to do this to enforce an API, when some third party is going to implement some stub, and you're sure they're going to mess it up. You can use specific prefix-templates in your parent class and a module that introspects on creation to achieve this:
module Abstract
def check
local = self.methods - Object.methods
templates = []
methods = []
local.each do |l|
if l =~ /abstract_(.*)/ # <--- Notice we look for abstract_* methods to bind to.
templates.push $1
end
methods.push l.to_s
end
if !((templates & methods) == templates)
raise "Class #{self.class.name} does not implement the required interface #{templates}"
end
end
end
class AbstractParent
include Abstract
def initialize
check
end
def abstract_call # <--- One abstract method here
end
def normal_call
end
end
class Child < AbstractParent # <-- Bad child, no implementation
end
class GoodChild < AbstractParent
def call # <-- Good child, has an implementation
end
end
Test:
begin
AbstractParent.new
puts "Created AbstractParent"
rescue Exception => e
puts "Unable to create AbstractParent"
puts e.message
end
puts
begin
Child.new
puts "Created Child"
rescue Exception => e
puts "Unable to create Child"
puts e.message
end
puts
begin
GoodChild.new
puts "Created GoodChild"
rescue Exception => e
puts "Unable to create GoodChild"
puts e.message
end
Result:
[~] ruby junk.rb
Unable to create AbstractParent
Class AbstractParent does not implement the required interface ["call"]
Unable to create Child
Class Child does not implement the required interface ["call"]
Created GoodChild
If you want this for doing STI, you could follow the suggestions in this thread:
class Periodical < ActiveRecord::Base
private_class_method :new, :allocate
validates_presence_of :type
end
class Book < Periodical
public_class_method :new, :allocate
end
class Magazine < Periodical
public_class_method :new, :allocate
end
Caveat: I'm not sure if this is a working solution. This hides new and allocate in the base class and re-enables them in child classes -- but that alone does not seem to prevent objects being created with create!. Adding the validation on type prevents the base class from being created. I guess you could also hide create!, but I'm not sure if that covers all the ways Rails can instantiate a model object.

How to log something in Rails in an independent log file?

In rails I want to log some information in a different log file and not the standard development.log or production.log. I want to do this logging from a model class.
You can create a Logger object yourself from inside any model. Just pass the file name to the constructor and use the object like the usual Rails logger:
class User < ActiveRecord::Base
def my_logger
##my_logger ||= Logger.new("#{Rails.root}/log/my.log")
end
def before_save
my_logger.info("Creating user with name #{self.name}")
end
end
Here I used a class attribute to memoize the logger. This way it won't be created for every single User object that gets created, but you aren't required to do that. Remember also that you can inject the my_logger method directly into the ActiveRecord::Base class (or into some superclass of your own if you don't like to monkey patch too much) to share the code between your app's models.
Update
I made a gem based on the solution below, called multi_logger. Just do this in the initializer:
MultiLogger.add_logger('post')
and call
Rails.logger.post.error('hi')
# or call logger.post.error('hi') if it is accessible.
and you are done.
If you want to code it yourself, see below:
A more complete solution would be to place the following in your lib/ or config/initializers/ directory.
The benefit is that you can setup formatter to prefix timestamps or severity to the logs automatically. This is accessible from anywhere in Rails, and looks neater by using the singleton pattern.
# Custom Post logger
require 'singleton'
class PostLogger < Logger
include Singleton
def initialize
super(Rails.root.join('log/post_error.log'))
self.formatter = formatter()
self
end
# Optional, but good for prefixing timestamps automatically
def formatter
Proc.new{|severity, time, progname, msg|
formatted_severity = sprintf("%-5s",severity.to_s)
formatted_time = time.strftime("%Y-%m-%d %H:%M:%S")
"[#{formatted_severity} #{formatted_time} #{$$}] #{msg.to_s.strip}\n"
}
end
class << self
delegate :error, :debug, :fatal, :info, :warn, :add, :log, :to => :instance
end
end
PostLogger.error('hi')
# [ERROR 2012-09-12 10:40:15] hi
A decent option that works for me is to just add a fairly plain class to your app/models folder such as app/models/my_log.rb
class MyLog
def self.debug(message=nil)
#my_log ||= Logger.new("#{Rails.root}/log/my.log")
#my_log.debug(message) unless message.nil?
end
end
then in your controller, or really almost anywhere that you could reference a model's class from within your rails app, i.e. anywhere you could do Post.create(:title => "Hello world", :contents => "Lorum ipsum"); or something similar you can log to your custom file like this
MyLog.debug "Hello world"
Define a logger class in (say) app/models/special_log.rb:
class SpecialLog
LogFile = Rails.root.join('log', 'special.log')
class << self
cattr_accessor :logger
delegate :debug, :info, :warn, :error, :fatal, :to => :logger
end
end
initialize the logger in (say) config/initializers/special_log.rb:
SpecialLog.logger = Logger.new(SpecialLog::LogFile)
SpecialLog.logger.level = 'debug' # could be debug, info, warn, error or fatal
Anywhere in your app, you can log with:
SpecialLog.debug("something went wrong")
# or
SpecialLog.info("life is good")
Here is my custom logger:
class DebugLog
def self.debug(message=nil)
return unless Rails.env.development? and message.present?
#logger ||= Logger.new(File.join(Rails.root, 'log', 'debug.log'))
#logger.debug(message)
end
end
class Article < ActiveRecord::Base
LOGFILE = File.join(RAILS_ROOT, '/log/', "article_#{RAILS_ENV}.log")
def validate
log "was validated!"
end
def log(*args)
args.size == 1 ? (message = args; severity = :info) : (severity, message = args)
Article.logger severity, "Article##{self.id}: #{message}"
end
def self.logger(severity = nil, message = nil)
#article_logger ||= Article.open_log
if !severity.nil? && !message.nil? && #article_logger.respond_to?(severity)
#article_logger.send severity, "[#{Time.now.to_s(:db)}] [#{severity.to_s.capitalize}] #{message}\n"
end
message or #article_logger
end
def self.open_log
ActiveSupport::BufferedLogger.new(LOGFILE)
end
end
I would suggest using Log4r gem for custom logging. Quoting description from its page:
Log4r is a comprehensive and flexible logging library written in Ruby for use
in Ruby programs. It features a hierarchical logging system of any number of
levels, custom level names, logger inheritance, multiple output destinations
per log event, execution tracing, custom formatting, thread safteyness, XML
and YAML configuration, and more.
class Post < ActiveRecord::Base
def initialize(attributes)
super(attributes)
#logger = Logger.new("#{Rails.root}/log/post.log")
end
def logger
#logger
end
def some_method
logger.info('Test 1')
end
end
ps = Post.new
ps.some_method
ps.logger.info('Test 2')
Post.new.logger.info('Test 3')
The Logging framework, with its deceptively simple name, has the sophistication you crave!
Follow the very short instructions of logging-rails to get started filtering out noise, getting alerts, and choosing output in a fine-grained and high-level way.
Pat yourself on the back when you are done. Log-rolling, daily. Worth it for that alone.

Resources