Giter VIP home page Giter VIP logo

dumb_delegator's Introduction

DumbDelegator

Gem Version CI Maintainability Test Coverage

Ruby provides the delegate standard library. However, we found that it is not appropriate for cases that require nearly every call to be proxied.

For instance, Rails uses #class and #instance_of? to introspect on Model classes when generating forms and URL helpers. These methods are not forwarded when using Delegator or SimpleDelegator.

require "delegate"

class MyAwesomeClass
  # ...
end

o = MyAwesomeClass.new
d = SimpleDelegator.new(o)

d.class                #=> SimpleDelegator
d.is_a? MyAwesomeClass #=> false

DumbDelegator, on the other hand, forwards almost ALL THE THINGS:

require "dumb_delegator"

class MyAwesomeClass
  # ...
end

o = MyAwesomeClass.new
d = DumbDelegator.new(o)

d.class                #=> MyAwesomeClass
d.is_a? MyAwesomeClass #=> true

Installation

Add this line to your Gemfile:

gem "dumb_delegator"

And then install:

$ bundle

Or install it yourself:

$ gem install dumb_delegator

Versioning

This project adheres to Semantic Versioning.

Version 0.8.x

The 0.8.0 release was downloaded 1.2MM times before the 1.0.0 work began. Which is great! ๐ŸŽ‰ But, we wanted to clean up some cruft, fix a few small things, and improve ergonomics. And we wanted to do all of that while, hopefully, not breaking existing usage.

To that end, 1.0.0 dropped support for all EoL'd Rubies and only officially supported Ruby 2.4 - 2.7 when it was released. However, most older Rubies, should still work. Maybeโ€ฆ Shmaybe? Except for Ruby 1.9, which probably does not work with DumbDelegator > 1.0.0. If you're on an EoL'd Ruby, please try the 0.8.x versions of this gem.

Usage

DumbDelegator's API and usage patters were inspired by Ruby stdlib's SimpleDelegator. So the usage and ergonomics are quite similar.

require "dumb_delegator"

class Coffee
  def cost
    2
  end

  def origin
    "Colombia"
  end
end

class Milk < DumbDelegator
  def cost
    super + 0.4
  end
end

class Sugar < DumbDelegator
  def cost
    super + 0.2
  end
end

coffee = Coffee.new

cup_o_coffee = Sugar.new(Milk.new(coffee))
cup_o_coffee.origin        #=> Colombia
cup_o_coffee.cost          #=> 2.6

# Introspection
cup_o_coffee.class         #=> Coffee
cup_o_coffee.__getobj__    #=> #<Coffee:0x00007fabed1d6910>
cup_o_coffee.inspect       #=> "#<Sugar:70188197507600 obj: #<Milk:70188197507620 obj: #<Coffee:0x00007fabed1d6910>>>"
cup_o_coffee.is_a?(Coffee) #=> true
cup_o_coffee.is_a?(Milk)   #=> true
cup_o_coffee.is_a?(Sugar)  #=> true

Rails Model Decorator

There are many decorator implementations in Ruby. One of the simplest is "SimpleDelegator + super + __getobj__," but it has the drawback of confusing Rails. It is necessary to redefine #class, at a minimum. If you're relying on Rails' URL Helpers with a delegated object, you also need to redefine #instance_of?. We've also observed the need to redefine other Rails-y methods to get various bits of ๐Ÿง™ Rails Magic ๐Ÿง™ to work as expected.

With DumbDelegator, there's not a need for redefining these things because nearly every possible method is delegated.

Optional case statement support

Instances of DumbDelegator will delegate #=== out of the box. Meaning an instance can be used in a case statement so long as the when clauses rely on instance comparison. For example, when using a case with a regular expression, range, etc...

It's also common to use Class/Module in the where clauses. In such usage, it's the Class/Module's ::=== method that gets called, rather than the #=== method on the DumbDelegator instance. That means we need to override each Class/Module's ::=== method, or even monkey-patch ::Module::===.

