• Stars
    star
    359
  • Rank 118,537 (Top 3 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 12 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Delegate methods in Ruby and preserve self. Add behaviors to your objects without altering their superclass hierarchy.

Casting

Code Climate Test Coverage Gem Version

Add behavior to your objects without using extend

Do it for the life of the object or only for the life of a block of code.

Casting gives you real delegation that flattens your object structure compared to libraries like Delegate or Forwardable. With casting, you can implement your own decorators that will be so much simpler than using wrappers.

Here's a quick example that you might try in a Rails project:

# implement a module that contains information for the request response
# and apply it to an object in your system.
def show
  @user = user.cast_as(UserRepresenter)
end

To use proper delegation, your approach should preserve self as a reference to the original object receiving a method. When the object receiving the forwarded message has its own and separate notion of self, you're working with a wrapper (also called consultation) and not using delegation.

The Ruby standard library includes a library called "delegate", but it is a consultation approach. With that "delegate", all messages are forwarded to another object, but the attendant object maintains its own identity.

With Casting, your defined methods may reference self and during execution it will refer to the original client object.

Casting was created while exploring ideas for cleaning up ruby programs.

Usage

To use Casting, you must first extend an object as the delegation client:

actor = Object.new
actor.extend(Casting::Client)

Or you may include the module in a particular class:

class Actor
  include Casting::Client
end
actor = Actor.new

Your objects will have a few additional methods: delegation, cast, and if you do not already have it defined (from another library, for example): delegate. The delegate method is aliased to cast.

Then you may delegate a method to an attendant object:

actor.delegate(:hello_world, other_actor)

Or you may create an object to manage the delegation of methods to an attendant object:

actor.delegation(:hello_world).to(other_actor).call

You may also delegate methods without an explicit attendant instance, but provide a module containing the behavior you need to use:

module GreetingModule
  def hello_world
    "hello world"
  end
end

actor.delegate(:hello_world, GreetingModule)
# or
actor.delegation(:hello_world).to(GreetingModule).call

Pass arguments to your delegated method:

actor.delegate(:verbose_method, another_actor, arg1, arg2)

actor.delegation(:verbose_method).to(another_actor).with(arg1, arg2).call

actor.delegation(:verbose_method).to(another_actor).call(arg1, arg2)

That's great, but why do I need to do these extra steps? I just want to run the method.

Casting gives you the option to do what you want. You can run just a single method once, or alter your object to always delegate. Even better, you can alter your object to delegate temporarily...

Temporary Behavior

Casting also provides an option to temporarily apply behaviors to an object.

Once your class or object is a Casting::Client you may send the delegate_missing_methods message to it and your object will use method_missing to delegate methods to a stored attendant.

class Actor
  include Casting::Client
  delegate_missing_methods
end
actor = Actor.new

actor.hello_world #=> NoMethodError

Casting.delegating(actor => GreetingModule) do
  actor.hello_world #=> output the value / perform the method
end

actor.hello_world #=> NoMethodError

The use of method_missing is opt-in. If you don't want that mucking up your method calls, just don't tell it to delegate_missing_methods.

Before the block is run in Casting.delegating, a collection of delegate objects is set in the current Thread for the provided attendant. Then the block yields, and an ensure block cleans up the stored attendant.

This allows you to nest your delegating blocks as well:

actor.hello_world #=> NoMethodError

Casting.delegating(actor => GreetingModule) do
  actor.hello_world #=> output the value / perform the method

  Casting.delegating(actor => OtherModule) do
    actor.hello_world #=> still works!
    actor.other_method # values/operations from the OtherModule
  end

  actor.other_method #=> NoMethodError
  actor.hello_world #=> still works!
end

actor.hello_world #=> NoMethodError

Currently, by using delegate_missing_methods you forever mark that object or class to use method_missing. This may change in the future.

Manual Delegate Management

If you'd rather not wrap things in the delegating block, you can control the delegation yourself. For example, you can cast_as and uncast an object with a given module:

actor.cast_as(GreetingModule)
actor.hello_world # all subsequent calls to this method run from the module
actor.uncast # manually cleanup the delegate
actor.hello_world # => NoMethodError

These methods are only defined on your Casting::Client object when you tell it to delegate_missing_methods. Because these require method_missing, they do not exist until you opt-in.

Duck-typing with NullObject-like behavior

Casting has a few modules built in to help with treating your objects like null objects. Take a look at the following example:

module SpecialStuff
  def special_link
    # some link code
  end
end

special_user.cast_as(SpecialStuff)
special_user.special_link # outputs your link

If your app, for example, generates a list of info for a collection of users, how do you manage the objects which don't have the expected behavior?

[normal_user, other_user, special_user].each do |user|
  user.special_link #=> blows up for normal_user or other_user
end

You can cast the other objects with Casting::Null or Casting::Blank:

normal_user.cast_as(Casting::Null)
other_user.cast_as(Casting::Blank)
special_user.cast_as(SpecialStuff)

[normal_user, other_user, special_user].each do |user|
  user.special_link #=> normal_user yields nil, other_user yields "", and special_user yields the special_link
end

I have a Rails app, how does this help me?

Well, a common use for this behavior would be in using decorators.

When using a wrapper, your forms can behave unexpectedly

class UsersController
  def edit
    @user = UserDecorator.new(User.find(params[:id]))
  end
end

<%= form_for(@user) do |f| %> #=> <form action="/user_decorators/1">

Ruby allows you to hack this by defining the class method:

class UserDecorator
  def class
    User
  end
end

That would solve the problem, and it works! But having an object report that its class is something other than what it actually is can be confusing when you're debugging.

Instead, you could cast the object as a module and your form will generate properly:

class UsersController
  def edit
    @user = User.find(params[:id]).cast_as(UserDecorator) # as a module
  end
end

<%= form_for(@user) do |f| %> #=> <form action="/users/1">

This keeps your code focused on the object you care about.

Check out Characterize for hooking into Rails automatically.

Oh, my! Could this be used to add behavior like refinements?

You can apply methods from a delegate to all instances of a class.

person.hello_world #=> NoMethodError

Casting.delegating(Person => GreetingModule) do
  person.hello_world #=> output the value / perform the method
end

person.hello_world #=> NoMethodError

By default, the delegate_missing_methods method will set delegates on instances so you'll need to opt-in for this.

class Person
  include Casting::Client
  delegate_missing_methods :class
end

But what happens when you have method clashes or want a specific instance to behave differently?

You can have your objects look to their instance delegates, their class delegates, or in a particular order:

class Person
  include Casting::Client
  # default delegation to instances
  delegate_missing_methods

  # delegate methods to those defined on the class
  delegate_missing_methods :class

  # delegate methods to those defined on the class, then those defined on the instance
  delegate_missing_methods :class, :instance

  # delegate methods to those defined on the instance, then those defined on the class
  delegate_missing_methods :instance, :class
end

What's happening when I use this?

Ruby allows you to access methods as objects and pass them around just like any other object.

For example, if you want a method from a class you may do this:

class Person
  def hello
    "hello"
  end
end
Person.new.method(:hello).unbind #=> #<UnboundMethod: Person#hello>
# or
Person.instance_method(:hello) #=> #<UnboundMethod: Person#hello>

But if you attempt to use that UnboundMethod on an object that is not a Person you'll get an error about a type mismatch.

Casting will bind an UnboundMethod method to a client object and execute the method as though it is defined on the client object. Any reference to self from the method block will refer to the client object.

Rather than define methods on classes, you may take any method from a module and apply it to any object regardless of its class.

GreetingModule.instance_method(:hello).bind(actor).call

Casting provides a convenience for doing this.

What if my modules create instance variables on the object? Can I clean them up?

Yup.

If you need to set some variables so that your module can access them, it's as easy as defining cast_object and uncast_object on your module. Here's an example:

module Special
  def self.cast_object(obj)
    obj.instance_variable_set(:@special_value, 'this is special!')
  end
  
  def self.uncast_object(obj)
    obj.remove_instance_variable(:@special_value)
  end
  
  def special_behavior
    "#{self.name} thinks... #{@special_value}"
  end
end

object.cast_as(Special)
object.special_method
object.uncast
# object no longer has the @special_value instance variable

You'll be able to leave your objects as if they were never touched by the module where you defined your behavior.

It doesn't work!

You might be trying to override existing methods. Casting can help you apply behavior to an object using delegate_missing_methods but that depends on the methods being missing. In other words, if you have an as_json method that you want to change with a module, you won't be able to just cast_as(MyJsonModule) and have the as_json method from it be picked up because that will never hit method_missing.

If you want to override an existing method, you must do so explicitly.

This will not work:

module MyJsonModule
  def as_json
    super.merge({ extra: 'details' })
  end
end
some_object.cast_as(MyJsonModule)
some_object.as_json

Instead, you'll need to explicitly override existing methods:

some_object.cast(:as_json, MyJsonModule)

How can I speed it up?

Are you looping over lots of objects and want see better performance?

If you want to make things a bit faster, you can prepare the method delegation ahead of time and change the client object.

prepared_delegation = some_object.delegation(:some_delegated_method).to(MySpecialModule)
# Some looping code
big_list_of_objects.each do |object|
  prepared_delegation.client = object
  prepared_delegation.call
end

Preparing the delegated method like this will probably speed things up for you but be sure to verify for yourself.

Installation

If you are using Bundler, add this line to your application's Gemfile:

gem 'casting'

And then execute:

$ bundle

Or install it yourself as:

$ gem install casting

Contributing

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

Built by Jim Gay at Saturn Flyer

More Repositories

1

surrounded

Create encapsulated systems of objects and focus on their interactions
Ruby
253
star
2

radiant-vapor-extension

Creates an interface to manage the redirection of URLs in Radiant CMS
Ruby
29
star
3

radiant-rbac_base-extension

Role Based Access Control
Ruby
25
star
4

polyfill-data

Adds the Ruby 3.2 Data class to lower Ruby versions
Ruby
24
star
5

radiant-portfolio

client portfolio extension for radiant cms
Ruby
23
star
6

radiant-blog-extension

Adds features for blogging
Ruby
20
star
7

radiant-help-extension

Help documentation extension for radiant cms
Ruby
19
star
8

characterize

Decorate objects in Rails without the pain of wrappers
Ruby
16
star
9

direction

Forward messages to collaborators in East-oriented style.
Ruby
14
star
10

radiant-dashboard-extension

Provides an area to view (and extend) recent activity in Radiant.
Ruby
13
star
11

radiant-haml_filter-extension

Filter RadiantCMS content with HAML
Ruby
12
star
12

strftime

Ruby gem to provide details about Date and Time string format directives
Ruby
11
star
13

group_cache_key

Adds a cache_key method to ActiveRecord collections
Ruby
10
star
14

radiant-site_watcher-extension

Records popular pages in Radiant by counting the number of times the cache has been created for each url
Ruby
9
star
15

radiant-featured_pages-extension

Adds the ability to feature pages in Radiant and list them without regard to their place in the page hierarchy
Ruby
8
star
16

radiant-application_theme-extension

Provides the ability to alter the default Radiant interface without changing the underlying layout.
Ruby
7
star
17

show_for_stonewall

Link stonewall and show_for
Ruby
7
star
18

radiant-settings

A global setting editor extension for Radiant
Ruby
7
star
19

triad

A collection that behaves like Set but each item has 3 parts.
Ruby
6
star
20

direct

direct your Ruby objects to perform a block. avoid using "if"
Ruby
6
star
21

blank_slate

BlankSlate is useful for implementing the Null Object pattern without method_missing.
Ruby
6
star
22

radiant-rbac_page_edit-extension

Role based access to editing and creating pages. Built on RBAC Base
Ruby
6
star
23

radiant-admin_breadcrumbs-extension

Adds breadcrumb links to the page edit interface for ancestors of the current page.
Ruby
6
star
24

radiant-page-group-permissions-extension

Organize your users into groups and divide up site-editing privileges accordingly
Ruby
6
star
25

radiant-header_authorize-extension

Overrides the Radiant authentication to login by HTTP header
Ruby
6
star
26

interrogator

get information about your activerecord classes
JavaScript
6
star
27

radiant-jquery_admin-extension

Alters the Radiant admin interface to use jQuery instead of Prototype
5
star
28

expand

edit classes and modules under a namespace
Ruby
5
star
29

radiant-wordpress_migrator-extension

Assistance with migrating WordPress content
Ruby
5
star
30

radiant-seo_help-extension

Provides extra tags for page meta information.
Ruby
5
star
31

radiant-sheets-extension

Manage CSS and Javascript content in Radiant CMS as Sheets, a subset of Pages
Ruby
5
star
32

radiant-wmd_filter-extension

Use WMD to write Markdown in a WYSIWYM editor
JavaScript
5
star
33

radiant-rbac_snippets-extension

Role based access to Radiant snippets - Requires RbacBaseExtension
Ruby
4
star
34

purple_door

Ruby
4
star
35

radiant-user_home-extension

Allows user configurable home screen location for Radiant CMS
Ruby
4
star
36

radiant-invisible_pages-extension

Allows you to remove pages from the index list
Ruby
4
star
37

radiant-admin_data-extension

Gives RadiantCMS a simple CRUD interface for database tables
Ruby
4
star
38

radiant-help_use_cases-extension

Adds additional information to the Help extension (radiant-help-extension)
Ruby
4
star
39

radiant-drafts-extension

Provides the ability to manage drafts of Page Parts.
Ruby
3
star
40

radiant-refactor

refactoring radiant for rails 3
Ruby
3
star
41

behavioral

Add and remove behaviors to individual Ruby objects
Ruby
3
star
42

radiant-change_author-extension

Allows administrators to change the author of a page
Ruby
3
star
43

radiant_page_preview_extension

This extension allows you to view previews of pages, even if they are not in "published" mode.
Ruby
3
star
44

radiant-user_pref_control-extension

Allow other extensions to provide user preferences in Radiant CMS
Ruby
3
star
45

radiant-sns_page_hook-extension

Allows you to use Radiant page radius tags in stylesheets and javascripts from the SNS extension.
Ruby
3
star
46

radiant-help_inline-extension

Adds help information directly into the Radiant interface (rather than in a separate tab)
Ruby
3
star
47

radiant-browser-extension

An onscreen browser for snippets and other assets for Radiant CMS
JavaScript
3
star
48

radiant-page_review_process-extension

Role and status based access to pages - Requires RbacBaseExtension
Ruby
3
star
49

radiant-page_group_rbac_migrator-extension

Provides a migration to move from Page Group Permissions to RBAC Page Edit
Ruby
2
star
50

name_change_o_chart

Name converter based on "Captain Underpants and the Perilous Plot of Professor Poopypants", by Dav Pilkey.
Ruby
2
star
51

spreedly_sample_app

Sample application for connecting to Spreedly
Ruby
2
star
52

radiant-wordpress_link_saver-extension

Maintains the links within imported content from Wordpress to Radiant
Ruby
2
star
53

java_access_manager_plugin

A plugin to help you work with Sun's Java Access Manager
Ruby
2
star
54

radiant-enabler-extension

Allows you to disable and enable an instance of RadiantCMS remotely
Ruby
2
star
55

radiant-people-extension

Manage people in Radiant
Ruby
2
star
56

surrounded-rails

Makes Rails models and controllers use Surrounded
Ruby
2
star
57

radiant-shortcut-extension

Allows you to set pages as shortcuts so that they may be found from the root by their nested url or slug
Ruby
2
star
58

radiant-compressor-extension

Strips all non space-character whitespace, and replaces double spaces with single spaces in the Radiant ResponseCache
Ruby
2
star
59

radiant-browser-extension-duplicate

An onscreen browser for snippets and other assets for Radiant CMS
2
star
60

radiant-escape-extension

Simple tags for escaping content in Radiant CMS
1
star
61

simpleDOM

Javascript library to manage the creation of DOM objects
JavaScript
1
star
62

radiant-snippets-extension

moved to https://github.com/radiant/radiant-snippets-extension
Ruby
1
star
63

saturnflyer.github.com

things
1
star
64

radiant-audit-extension

An extensible auditing framework for Radiant
JavaScript
1
star
65

radiant-snapshot-extension

Creates a static HTML copy of your Radiant CMS site
Ruby
1
star
66

radiant-engine

hacks
1
star
67

import_module

"import-module" enables to incude modules dynamically
Ruby
1
star
68

rubycitizen

Be A Good Ruby Citizen
JavaScript
1
star
69

radiant-employment-extension

Manage your company's employment opportunities from Radiant CMS
Ruby
1
star
70

radiant-blogger-extension

Integrate blogger content into radiant as tag.
Ruby
1
star
71

autoselect

jQuery UI autocomplete field for a large select element
JavaScript
1
star
72

visualize

fork of the jQuery visualize plugin for charting data tables
JavaScript
1
star