Get extended object from multiple decoration - ruby

Using SimpleDelegator, I created a few decorators to add extra functionalities to my objects. I need to decorate an object twice, as below:
Tracked.new(Audited.new(User.new))).save
Here is the basic structure of the decorator(s):
class Tracked #or Audited
delegate :id, to: :__getobj__
def initialize(extened_object)
super(extened_object)
#extened_object = extened_object
end
def itself
__getobj__
end
def save
super
# the extended behavior
end
end
I want to access the class User from the object. In Audited, if I call #extended_object.class with Audited.new(User.new)).save, I get User. In Tracked, if I call #extended_object.class with Tracked(Audited.new(User.new))).save, I get Audited instead.
How can I get the Class of the extended_object regardless of the number of times I decorate it?

I don't think you can do this with SimpleDelegator.
You need to implement this method yourself.
For example:
class MyDelegator < SimpleDelegator
def original_object
obj = __getobj__
obj.is_a?(MyDecorator) ? obj.original_object : obj
end
end
And all of your decorators should be inherited from MyDelegator

Related

Ruby delegation for an object without overriding constructor

One example of Ruby delegation is to use a SimpleDelegator:
class FooDecorator < SimpleDelegator
include ActiveModel::Model
# ...
end
That's very convenient, since anything not responded to by FooDecorator is passed to the underlying object.
But this overwrites the constructor's signature, which makes it incompatible with things like ActiveModel::Model that expect to see a particular signature.
Another example of Ruby delegation is to use Forwardable:
class Decorator
include ActiveModel::Model
extend Forwardable
attr_accessor :members
def_delegator :#members, :bar, :baz
def_delegators :#members, :a, :b, :c
end
But now you have to be explicit about which methods you want to delegate, which is brittle.
Is there a way to get the best of both worlds, where I...
don't tamper with the constructor
get pass-through delegation for any equivalently named method on the delegated object
Have you looked at the Delegator docs? You could basically re-implement your own Delegator subclass with the __getobj__ and __setobj__ methods. Or, you could just subclass SimpleDelegator and specify your own constructor that calls super(obj_to_delegate_to), no?
You could always just implement method_missing on your decorator and pass through any not-found method to the underlying object, too.
Edit: Here we go. The use of an intermediate inherited class breaks the super() chaining, allowing you to wrap the class as desired:
require 'active_model'
require 'delegate'
class Foo
include ActiveModel::Model
attr_accessor :bar
end
class FooDelegator < Delegator
def initialize
# Explicitly don't call super
end
def wrap(obj)
#obj = obj
self
end
def __getobj__
#obj
end
end
class FooDecorator < FooDelegator
include ActiveModel::Model
def name
self.class.name
end
end
decorator = FooDecorator.new.wrap(Foo.new bar: "baz")
puts decorator.name #=> "decorator"
puts decorator.bar #=> "bar"
Why do you want ActiveModel::Model? Do you truly need all of the features?
Could you merely do the same thing?
extend ActiveModel::Naming
extend ActiveModel::Translation
include ActiveModel::Validations
include ActiveModel::Conversion
I'm aware that changes to ActiveModel::Model could break your decorator in terms of the expected behavior, but you're coupling pretty tightly to it anyway.
Allowing modules to control constructors is a code smell. Might be ok, but you should second guess it and be well aware of why it does so.
ActiveModel::Model defines initialize to expect a hash. I'm not sure how you expect to get the particular object that you want to wrap.
SimpleDelegator uses __setobj__ inside of it's constructor, so you can use that after including a module that overrides your constructor.
If you want automatic forwarding you can just define the methods you need on your decorator when you set the object. If you can control how your object is created, make a build method (or something like that) which calls the initialize that needs to be used for ActiveModel::Model and __setobj__ that's used for SimpleDelegator:
require 'delegate'
require 'forwardable'
class FooCollection < SimpleDelegator
extend Forwardable
include ActiveModel::Model
def self.build(hash, obj)
instance = new(hash)
instance.send(:set_object, obj)
instance
end
private
def set_object(obj)
important_methods = obj.methods(false) - self.class.instance_methods
self.class.delegate [*important_methods] => :__getobj__
__setobj__(obj)
end
end
This allows you to used the ActiveModel interface but adds the SingleForwardable module to the singleton class of the decorator which gives you the delegate method. All you need to do then is pass it a collection of method names and a method to use to get the object for forwarding.
If you need to include or exclude particular methods, just change the way important_methods is created. I didn't think much about that, so double-check what's actually being used there before grabbing this code. For example, once the set_object method is called once, you can skip calling it later, but this is built to expect that all wrapped objects have the same interface.
As you pointed out on twitter, the draper gem uses the delegate method (from ActiveSupport) inside of method_missing. With that approach, each missed hit will incur the cost of opening the class and defining the method for forwarding. The upside is that it's lazy and you don't need to calculate which methods need to be forwarded and that hit is only incurred on the first miss; subsequent method calls won't be missed because you're defining that method.
The code I made above will get all those methods and define them at once.
If you need more flexibility and expect your decorator to not be the same type of object you can use SingleForwardable for the same effect but it will define methods for each wrapped instance instead of affecting the decorator class:
require 'delegate'
require 'forwardable'
class FooCollection < SimpleDelegator
include ActiveModel::Model
def self.build(hash, obj)
instance = new(hash)
instance.set_object(obj)
instance
end
def set_object(obj)
important_methods = obj.methods(false) - self.class.instance_methods
singleton_class.extend SingleForwardable
singleton_class.delegate [*important_methods] => :__getobj__
__setobj__(obj)
end
end
But all of this is using SimpleDelegator and if you're not actually using method_missing, you can cut that out (assuming you've calculated the important_methods part correctly:
require 'forwardable'
class FooCollection
include ActiveModel::Model
def self.build(hash, obj)
instance = new(hash)
instance.set_object(obj)
instance
end
def set_object(obj)
important_methods = obj.methods(false)# - self.class.instance_methods
singleton_class.extend SingleForwardable
singleton_class.delegate [*important_methods] => :__getobj__
__setobj__(obj)
end
def __getobj__
#obj
end
def __setobj__(obj)
__raise__ ::ArgumentError, "cannot forward to self" if self.equal?(obj)
#obj = obj
end
end
If you do that, however, it kills the use of super so you can't override a method defined on your wrapped object and call super to get the original value like you can with method_missing used in SimpleDelegator.
I wrote casting to add behavior to objects without worrying about wrappers. You can't override methods with it, but if all you're doing is adding new behaviors and new methods, then it will be much simpler to use by just adding a bucket of methods to an existing object. It's worth checking it out. I gave a presentation about the delegate and forwardable libraries at RubyConf 2013

