├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── eigenclass.gemspec ├── lib ├── eigenclass.rb └── eigenclass │ └── version.rb └── spec ├── eigenclass_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | pkg -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | addons: 2 | code_climate: 3 | repo_token: 6e85e0e345260307d97927e8eee7f52e2cec36c021b792a30bf7ffb9a6f8eea8 4 | 5 | install: bundle install --jobs 3 --retry 3 6 | 7 | rvm: 8 | - 1.8.7 9 | - 1.9.3 10 | - 2.0.0 11 | - ruby-head 12 | 13 | script: bundle exec rspec 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://www.rubygems.org' 2 | 3 | gemspec 4 | 5 | unless RUBY_VERSION =~ /1\.8\./ 6 | gem 'codeclimate-test-reporter' 7 | end 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | eigenclass (2.0.4) 5 | 6 | GEM 7 | remote: https://www.rubygems.org/ 8 | specs: 9 | codeclimate-test-reporter (0.4.7) 10 | simplecov (>= 0.7.1, < 1.0.0) 11 | diff-lcs (1.2.5) 12 | docile (1.1.5) 13 | multi_json (1.10.1) 14 | rspec (3.2.0) 15 | rspec-core (~> 3.2.0) 16 | rspec-expectations (~> 3.2.0) 17 | rspec-mocks (~> 3.2.0) 18 | rspec-core (3.2.1) 19 | rspec-support (~> 3.2.0) 20 | rspec-expectations (3.2.0) 21 | diff-lcs (>= 1.2.0, < 2.0) 22 | rspec-support (~> 3.2.0) 23 | rspec-mocks (3.2.1) 24 | diff-lcs (>= 1.2.0, < 2.0) 25 | rspec-support (~> 3.2.0) 26 | rspec-support (3.2.2) 27 | simplecov (0.9.2) 28 | docile (~> 1.1.0) 29 | multi_json (~> 1.0) 30 | simplecov-html (~> 0.9.0) 31 | simplecov-html (0.9.0) 32 | 33 | PLATFORMS 34 | ruby 35 | 36 | DEPENDENCIES 37 | codeclimate-test-reporter 38 | eigenclass! 39 | rspec 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Sean Huber - github@shuber.io 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [![Sean Huber](https://cloud.githubusercontent.com/assets/2419/6550752/832d9a64-c5ea-11e4-9717-6f9aa6e023b5.png)](https://github.com/shuber) eigenclass 2 | 3 | [![Build Status](https://secure.travis-ci.org/shuber/eigenclass.svg)](http://travis-ci.org/shuber/eigenclass) [![Code Climate](https://codeclimate.com/github/shuber/eigenclass/badges/gpa.svg)](https://codeclimate.com/github/shuber/eigenclass) [![Coverage](https://codeclimate.com/github/shuber/eigenclass/badges/coverage.svg)](https://codeclimate.com/github/shuber/eigenclass) [![Gem Version](https://badge.fury.io/rb/eigenclass.svg)](http://badge.fury.io/rb/eigenclass) 4 | 5 | > Eigenclasses aka *metaclasses* or *singleton classes* in Ruby. 6 | 7 | Check out the [metaclass] implementations in other languages for more examples. 8 | 9 | **Note**: This gem was originally written back in 2008. Since then, Ruby has introduced a couple new methods which provide the same functionality as this gem's `eigenclass` and `edefine_method` methods. 10 | 11 | * [Object#singleton_class] 12 | * [Object#define_singleton_method] 13 | 14 | [metaclass]: http://en.wikipedia.org/wiki/Metaclass 15 | [Object#singleton_class]: http://ruby-doc.org/core-1.9.2/Object.html#method-i-singleton_class 16 | [Object#define_singleton_method]: http://ruby-doc.org/core-1.9.2/Object.html#method-i-define_singleton_method 17 | 18 | 19 | ## Installation 20 | 21 | ``` 22 | gem install eigenclass 23 | ``` 24 | 25 | 26 | ## Requirements 27 | 28 | Ruby 1.8.7+ 29 | 30 | 31 | ## Usage 32 | 33 | Everything in Ruby is an object, including classes. 34 | 35 | ```ruby 36 | SomeObject = Class.new 37 | ``` 38 | 39 | Every object has an `eigenclass`. 40 | 41 | ```ruby 42 | SomeObject.eigenclass #=> #> 43 | ``` 44 | 45 | The implementation of the `eigenclass` method is pretty simple. 46 | 47 | ```ruby 48 | class Object 49 | def eigenclass 50 | class << self 51 | self 52 | end 53 | end 54 | end 55 | ``` 56 | 57 | Evaluating code within an `eigenclass` lets us do some cool things like defining class level attributes. 58 | 59 | ```ruby 60 | SomeObject.eigenclass_eval do 61 | attr_accessor :example 62 | end 63 | 64 | SomeObject.example = :test 65 | SomeObject.example #=> :test 66 | ``` 67 | 68 | The convenience methods for defining class level methods makes this even easier. 69 | 70 | ```ruby 71 | class SomeObject 72 | eattr_accessor :example_accessor 73 | eattr_reader :example_reader 74 | eattr_writer :example_writer 75 | 76 | ealias_method :new_example_accessor, :example_accessor 77 | 78 | edefine_method(:example_class_method) do 79 | 1 + 1 80 | end 81 | end 82 | 83 | SomeObject.example_class_method #=> 2 84 | ``` 85 | 86 | When we `extend` modules, we're actually just calling `include` on an object's `eigenclass`. 87 | 88 | ```ruby 89 | SomeObject.eigenclass.included_modules #=> [Eigenclass, Kernel] 90 | 91 | Example = Module.new 92 | SomeObject.extend(Example) 93 | 94 | SomeObject.eigenclass.included_modules #=> [Example, Eigenclass, Kernel] 95 | ``` 96 | 97 | A convenience method for viewing an object's extended modules is available for us as well. 98 | 99 | ```ruby 100 | SomeObject.extended_modules #=> [Example, Eigenclass, Kernel] 101 | ``` 102 | 103 | Since all objects have an `eigenclass`, we can even define methods for a single _instance_ of a class. 104 | 105 | ```ruby 106 | object = SomeObject.new 107 | object.eattr_accessor :example 108 | object.example = :test 109 | object.example #=> :test 110 | 111 | other_object = SomeObject.new 112 | other_object.example #=> NoMethodError undefined method `example' for # 113 | ``` 114 | 115 | This is pretty incredible! We can hook in and inject behavior into *any and all* objects - **at runtime**! 116 | 117 | Ruby is like one big plugin framework - with an awesome standard library and amazing community! 118 | 119 | ## API 120 | 121 | [YARD Documentation](http://www.rubydoc.info/github/shuber/eigenclass/master) 122 | 123 | All methods defined by this gem are simple delegators to existing methods on the `eigenclass` object. The links below redirect to each corresponding method in the standard library documentation. 124 | 125 | * [ealias_method](http://ruby-doc.org/core-1.9.3/Module.html#method-i-alias_method) 126 | * [eattr_accessor](http://ruby-doc.org/core-1.9.3/Module.html#method-i-attr_accessor) 127 | * [eattr_reader](http://ruby-doc.org/core-1.9.3/Module.html#method-i-attr_reader) 128 | * [eattr_writer](http://ruby-doc.org/core-1.9.3/Module.html#method-i-attr_writer) 129 | * [edefine_method](http://ruby-doc.org/core-1.9.2/Object.html#method-i-define_singleton_method) 130 | * [eigenclass](http://ruby-doc.org/core-1.9.2/Object.html#method-i-singleton_class) 131 | * [eigenclass_eval](http://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-instance_eval) 132 | * [eigenclass_exec](http://ruby-doc.org/core-1.9.3/BasicObject.html#method-i-instance_exec) 133 | * [extended_modules](http://ruby-doc.org/core-1.9.3/Module.html#method-i-included_modules) 134 | 135 | 136 | ## Testing 137 | 138 | ``` 139 | bundle exec rspec 140 | ``` 141 | 142 | 143 | ## Contributing 144 | 145 | * Fork the project. 146 | * Make your feature addition or bug fix. 147 | * Add tests for it. This is important so I don't break it in a future version unintentionally. 148 | * Commit, do not mess with Rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) 149 | * Send me a pull request. Bonus points for topic branches. 150 | 151 | 152 | ## License 153 | 154 | [MIT](https://github.com/shuber/eigenclass/blob/master/LICENSE) - Copyright © 2008 Sean Huber 155 | -------------------------------------------------------------------------------- /eigenclass.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path('../lib/eigenclass/version', __FILE__) 2 | 3 | Gem::Specification.new do |s| 4 | s.author = 'Sean Huber' 5 | s.description = 'Eigenclasses (aka metaclasses or singleton classes) in ruby' 6 | s.email = 'github@shuber.io' 7 | s.extra_rdoc_files = %w(LICENSE) 8 | s.files = `git ls-files`.split("\n") 9 | s.homepage = 'https://github.com/shuber/eigenclass' 10 | s.license = 'MIT' 11 | s.name = 'eigenclass' 12 | s.rdoc_options = %w(--charset=UTF-8 --inline-source --line-numbers --main README.md) 13 | s.require_paths = %w(lib) 14 | s.summary = 'Eigenclasses in ruby' 15 | s.test_files = `git ls-files -- spec/*`.split("\n") 16 | s.version = Eigenclass::VERSION 17 | 18 | s.add_development_dependency 'rspec' 19 | end 20 | -------------------------------------------------------------------------------- /lib/eigenclass.rb: -------------------------------------------------------------------------------- 1 | require 'forwardable' 2 | require 'eigenclass/version' 3 | 4 | # Provides access to an object's {eigenclass} and defines 5 | # some convenient helper methods to interact with it. 6 | module Eigenclass 7 | extend Forwardable 8 | 9 | def_delegator :eigenclass, :alias_method, :ealias_method 10 | def_delegator :eigenclass, :attr_accessor, :eattr_accessor 11 | def_delegator :eigenclass, :attr_reader, :eattr_reader 12 | def_delegator :eigenclass, :attr_writer, :eattr_writer 13 | def_delegator :eigenclass, :define_method, :edefine_method 14 | def_delegator :eigenclass, :included_modules, :extended_modules 15 | def_delegator :eigenclass, :instance_eval, :eigenclass_eval 16 | def_delegator :eigenclass, :instance_exec, :eigenclass_exec 17 | 18 | # Alias of {Object#singleton_class} 19 | # @see http://ruby-doc.org/core-1.9.2/Object.html#method-i-singleton_class 20 | def eigenclass 21 | class << self 22 | self 23 | end 24 | end 25 | end 26 | 27 | Object.send(:include, Eigenclass) 28 | -------------------------------------------------------------------------------- /lib/eigenclass/version.rb: -------------------------------------------------------------------------------- 1 | module Eigenclass 2 | VERSION = '2.0.4' 3 | end 4 | -------------------------------------------------------------------------------- /spec/eigenclass_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../lib/eigenclass', __FILE__) 2 | 3 | RSpec.describe Eigenclass do 4 | subject { Object.new.extend(described_class) } 5 | 6 | it { should delegate_method(:ealias_method).to(:eigenclass).as(:alias_method) } 7 | it { should delegate_method(:eattr_accessor).to(:eigenclass).as(:attr_accessor) } 8 | it { should delegate_method(:eattr_reader).to(:eigenclass).as(:attr_reader) } 9 | it { should delegate_method(:eattr_writer).to(:eigenclass).as(:attr_writer) } 10 | it { should delegate_method(:edefine_method).to(:eigenclass).as(:define_method) } 11 | it { should delegate_method(:extended_modules).to(:eigenclass).as(:included_modules) } 12 | it { should delegate_method(:eigenclass_eval).to(:eigenclass).as(:instance_eval) } 13 | it { should delegate_method(:eigenclass_exec).to(:eigenclass).as(:instance_exec) } 14 | 15 | describe '#eigenclass' do 16 | it 'should return the eigenclass instance' do 17 | expected = class << subject; self end 18 | expect(subject.eigenclass).to eq(expected) 19 | end 20 | 21 | it 'should return the same object as singleton_class if that method exists' do 22 | if subject.respond_to?(:singleton_class) 23 | expect(subject.eigenclass).to eq(subject.singleton_class) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | if ENV['CODECLIMATE_REPO_TOKEN'] 3 | require 'codeclimate-test-reporter' 4 | CodeClimate::TestReporter.start 5 | else 6 | require 'simplecov' 7 | SimpleCov.start { add_filter('/vendor/bundle/') } 8 | end 9 | rescue LoadError 10 | # Ignore when testing with Ruby 1.8.7 11 | end 12 | 13 | RSpec.configure do |config| 14 | config.raise_errors_for_deprecations! 15 | end 16 | 17 | RSpec::Matchers.define(:delegate_method) do |method| 18 | chain(:to) { |to| @to = to } 19 | chain(:as) { |as| @as = as } 20 | 21 | match do |subject| 22 | target = double "target" 23 | expect(subject).to receive(@to).and_return(target) 24 | 25 | args = double "args" 26 | block = proc { } 27 | value = double "value" 28 | expect(target).to receive(@as).with(args, &block).and_return(value) 29 | 30 | result = subject.send(method, args, &block) 31 | expect(result).to eq(value) 32 | end 33 | end 34 | --------------------------------------------------------------------------------