Iterating through the Rails lib folder to find children - ruby-on-rails

The following code is meant to determine which subclass of itself to execute. It cycles through everything in the ObjectSpace to find subclasses and then execute the correct one from there. In rails, this does not work, because classes in the library folder are not in the ObjectSpace. What is a way to search through a specific folder for subclasses?
def execute
ObjectSpace.each_object(Class).select do |klass|
if (klass < self.class)
klass.designations.each do |designation|
if (designation.downcase.capitalize == #action.downcase.capitalize)
command = klass.new(#sumo_params)
command.execute
end
end
end
end
end
OR -- Is there a superior solution to this problem that you would recommend?

I'd say use a module for this. So, this would make you code something like the following:
lib/base_methods.rb
module BaseMethods
def self.included(base)
##classes_including ||= []
##classes_including << base
end
def do_whatever
##classes_including.each do |class|
#....
end
end
end
lib/class_1.rb
class Class1
include BaseMethods
end

I took the easy way out for now (iterating through an array of string values):
def execute
#descendants.each do |descendant|
klass = descendant.downcase.capitalize.constantize
if (klass < self.class)
klass.designations.each do |designation|
if (designation.downcase.capitalize == #action.downcase.capitalize)
command = klass.new(#sumo_params)
command.execute
end
end
end
end
end

Related

Ruby extend & include tracing code

I'm confused about using "include" vs "extend, after searching for hours all I got is that module methods used with instance of the class including the module, and module methods used with the class itself when the class extending the module of those methods.
but this didn't help me to figure out, why this code give error when commenting the extend module line in "#extend Inventoryable"
while work when uncomment it, here's the code
module Inventoryable
def create(attributes)
object = new(attributes)
instances.push(object)
return object
end
def instances
#instances ||= []
end
def stock_count
#stock_count ||= 0
end
def stock_count=(number)
#stock_count = number
end
def in_stock?
stock_count > 0
end
end
class Shirt
#extend Inventoryable
include Inventoryable
attr_accessor :attributes
def initialize(attributes)
#attributes = attributes
end
end
shirt1 = Shirt.create(name: "MTF", size: "L")
shirt2 = Shirt.create(name: "MTF", size: "M")
puts Shirt.instances.inspect
the output is
store2.rb:52:in `<main>': undefined method `create' for Shirt:Class (NoMethodError)
while when uncomment the "extend Inventoryable" to make the code work:
module Inventoryable
def create(attributes)
object = new(attributes)
instances.push(object)
return object
end
def instances
#instances ||= []
end
def stock_count
#stock_count ||= 0
end
def stock_count=(number)
#stock_count = number
end
def in_stock?
stock_count > 0
end
end
class Shirt
extend Inventoryable
include Inventoryable
attr_accessor :attributes
def initialize(attributes)
#attributes = attributes
end
end
shirt1 = Shirt.create(name: "MTF", size: "L")
shirt2 = Shirt.create(name: "MTF", size: "M")
puts Shirt.instances.inspect
makes the code work and output the following
[#<Shirt:0x0055792cb93890 #attributes={:name=>"MTF", :size=>"L"}>, #<Shirt:0x0055792cb937a0 #attributes={:name=>"MTF", :size=>"M"}>]
it's kinda confusing, but all I need to know, is why I need to extend the module in order to avoid the error ?, and how to edit this code to make it work without the extend method ? , what's left in the code that still depends on the extend ?
When you extend a module, the methods in that module become "class methods"**. So, when you extend Inventoryable, create becomes available as a method on the Shirt class.
When you include a module, the methods in that module become "instance methods"**. So, when you include Inventoryable, create is not available on the Shirt class (but is available on an instance of Shirt).
To make create available on the Shirt class when using include, you can use the included hook. That might look something like:
module Inventoryable
module ClassMethods
def create
puts "create!"
end
end
module InstanceMethods
end
def self.included(receiver)
receiver.extend ClassMethods
receiver.include InstanceMethods
end
end
Then if you do:
class Shirt
include Invetoryable
end
You can do:
> Shirt.create
create!
=> nil
** The ruby purists in the crowd will correctly point out that, in ruby, everything is an instance method and that there are no class methods. That is formally 100% correct, but we'll use the colloquial meaning of class and instance methods here.
When you extend a module in a class, you get the module's methods exposed as class methods but if you include the module then you get the module's method as instance methods, in your example for you to be able to call create method of Inventoryable class you need to invoke it using an instance of Shirt class (if you include the module)
shirt1 = Shirt.new(attributes).create(attributes)
Without more info I can't tell what you are trying to do but you need to redesign the initialize and create methods to decide where or what to do in those methods.
I'll try to explain it using a simple example
module A
def test
puts "ok"
end
end
class B
include A
end
class C
extend A
end
puts C.test # here you invoke the method against the class itself
puts B.new.test #here you create an instance to do it
Hope it helps.
At the end of the day, it's really simple:
C.include(M) makes the current superclass of C the superclass of M and M the superclass of C. In other words, it inserts M into C's ancestry chain.
obj.extend(M) is (roughly) the same as obj.singleton_class.include(M).

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.

About strategy pattern and Rails

I want to incorporate the strategy pattern in my application.
I have stored under lib the following classes.
class Network
def search
raise "NO"
end
def w_read
raise "NO"
end
#...
end
AND
class FacebookClass < Network
def search
# FacebookClass specific...
end
def w_read
raise OneError.new("...")
end
end
AND
class TwitterClass < Network
def search
# TwitterClass specific...
end
def w_read
# TwitterClass specific...
end
def write
# TwitterClass specific...
end
end
Now I want to call the method search of TwitterClass from app/model/network_searcher.rb. How can I do that? Did I implemented the strategy pattern here successfully?
Going by the example in the Wikipedia, I think your app/model/network_searcher should be something like this
class NetworkSearcher
def initialize(search_class)
#search_class = search_class
end
def search_social
#search_class.search
end
def w_read_social
#search_class.w_read
end
def write_social
#search_class.write
end
end
Then in controller or where you want to invoke it, you can call like this:
search_class = TwitterClass.new # or FacebookClass.new
network_searcher = NetworkSearch.new(search_class)
network_searcher.search_social # or network_searcher.w_read_social or network_searcher.write_social
Also if you are keeping these classes in lib, for Rails 3, inorder to get these classes autoloaded, you need to add this line to config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
and also follow the naming convention for the filenames in Rails (for example TwitterClass should be named twitter_class.rb). Otherwise you will have to require these files wherever you are using these classes.
The strategy pattern is used to allow the algorithm to use to be selected at runtime. Without more details it's hard to say if this is appropriate to your problem. Assuming that it is then what you need is a way to set the search on your model and you can then use the selected algorithm elsewhere in your model. e.g.
class TheInformation
attr_writer :searcher
def other_method
..
# can use the selected searcher here
#searcher.search
..
end
end
Does that help?

Rails Metaprogramming: How to add instance methods at runtime?

I'm defining my own AR class in Rails that will include dynamically created instance methods for user fields 0-9. The user fields are not stored in the db directly, they'll be serialized together since they'll be used infrequently. Is the following the best way to do this? Alternatives?
Where should the start up code for adding the methods be called from?
class Info < ActiveRecord::Base
end
# called from an init file to add the instance methods
parts = []
(0..9).each do |i|
parts.push "def user_field_#{i}" # def user_field_0
parts.push "get_user_fields && #user_fields[#{i}]"
parts.push "end"
end
Info.class_eval parts.join
One nice way, especially if you might have more than 0..9 user fields, would be to use method_missing:
class Info
USER_FIELD_METHOD = /^user_field_(\n+)$/
def method_missing(method, *arg)
return super unless method =~ USER_FIELD_METHOD
i = Regexp.last_match[1].to_i
get_user_fields && #user_fields[i]
end
# Useful in 1.9.2, or with backports gem:
def respond_to_missing?(method, private)
super || method =~ USER_FIELD_METHOD
end
end
If you prefer to define methods:
10.times do |i|
Info.class_eval do
define_method :"user_field_#{i}" do
get_user_fields && #user_fields[i]
end
end
end
Using method_missing is very difficult to maintain and unnecessary. The other alternative using define_method is better but leads to poorly performing code. The following 1 liner is all you need:
class Info
end
Info.class_eval 10.times.inject("") {|s,i| s += <<END}
def user_field_#{i}
puts "in user_field_#{i}"
end
END
puts Info.new.user_field_4

How to alias ActiveRecord class methods dynamically in a rails plugin?

I'm having trouble removing some duplication I've introduced in a rails plugin.
The code below modifies the find and calculate methods of ActiveRecord in the same way, but I've been unable to remove the duplication.
The find and calculate methods below make use of the super keyword which is one hurdle as the super keyword can only be used to call a method sharing the same name as the calling method, so I can't move the super keyword to a method shared by find and calculate.
So next I tried aliasing the find and calculate class methods from the superclass ActiveRecord, however, I've not been able to get the syntax right for the aliasing. If someone could show me that, it would be a great help.
If you've got a better way entirely of doing this I'd love for you to post that too.
Below I've trimmed the code down a little to highlight the problem:
module Geocodable #:nodoc:
def self.included(mod)
mod.extend(ClassMethods)
end
module ClassMethods
def acts_as_geocodable(options = {})
extend Geocodable::SingletonMethods
end
end
module SingletonMethods
def find(*args)
some_method_1
super *args.push(options)
some_method_2
end
# TODO: Remove duplication of find above and calculate below.
def calculate(*args)
some_method_1
super *args.push(options)
some_method_2
end
end
end
Your best way to refactor this code is to leave find and calculate unchanged, and add apply the wrapping using a class-level function.
Here's rough sketch, without your module and mixin logic:
class A
def find x
puts 'finding'
end
def calculate x
puts 'calculating'
end
end
class B < A
def self.make_wrapper_method name
define_method name do |*args|
puts "entering"
result = super *args
puts "exiting"
result
end
end
make_wrapper_method :find
make_wrapper_method :calculate
end
Note that this will need to be modified if B has already overridden find or calculate.
To use this code, first make your version work correctly, then modify it to use define_method. (And if you need extremely high performance, you may need to use one of the *_eval functions to create the wrappers instead of define_method.)
This is the option I went for in the end, thanks to emk for guidance to get to this point!
module Geocodable
def self.included(mod)
mod.extend(ClassMethods)
end
module ClassMethods
def acts_as_geocodable(options = {})
geoify_query_methods
end
private
# This is where the duplication has been removed
def geoify_query_methods
class << self
[:calculate, :find].each do |method_name|
define_method method_name do |*args|
some_method_1
super *args.push(options)
some_method_2
end
end
end
end
end
end
To just alias the find method:
module SingletonMethods
def find(*args)
some_method_1
super *args.push(options)
some_method_2
end
alias :calculate :find
end

Resources