DumbDelegator ships with an optional extension to override a Class/Module's ::=== method. But you need to extend each Class/Module you use in a where clause.

def try_a_case(thing)
  case thing
  when MyAwesomeClass
    "thing is a MyAwesomeClass."
  when DumbDelegator
    "thing is a DumbDelegator."
  else
    "Bad. This is bad."
  end
end

target = MyAwesomeClass.new
dummy = DumbDelegator.new(target)

try_a_case(dummy) #=> thing is a DumbDelegator.

MyAwesomeClass.extend(DumbDelegator::TripleEqualExt)

try_a_case(dummy) #=> thing is a MyAwesomeClass.

Overriding Module::===

If necessary, you could also override the base Module::===, though that's pretty invasive.

๐Ÿฒ There be dragons! ๐Ÿ‰

::Module.extend(DumbDelegator::TripleEqualExt)

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Contribution Ideas/Needs

  1. Ruby 1.8 support (use the blankslate gem?)

dumb_delegator's People

Contributors

alindeman avatar bmxpert1 avatar clowder avatar phiggins avatar radarseesradar avatar stevenharman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

dumb_delegator's Issues

Send goes straight to the delegated object

Hi, I just discovered this gem and it looks very interesting, I found a small bug, though.

If I call send on a DumbDelegator object, it goes straight to the delegated object.

>> coffee = Coffee.new
=> #<Coffee:0x007fad18dc6550>
>> Milk.new(coffee).cost
=> 2.4
>> Milk.new(coffee).send(:cost)
=> 2
>>

Something like this does the job, but I don't know if it's ideal:

def send(*args)
  if self.respond_to?(args.first) then
    self.__send__(*args)
  else
    super
  end
end

Does not work with case statements

While dumb delegator allows us to forward is_a? and class to the delegate, it does not work with case statements, which internally uses the === operator:

MyAwesomeClass  = Class.new

o = MyAwesomeClass.new
d = DumbDelegator.new(o)

case d
when MyAwesomeClass then 'awesome'
when DumbDelegator then 'not so awesome'
else 'not awesome at all'
end
#=> 'not so awesome'

The original comparison is done in C so the changed kind_of? method is not in effect:

From: object.c (C Method):
Owner: Module
Visibility: public
Number of lines: 5

static VALUE
rb_mod_eqq(VALUE mod, VALUE arg)
{
    return rb_obj_is_kind_of(arg, mod);
}

I suppose a solution would require to monkey patch Module#===. For example

class Module
  def ===(other)
    other.kind_of?(self)
  end
end

If you like I can make a PR for this but I wanted to hear your opinion first.

Thanks!

`respond_to?` does not return `true` for new methods added to the delegator

It looks like new methods added to a delegator do not return true for respond_to?.

class Shape
  def sides; 3; end
end

class BigShape
   def sides; 10; end
   def colour; 'blue'; end
end

shape = Shape.new
big_shape = BigShape.new(shape)

# okay for overriden methods
big_shape.sides # => 10
big_shape.respond_to? :side # => true

# not okay for new methods
big_shape.colour # => 'blue'
big_shape.respond_to? :colour # => false

Is there any work around, it does not look like respond_to? in the delegator is not called at all.

The simple example aside I am using DumbDelegator to add some setter methods to a ActiveRecord object which do not corrispond to columns in the data table. When doing assign_attributes I get an unknown attribute error since Rails, I guess, checks respond_to? first.

#NoMethodsError: super : no superclass method

require 'dumb_delegator'

class A < DumbDelegator
  def bar
    'bar'
  end
end

class B < DumbDelegator
  def bar
    "foo" + super
  end
end

B.new(A.new("")).bar

=>

#irb(main):002:0> B.new(A.new("")).bar
#NoMethodError: super: no superclass method `bar' for "":B

=>from /usr/local/opt/rbenv/versions/1.9.3-p327-perf/gemsets/rmo/gems/dumb_delegator-0.5.1/lib/d>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.