Convert a class to a subclass on instantiation - ruby

I'm writing a framework for querying the Mediawiki API. I have a Page class which represents articles on the wiki, and I've also got a Category class, which is-a Page with more specific methods (like being able to count the number of members in the category. I've also got a method Page#category? which determines if an instantiated Page object is actually representative of a Mediawiki category page, by querying the API to determine the namespace of the article.
class Page
def initialize(title)
# do initialization stuff
end
def category?
# query the API to get the namespace of the page and then...
namespace == CATEGORY_NAMESPACE
end
end
class Category < Page
# ...
end
What I would like to do is be able to detect if the user of my framework tries to instantiate a Mediawiki category using a Page object (ie. Page.new("Category:My Category")), and if so, instantiate a Category object, instead of a Page object, directly from the Page constructor.
It seems to me that this should be possible because it's reminiscent of single table inheritance in Rails, but I'm not sure how to go about getting it to work.

Ok, couple of things:
You can't convert an instance of a class A to an instance of A's subclass B. At least, not automatically. B can (and usually does) contain attributes not present in A, it can have completely different constructor etc. So, AFAIK, no OO language will allow you to "convert" classes that way.
Even in static-typed languages, when you instantiate B, and then assign it to a variable a of type A, it is still instance of B, it is not converted to its ancestor class whatsoever.
Ruby is a dynamic language with powerful reflection capabilities, so you can always decide which class to instantiate in the runtime - check this out:
puts "Which class to instantiate: "
class_name = gets.chomp
klass = Module.const_get class_name
instance = klass.new
So, no need for any conversion here - just instantiate the class you need in the first place.
Another thing: as I mentioned in the comment, method category? is simply wrong, as it violates OOP principles. In Ruby, you can - and should - use method is_a?, so your check will look like:
if instance.is_a? Category
puts 'Yes, yes, it is a category!'
else
puts "Nope, it's something else."
end
This is just a tip of the iceberg, there's lot more about instantiating different classes, and another question I have linked in the comment can be a great starting point, although some code examples there might confuse you. But it is definitely worth understanding them.
Edit: After re-reading your updated question, it seems to me that the right way for you would be to create a factory class and let it do the detecting and instantiating different page types. So, user wouldn't call Page.new directly, but rather call something like
MediaWikiClient.get_page "Category:My Category"
and get_page method would instantiate corresponding class.

Why not something like this? Being able to do that is a good enough reason to do it!
class Page
def self.new(title)
if self == Page and is_category?(title)
Category.new(title)
else
super
end
end
def self.is_category?(title)
# ... (query the API etc.)
end
def initialize(title)
# do initialization stuff
end
def category?
# query the API to get the namespace of the page and then...
namespace == CATEGORY_NAMESPACE
end
end
class Category < Page
# ...
end

You could define a method that instantiate the class and returns the instance.
This is know as Factory Pattern
class PageFactory
def create(title) # the pattern uses "create".. but "new" is more Ruby' style
namespace = title[/\A[^:]+(?=:)/]
# matches the prefix, up to and excluding the first colon.
if namespace == CATEGORY_NAMESPACE
Category.new(title)
else
Page.new(title)
end
end
end
class ClientClass
def do_something()
factory = PageFactory.new
my_page = factory.create('Category:Foo')
my_page.do_something()
end
end

Related

Reduce number of instance methods

I am modelling a report as a class and each column value as an accessor. Each value in the report needs to be accessed from the database. But this makes the class look quite fat and RubyMine warns me of too many methods in class.
class Report
attr_accessor :name, :col1, :col2, :col3 .... :col15
def col1
db.find({x: 1})['some_var']
end
def col2
db.find({y: 4})['some_other_var']
end
and so forth for each attribute...
end
Since each getter is essentially single line that makes call to database, is there a simpler way to declare these vars without being in a method?
I don't want to set these in the initialize method as these reports will be subclassed and child reports will not have all/some of these attributes.
You can use meta-programming to create attr_accessor like methods on the fly.
For Example:
class Report
def initialize(attributes)
attributes.each do |attribute|
define_singleton_method :"#{attribute}" do |hash_param, string_param|
db.find(hash_param)[string_param]
end
end
end
end
Then you can create new report object and pass attribute names as follow:
r = Report.new(["n","m"])
Now you can call n and m methods on the r object
r.m({val1: "val1"}, "val2")
r.n({val2: "val1"}, "val2")
#Rahul based on answer to my question, the only advice I can give here then is to use best OOP design principles. Subclass and modularize where possible as well as using ruby meta-programming. If you need the methods, they have to be written somewhere. But if you only need getters, consider attr_reader instead, unless you'll need setters too.
If you can get the column names you can use dynamic method definition with something like this assuming db is magically defined somewhere you have not made clear, we'll assume it's a connection to the database.
class Report
db.column_names.each do |col|
define_method(col.to_sym) { db.find(options={}) }
end
end
If you just want RubyMine to stop nagging you, I assume it's using rubocop and you can see this post for how to override rules.
https://www.jetbrains.com/help/ruby/2017.1/rubocop.html
or
https://github.com/rubocop-hq/rubocop

Clarification about constants in ruby

I'm trying to build a rest client and since a lot of is almost the same I thought I'd put the actions into a module and just extend the module to get that set of actions and keep the unique bits separate by defining them as a constant, the same constant but each route has it set differently so the end result is a different URL but the same action.
module Common
def list
"some_url/#{Route_Name}.json"
end
end
class Posts
extend Common
Route_Name = 'posts'
end
class Comments
extend Common
Route_Name = 'comments'
end
Comments.list
#=> "some_url/comments.json" what I expect to be outputted
But it just errors, in this example it would error uninitialized constant Common::Route_Name.
How do I get Route_Name to be what I expect it to be?
EDIT:
I kind of solved the issue by changing Route_Name to #route_name, but the problem is that Route_Name is constant, it will never change so it doesn't feel right using an instance variable, even if it does work.
Ruby does constant lookup first by namespace (roughly: where you've nested them) and then by ancestors (what you've inherited from). Because list is declared in Common, lookup starts with Common::Route_Name and can't go any further.
But, when list is called, it's called on Comments or Posts, giving you access to those via self. Instead of leaving Ruby to look for the constant itself, you can use self::Route_Name to make lookup start where you want it.
module Common
def list
"some_url/#{self::Route_Name}.json"
end
end
class Posts
extend Common
Route_Name = 'posts'
end
class Comments
extend Common
Route_Name = 'comments'
end
puts Comments.list #=> some_url/comments.json
puts Posts.list #=> some_url/posts.json
Use const_get instead. It's a method call (compared to lookup by static name). Meaning, that it will start the constant lookup from the bottom of the ancestor hierarchy.
module Common
def list
"some_url/#{const_get('Route_Name')}.json"
end
end
class Posts
extend Common
Route_Name = 'posts'
end
class Comments
extend Common
Route_Name = 'comments'
end
Comments.list # => "some_url/comments.json"
#mtamhankar has a good point about deriving routes from class names. Something like this, perhaps (uses tableize from ActiveSupport)
module Common
def list
"some_url/#{name.tableize}.json"
end
end
I've done something similar to this, where I've defined several controller actions (index, create, etc..) in a module, then just include MyModule in the controller in which I want to use them.
At this point, in my Module I can call self.controller_name to get where I actually am coming from.
So instead of trying to set the Route_Name, you could derive it with something like self.controller_name.tableize or self.class.tableize
#route_name as you mentioned is an instance variable and will work, but I think what you're actually looking for is ##route_name which would make it a class level variable (one per class, not per instance).
A ruby method looks up constants from that method's owner, not the receiver or the class of the receiver. When you access a constant, ruby interpreter looks up the constant along Module.nesting of the calling module or class.
X = 0
module A
X = 1
module B
p Module.nesting #=> [A::B, A]
X = 2
def self.x
X
end
end
end
A::B.x # Try commenting out any 'X = ?' to see the difference
When you call A::B.x, ruby tries to find the constant X in the namespace A::B (i.e. tries to find A::B::X), if found, it stops the lookup and returns that value immediately. If not found, then ruby tries to find X inside A, if still not fount, ruby tries to find X inside the top level namespace, which is Object.
Since classes and modules have a class/module method name which returns the name of the class/module, you can refactor your Common module like this:
module Common
def list
"some_url/#{name.downcase}.json"
end
end
And annihilate all Route_Name = 'xxx'

Allow a block to reference classes/modules not currently in scope, but will be in scope when invoked?

Is it possible to do something like this in Ruby (1.9.2-p290)?
class SomeClass
include SomeModuleThatProvidesLotOfConstants
def build(&block)
singleton_class.instance_eval(&block)
end
end
obj = SomeClass.new
obj.build do
some_class_method SomeConstant, :an => :option
...
end
Where some_class_method is a method that is available to SomeClass (not to instances of it) and SomeConstant is a class/module that is in scope inside of SomeClass, but would have to be references as SomeClass::SomeConstant from outside.
I can get this working if I always pass fully-qualified class names inside my block, but I'm trying to effectively "re-scope" the block when it is invoked. Is this possible? I'm pretty sure RSpec and other such tools that make heavy use of blocks achieve something like this :)
Note that while I'm calling class methods from inside the block, I only want the changes to affect this individual singleton class, rather than propogate to all instances.
EDIT | Ok, here's the non-pseudo version of what I'm trying to achieve. I'm trying to add some DataMapper properties at runtime, but only to singleton classes... I don't want them to suddenly appear across all instances of the model.
class Post
include DataMapper::Resource
property :id, Serial
property :title, String
property :created_at, DateTime
... etc ...
def virtualize(&block)
singleton_class.instance_eval(&block)
self
end
end
def suspend_post
#post = Post.get!(1).virtualize do
property :delete_comments, Boolean
end
end
I know there are other ways to do virtual attributes (I'm currently using a couple of different approaches, depending on the complexity), but I'm just experimenting with a few ideas to avoid cluttering my model definitions with transient methods that are only used for transporting form data in one specific part of the site and don't mean anything when you're reading the source code of the model by itself. One or two virtual attributes are ok, but as they start to mount up on commonly used models I start to explore things like this ;)
In the above, the resource would have all of the standard properties defined in the concrete class, plus any that are added in the #virtualize method. It's the reference to Boolean without the DataMapper::Property:: prefix that's throwing it off.
You've already got what you want with respect to methods. If you define some_class_method like this:
def Foo.some_class_method(name)
define_method name do
puts("this is the method #{name}")
end
end
and do
f = Foo.new
f.build { some_class_method "new_method" }
f.singleton_methods # => [:new_method]
You've defined behavior on just that one instance.
However I don't think you can get what you're looking for with respect to constants. One option would be to use methods instead of constants for those arguments. Another would be to have the client code mix in whatever module defines the constants.
Do keep in mind this is pretty dense metaprogramming, so the complexity may not be justified.
What's wrong with this:
class SomeClass
SOME_CONSTANT = 42
class << self
def some_class_method
'foo'
end
end
def build &block
self.class.instance_eval(&block)
end
end
SomeClass.new.build do
puts "#{some_class_method} #{SOME_CONSTANT}"
end
#=>foo 42