How can I create fake has_many relationships, and have each one include class methods as instance methods?

I have a class, with some fake relationships I want to implement:
module FormStack
class Connection
def forms; end
def fields; end
end
end
I have metaprogramically generated classes for both forms, and fields (as they are RESTful resources, they share the same action names and params), and I want to include those methods in my fake relationships in my FormStack::Connection class. can this be done?
I essentially want <FromStack::Connection Instance>.forms to behave as if it were FormStack::Form, so I can do things like <connection>.forms.all or <connection>.forms.find(id).
Is this possible?
Any best practices I should maybe be looking at? (This seems a little strange to me, but I think it's an elegant way to have the methods implemented in a useful way, while still having an ActiveRecord-esque abstraction of the restful resources / objects).
Here is the code I'm working with, if you want to look: https://github.com/TinderBox/formstack/tree/connection_instances
Why not just use simple composition? Pass whatever object has the has_many FormStack::Form relation in when you initialize a new FormStack::Connection instance. Then you can directly invoke the #forms method on the FormStack::Form collection instance, or you can use delegation.
FormStack::Connection.new(FormStack::FormCollection.new(params[:form]) #sample class name -- obviously use whatever has the real has_many :forms
module FormStack
class Connection
def initialize(form_collection)
#form_collection = form_collection
end
def forms
#form_collection.forms
end
def fields
#form_collection.fields
end
end
end
Or
module FormStack
class Connection
extend Forwardable
def_delegators :#form_collection, :forms, :fields
def initialize(form_collection)
#form_collection = form_collection
end
end
end
Unless there is a better way, this is how I've solved my problem for now:
def method_missing(meth, *args, &block)
method_name = meth.to_s
if "forms" == method_name
FormStack::Form.connection = self
FormStack::Form
elsif ...
else
super
end
end
https://github.com/TinderBox/formstack/blob/082793bed97e97cc65c703c8ca3cb382cbdf743a/lib/formstack/connection.rb

List only ActiveRecord subclass methods

Given the following ActiveRecord model:
class User < ActiveRecord::Base
has_many :games
def name
"Joe"
end
def city
"Chicago"
end
end
I'd like to retrieve a list of the methods I added directly to the User class (and not those added by extending ActiveRecord and/or adding associations). Example output:
["name","city"]
Calling User.instance_methods(false) returns method added by ActiveRecord:
["validate_associated_records_for_games", "games", "game_ids", "games=", "game_ids=", "after_create_or_update_associated_records_for_games", "before_save_associated_records_for_games"]
Along with any model attributes from database columns. I'd like to exclude those and just get the custom methods on the subclass.
My purpose is method tracing: I'd like to trace my custom methods while excluding those added by ActiveRecord.
Any ideas?
User.instance_methods - ActiveRecord::Base.instance_methods #=> [:name,:city]
UPDATE:
the order of these methods are significant
class User < ActiveRecord::Base
def self.my_own_methods
self.instance_methods - ##im
end
has_many :games
##im = self.instance_methods
def name
"Joe"
end
def city
"Chicago"
end
end
User.my_own_methods #=> [:name, :city]
This one tested and it works
Use a method_added hook which adds method names to a list, and monkey patch Active Record so that methods added by AR are not added to that list.
If you don't want to crack AR open and start poking around, you could also define a "class macro" which defines a method and adds it to the list. For your own custom methods, use the class macro rather than def.
If you're not familiar with what I'm referring to as a "class macro", it's simply a method like this:
class Class
def mydef(name,&block)
(#methods || []) << name
define_method(name,&block)
end
end
Using something like mydef to define methods rather than def is definitely ugly, but it would solve the problem without requiring any monkey-patching.

Create property as method on class in Ruby

Rails has these cool properties that seem to be actually methods. For example:
class SomeController < ApplicationController
before_filter :authenticate!
end
What are these actually called and how would you create your own? For example, in one of my models I want to be able to have a dynamic property that selects an internal method for processing some results:
class MyModel < ActiveRecord::Base
active_method :some_class_method
end
How would I set this up so I can set active_method like that and be able to access the active_method symbol as an instance var?
Edit for elaboration:
So give this starter below, I need to figure out how to define "selected_method" so that it defines a accessor or instance variable so "called_selected_method" calls "method_b".
class MyClass
selected_method :method_b
def call_selected_method
end
private
def method_a
puts 'method_a'
end
def method_b
puts 'method_b'
end
end
c = MyClass.new
c.call_selected_method # should put 'method_b'
It's actually just a method call to a method defined on the class. before_filter is provided by a ruby Module, which is mixed in to ActionController.
Creating your own methods similar to before_filter is as easy as:
Define a class method on your Class
Call that method in any concrete implementations of your class.
Some example code:
class MyClass
class << self
def some_function(*args)
# your code here
end
end
some_function "foo"
end
If you wanted to abstract it further, you can put the class method in to a Module, and then include that module in to your class(es).
UPDATE:
In relation to your asking of how to get a call of some_function to set an instance variable on your class, you can't, as class methods cannot affect specific instances of that class.
I have to wonder, though... you're writing a method that will just act as a proxy to your other method, and would be hard-coded in to the class definition. That offers no benefit to you, and would just make your code redundantly complicated.

How to add before_validation callback to single object, not whole class in ActiveRecord 3?

How to add before_validation callback to single object, not whole class in ActiveRecord 3?
In AR 2 I did like this:
module ObjExtend
def before_validation
p 'before_validation'
return super
end
end
obj.extend ObjExtend
but it does not work anymore
That's because in rails3 you can no longer just define methods in your models named for the callback in question. You have to declare them with before_validation :foo or what have you (:foo refers to an instance method of the model class but you can also pass in a proc or a class).
So.. This isn't the ideal solution but it should give you some ideas:
obj = MyModel.first
class << obj
before_validation :foo
def foo
p 'before_validation'
return super
end
end
This code opens the object's 'eigenclass' (there's various names for this thing) and behaves as though it were the original class context.

Resources