Best practices and implementation for macro and class-level accessor methods?

I'm designing/building a system of classes that all derive from a single base class.
The goal is to have easy-to-use inherited macro methods that look something like this:
class Something < A::Base
full_name 'Something that goes bump in the night.'
end
Any code should be able to ask the class for this information (or, likely, normalized/derived infomation) later on via class-level accessor method(s).
puts Something.full_name
# => "Some kind of calculated value that may or may not go bump in the night."
Given that A::Base includes/extends/somehow-otherwise-mixes-in both a module with the macro method that works something like this:
module MacroMethods
private
def full_name(full_name)
# non-trivial, one-time-only set-up code exists here in actual usage
end
end
and a module with the class-level accessor method that works something like this:
module AccessorMethods
public
def full_name
# a non-trivial, runtime-calculated value is returned here in actual usage
end
end
no matter how I mix them in, I'm continually running into naming conflicts (i.e. ‘wrong number of arguments (1 for 0) (ArgumentError)’) between the two.
Note: full_name is the simplest example of what is needed; other, more-complex macros/accessors ensure the non-flexible constraints of macro methods needing to be declared inside the class and needing to be set once-and-only-once.
My question is two-fold:
Is there a way to make this all work inside of the A::Base class?
Is this the right way to do this in Ruby? Is there a better way go about it, achieving the same result?
Options that have been considered:
Calling either the macro or accessor method(s) something else.
(e.g. in Something class: set_up_full_name 'Something that …')
Downside is that the naming is confusing and unconventional.
Making the accessor method(s) instance-level instead of class-level.
(e.g. puts a_something.full_name')
Downside is that the traits set up by the macros are inherent to the class, not to each instance (in some cases, only a reference to the class may be available, not an instance).
Creating a single method that handles both macro and accessor functionality.
(e.g. in A::Base class: def self.full_name(*args) …)
Downside is that the macro methods can no longer be private and the RDoc looks like sh*t.
Using abstact/virtual-ish methods instead.
(e.g. in Something class: def self.full_name; 'Something that …'; end)
Downside is that this is more code in sub-classes and is more of a Objective-C (or C++, or Java, …) thing than a good Ruby paradigm.
Slipp, I read your question carefully. There is no way you can have 2 different methods called full_name defined on the same object at the same time. BUT, you could do something like this:
module MacroMethods
private
def full_name(full_name)
# non-trivial, one-time-only set-up code exists here in actual usage
# this method can only be called once in the definition of any given class,
# because after the first call, it will be redefined!
extend AccessorMethods
end
end
module AccessorMethods
public
def full_name
# a non-trivial, runtime-calculated value is returned here in actual usage
end
end
class Base
extend MacroMethods
end
class Something < Base
full_name 'after this call, I will get a new full_name method!'
end
class SomethingElse < Base
full_name 'so will I!'
end
If you want to have class macros available to certain classes, then a common base class is not the Ruby solution. Instead, you create a module that extends the base classes with the functionality you want them to have:
module Extensions
def self.included(base_class)
base_class.extend ClassMethods
end
module ClassMethods
attr_accessor :full_name
end
end
class Something
include Extensions
self.full_name = "Something that goes bump in the night"
end
puts Something.full_name # => Something that goes bump in the night
thing = Something.new
puts thing.full_name # Error
This overrides a hook method in Extensions called Module#included that passes any class that includes the module as an argument. The new method then calls Object#extend on the base class to put the methods available in ClassMethods directly onto that class as class methods. This works the same way as defining class methods, but this runs dynamically. This frees you of needing to use your only base class on a class that provides macros. Note that the methods are not defined on instances of classes that include the module.
It looks like most of the other answers have the right idea, but are lacking the getter method for #full_name. This example might be what you're looking for:
class Thing
class << self
attr_writer :full_name
def full_name
"My full name is #{#full_name}"
end
end
end
With this you can do something like this:
> Thing.full_name = 'Thing Class'
=> "Thing Class"
> Thing.full_name
=> "My full name is Thing Class"
This seems needlessly complex. Why not just use an attribute on the parent class?
class Base
class << self
attr_accessor :full_name
end
end
class A < Base; end
class B < Base; end
A.full_name = "The full name of A"
B.full_name = "The full name of B"
puts A.full_name # "The full name of A"
puts B.full_name # "The full name of B"

Help a ruby noob understand class inheritance

I'm trying to learn ruby by building a basic Campfire bot to screw around with at work. I've gotten pretty far (it works!) and learned a lot (it works!), but now I'm trying to make it a bit more complex by separating the actions to be performed out into their own classes, so that they can be easier to write / fix when broken. If you're interested in seeing all the (probably crappy) code, it's all up on GitHub. But for the sake of this question, I'll narrow the scope a bit.
Ideally, I would like to be able to create plugins easily, name them the same as the class name, and drop them into an "actions" directory in the root of the project, where they will be instantiated at runtime. I want the plugins themselves to be as simple as possible to write, so I want them all to inherit some basic methods and properties from an action class.
Here is action.rb as it currently exists:
module CampfireBot
class Action
#handlers = {}
def initialize(room)
#room = room
end
class << self
attr_reader :handlers
attr_reader :room
def hear(pattern, &action)
Action.handlers[pattern] = action
end
end
end
end
Where #room is the room object, and #handlers is a hash of patterns and blocks. I kind of don't understand why I have to do that class << self call, but that's the only way I could get the child plugin classes to see that hear method.
I then attempt to create a simple plugin like so (named Foo.rb):
class Foo < CampfireBot::Action
hear /foo/i do
#room.speak "bar"
end
end
I then have my plugins instantiated inside bot.rb like so:
def load_handlers(room)
actions = Dir.entries("#{BOT_ROOT}/actions").delete_if {|action| /^\./.match(action)}
action_classes = []
# load the source
actions.each do |action|
load "#{BOT_ROOT}/actions/#{action}"
action_classes.push(action.chomp(".rb"))
end
# and instantiate
action_classes.each do |action_class|
Kernel.const_get(action_class).new(room)
end
#handlers = Action.handlers
end
The blocks are then called inside room.rb when the pattern is matched by the following:
handlers.each do |pattern, action|
if pattern.match(msg)
action.call($~)
end
end
If I do puts #room inside the initialization of Action, I see the room object printed out in the console. And if I do puts "foo" inside Foo.rb's hear method, I see foo printed out on the console (so, the pattern match is working). But, I can't read that #room object from the parent class (it comes out as a nil object). So obviously I'm missing something about how this is supposed to be working.
Furthermore, if I do something to make the plugin a bit cleaner (for larger functions) and rewrite it like so:
class Foo < CampfireBot::Action
hear /foo/i do
say_bar
end
def say_bar
#room.speak "bar"
end
end
I get NoMethodError: undefined method 'say_bar' for Foo:Class.
The definition of hear can be pulled out of the class << self block and changed to:
def self.hear(pattern, &action)
Action.handlers[pattern] = action
end
to yield the exact same result. That also immediately explains the problem. hear Is a class method. say_bar is an instance method. You can't call an instance method from a class method, because there simply isn't an instance of the class available.
To understand the class << self bit, you'll have to do your own reading and experiments: I won't try to improve on what has already been said. I'll only say that within the class << self .. end block, self refers to the eigenclass or metaclass of the CampfireBot::Action class. This is the instance of the Class class that holds the definition of the CampfireBot::Action class.

Resources