adamantium-0.2.0/0000755000175100017510000000000013631463573013140 5ustar hackskhackskadamantium-0.2.0/spec/0000755000175100017510000000000013631463573014072 5ustar hackskhackskadamantium-0.2.0/spec/unit/0000755000175100017510000000000013631463573015051 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/0000755000175100017510000000000013631463573017171 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/transform_unless_spec.rb0000644000175100017510000000173713631463573024144 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' require File.expand_path('../fixtures/classes', __FILE__) describe Adamantium, '#transform_unless' do let(:object) { described_class.new } let(:described_class) { AdamantiumSpecs::Object } context 'when the condition is true' do let(:condition) { true } it 'does not evaluate the block' do expect { |block| object.transform_unless(condition, &block) } .not_to yield_control end it 'returns the object' do expect(object.transform_unless(condition) {}).to be(object) end end context 'when the condition is false' do let(:condition) { false } it 'evaluates the block' do expect { |block| object.transform_unless(condition, &block) } .to yield_control end it 'returns a copy of the object' do transformed = object.transform_unless(condition) {} expect(transformed).to_not be(object) expect(transformed).to eql(object) end end end adamantium-0.2.0/spec/unit/adamantium/transform_spec.rb0000644000175100017510000000171213631463573022544 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' require File.expand_path('../fixtures/classes', __FILE__) describe Adamantium, '#transform' do let(:object) { described_class.new } let(:described_class) { AdamantiumSpecs::Object } it 'returns a copy of the object' do transformed = object.transform {} expect(transformed).to_not be(object) expect(transformed).to eql(object) end it 'freezes the copy' do expect(object.transform {}).to be_frozen end it 'yields the copy to the block' do expect { |block| object.transform(&block) } .to yield_with_args(described_class) end it 'evaluates the block within the context of the copy' do copy = nil expect(object.transform { copy = self }).to be(copy) end it 'evaluates the block with an unfrozen copy' do frozen_in_block = nil expect { object.transform { frozen_in_block = frozen? } } .to change { frozen_in_block }.from(nil).to(false) end end adamantium-0.2.0/spec/unit/adamantium/mutable/0000755000175100017510000000000013631463573020622 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/mutable/frozen_predicate_spec.rb0000644000175100017510000000051013631463573025500 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::Mutable, '#frozen?' do subject { object.frozen? } let(:object) { class_under_test.new } let(:class_under_test) do Class.new do include Adamantium::Mutable end end it { should be(true) } it_should_behave_like 'an idempotent method' end adamantium-0.2.0/spec/unit/adamantium/mutable/freeze_spec.rb0000644000175100017510000000061013631463573023436 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::Mutable, '#freeze' do subject { object.freeze } let(:object) { class_under_test.new } let(:class_under_test) do Class.new do include Adamantium::Mutable end end it_should_behave_like 'a command method' it 'keeps object mutable' do subject object.instance_variable_set(:@foo, :bar) end end adamantium-0.2.0/spec/unit/adamantium/module_methods/0000755000175100017510000000000013631463573022201 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/module_methods/memoized_predicate_spec.rb0000644000175100017510000000070013631463573027366 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::ModuleMethods, '#memoized?' do let(:object) do Class.new do include Adamantium def foo end memoize :foo end end subject { object.memoized?(name) } context 'with memoized method' do let(:name) { :foo } it { should be(true) } end context 'with non memoized method' do let(:name) { :bar } it { should be(false) } end end adamantium-0.2.0/spec/unit/adamantium/module_methods/memoize_spec.rb0000644000175100017510000000644713631463573025220 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' require File.expand_path('../../fixtures/classes', __FILE__) shared_examples_for 'memoizes method' do it 'memoizes the instance method' do subject instance = object.new expect(instance.send(method)).to be(instance.send(method)) end end shared_examples_for 'wraps original method' do it 'creates a method with an arity of 0' do subject expect(object.new.method(method).arity).to be_zero end context 'when the initializer calls the memoized method' do it_should_behave_like 'memoizes method' before do method = self.method object.send(:define_method, :initialize) { send(method) } end it 'allows the memoized method to be called within the initializer' do subject expect { object.new }.to_not raise_error end end end describe Adamantium::ModuleMethods, '#memoize' do subject { object.memoize(method, options) } let(:options) { {} } let(:object) do Class.new(AdamantiumSpecs::Object) do def some_state Object.new end end end context 'on method with arguments' do let(:method) { :argumented } it 'should raise error' do expect { subject }.to raise_error(ArgumentError, "Cannot memoize #{object}#argumented, its arity is 1") end end context 'with :noop freezer option' do let(:method) { :some_state } let(:options) { { freezer: :noop } } it_should_behave_like 'a command method' it_should_behave_like 'wraps original method' it 'is still a public method' do should be_public_method_defined(method) end it 'creates a method that returns a non frozen value' do subject expect(object.new.send(method)).to_not be_frozen end end context 'memoized method that returns generated values' do let(:method) { :some_state } it_should_behave_like 'a command method' it_should_behave_like 'memoizes method' it_should_behave_like 'wraps original method' it 'creates a method that returns a frozen value' do subject expect(object.new.send(method)).to be_frozen end end context 'public method' do let(:method) { :public_method } it_should_behave_like 'a command method' it_should_behave_like 'memoizes method' it_should_behave_like 'wraps original method' it 'is still a public method' do should be_public_method_defined(method) end it 'creates a method that returns a frozen value' do subject expect(object.new.send(method)).to be_frozen end end context 'protected method' do let(:method) { :protected_method } it_should_behave_like 'a command method' it_should_behave_like 'memoizes method' it 'is still a protected method' do should be_protected_method_defined(method) end it 'creates a method that returns a frozen value' do subject expect(object.new.send(method)).to be_frozen end end context 'private method' do let(:method) { :private_method } it_should_behave_like 'a command method' it_should_behave_like 'memoizes method' it 'is still a private method' do should be_private_method_defined(method) end it 'creates a method that returns a frozen value' do subject expect(object.new.send(method)).to be_frozen end end end adamantium-0.2.0/spec/unit/adamantium/module_methods/included_spec.rb0000644000175100017510000000237213631463573025333 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::ModuleMethods, '#included' do subject { descendant.instance_exec(object) { |mod| include mod } } let(:object) { Module.new.extend(described_class) } let(:descendant) { Class.new } let(:superclass) { Module } before do # Prevent Module.included from being called through inheritance Adamantium.stub(:included) end around do |example| # Restore included method after each example superclass.class_eval do alias_method :original_included, :included example.call undef_method :included alias_method :included, :original_included end end it 'delegates to the superclass #included method' do # This is the most succinct approach I could think of to test whether the # superclass#included method is called. All of the built-in rspec helpers # did not seem to work for this. included = false superclass.class_eval { define_method(:included) { |_| included = true } } expect { subject }.to change { included }.from(false).to(true) end it 'includes Adamantium into the descendant' do subject expect(descendant.included_modules).to include(Adamantium) end end adamantium-0.2.0/spec/unit/adamantium/module_methods/freezer_spec.rb0000644000175100017510000000044713631463573025207 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::ModuleMethods, '#freezer' do let(:object) do Class.new do include Adamantium end end subject { object.freezer } it { should be(Adamantium::Freezer::Deep) } it_should_behave_like 'an idempotent method' end adamantium-0.2.0/spec/unit/adamantium/memoize_spec.rb0000644000175100017510000000171513631463573022201 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' require File.expand_path('../fixtures/classes', __FILE__) describe Adamantium, '#memoize' do subject { object.memoize(method => value) } let(:described_class) { Class.new(AdamantiumSpecs::Object) } let(:object) { described_class.new } let(:method) { :test } before do described_class.memoize(method) end context 'when the method is not memoized' do let(:value) { String.new } it 'sets the memoized value for the method to the value' do subject expect(object.send(method)).to be(value) end it_should_behave_like 'a command method' end context 'when the method is already memoized' do let(:value) { double } let(:original) { nil } before do object.memoize(method => original) end it 'raises an exception' do expect { subject }.to raise_error(ArgumentError) end end end adamantium-0.2.0/spec/unit/adamantium/freezer/0000755000175100017510000000000013631463573020633 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/freezer/flat/0000755000175100017510000000000013631463573021561 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/freezer/flat/class_methods/0000755000175100017510000000000013631463573024411 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/freezer/flat/class_methods/freeze_spec.rb0000644000175100017510000000046713631463573027237 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::Freezer::Flat, '.freeze' do subject { object.freeze(value) } let(:object) { described_class } let(:value) { double('Value') } it 'should freeze value' do value.should_receive(:freeze).and_return(value) should be(value) end end adamantium-0.2.0/spec/unit/adamantium/freezer/flat/class_methods/call_spec.rb0000644000175100017510000000217513631463573026670 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::Freezer::Flat, '.call' do subject { object.call(value) } let(:object) { described_class } context 'with a numeric value' do let(:value) { 1 } it { should equal(value) } end context 'with a true value' do let(:value) { true } it { should equal(value) } end context 'with a false value' do let(:value) { false } it { should equal(value) } end context 'with a nil value' do let(:value) { nil } it { should equal(value) } end context 'with a symbol value' do let(:value) { :symbol } it { should equal(value) } end context 'with a frozen value' do let(:value) { String.new.freeze } it { should equal(value) } end context 'with an unfrozen value' do let(:value) { String.new } it { should_not equal(value) } it { should be_instance_of(String) } it { should eql(value) } it { should be_frozen } end context 'with a composed value' do let(:value) { [String.new] } it 'does NOT freeze inner values' do expect(subject.first).to_not be_frozen end end end adamantium-0.2.0/spec/unit/adamantium/freezer/deep/0000755000175100017510000000000013631463573021550 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/freezer/deep/class_methods/0000755000175100017510000000000013631463573024400 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/freezer/deep/class_methods/freeze_spec.rb0000644000175100017510000000052013631463573027214 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::Freezer::Deep, '.freeze' do subject { object.freeze(value) } let(:object) { described_class } let(:value) { double('Value') } it 'should deep freeze value' do IceNine.should_receive(:deep_freeze!).with(value).and_return(value) should be(value) end end adamantium-0.2.0/spec/unit/adamantium/freezer/deep/class_methods/call_spec.rb0000644000175100017510000000330013631463573026646 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::Freezer::Deep, '.call' do subject { object.call(value) } let(:object) { described_class } context 'with a numeric value' do let(:value) { 1 } it { should equal(value) } end context 'with a true value' do let(:value) { true } it { should equal(value) } end context 'with a false value' do let(:value) { false } it { should equal(value) } end context 'with a nil value' do let(:value) { nil } it { should equal(value) } end context 'with a symbol value' do let(:value) { :symbol } it { should equal(value) } end context 'with a frozen value' do let(:value) { String.new.freeze } it { should equal(value) } end context 'with a method value' do let(:value) { Object.method(:to_s) } it { should equal(value) } it { should_not be_frozen } end context 'with a unbound method value' do let(:value) { Object.instance_method(:to_s) } it { should equal(value) } it { should_not be_frozen } end context 'with a module value' do let(:value) { Module.new } it { should equal(value) } it { should_not be_frozen } end context 'with a class value' do let(:value) { Class.new } it { should equal(value) } it { should_not be_frozen } end context 'with an unfrozen value' do let(:value) { String.new } it { should_not equal(value) } it { should be_instance_of(String) } it { should eql(value) } it { should be_frozen } end context 'with a composed value' do let(:value) { [String.new] } it 'does freeze inner values' do expect(subject.first).to be_frozen end end end adamantium-0.2.0/spec/unit/adamantium/freezer/class_methods/0000755000175100017510000000000013631463573023463 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/freezer/class_methods/parse_spec.rb0000644000175100017510000000142513631463573026136 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::Freezer, '.parse' do subject { object.parse(options) } let(:object) { described_class } let(:freezer) { double('Freezer') } context 'with empty options' do let(:options) { {} } it { should be(nil) } end context 'with :freezer key' do let(:options) { { freezer: name } } let(:name) { double('Name') } it 'should get freezer' do described_class.should_receive(:get).with(name).and_return(freezer) should be(freezer) end end context 'with any other key' do let(:options) { { other: :key } } it 'should raise error' do expect { subject }.to raise_error(described_class::OptionError, 'Unknown option key(s) for memoizer [:other]') end end end adamantium-0.2.0/spec/unit/adamantium/freezer/class_methods/get_spec.rb0000644000175100017510000000127113631463573025602 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::Freezer, '.get' do subject { object.get(name) } let(:object) { described_class } context 'with :deep' do let(:name) { :deep } it { should be(Adamantium::Freezer::Deep) } end context 'with :noop' do let(:name) { :noop } it { should be(Adamantium::Freezer::Noop) } end context 'with :flat' do let(:name) { :flat } it { should be(Adamantium::Freezer::Flat) } end context 'with unknown name' do let(:name) { :other } it 'should raise error' do expect { subject }.to raise_error(described_class::UnknownFreezerError, 'Freezer with name :other is unknown') end end end adamantium-0.2.0/spec/unit/adamantium/freeze_spec.rb0000644000175100017510000000145113631463573022011 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' require File.expand_path('../fixtures/classes', __FILE__) describe Adamantium, '#freeze' do subject { object.freeze } let(:described_class) { Class.new(AdamantiumSpecs::Object) } before do described_class.memoize(:test) end context 'with an unfrozen object' do let(:object) { described_class.allocate } it_should_behave_like 'a command method' it 'freezes the object' do expect { subject }.to change(object, :frozen?) .from(false) .to(true) end end context 'with a frozen object' do let(:object) { described_class.new } it_should_behave_like 'a command method' it 'does not change the frozen state of the object' do expect { subject }.to_not change(object, :frozen?) end end end adamantium-0.2.0/spec/unit/adamantium/flat/0000755000175100017510000000000013631463573020117 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/flat/freezer_spec.rb0000644000175100017510000000045413631463573023123 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::ClassMethods, '#freezer' do let(:object) do Class.new do include Adamantium::Flat end end subject { object.freezer } it { should be(Adamantium::Freezer::Flat) } it_should_behave_like 'an idempotent method' end adamantium-0.2.0/spec/unit/adamantium/flat/class_methods/0000755000175100017510000000000013631463573022747 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/flat/class_methods/included_spec.rb0000644000175100017510000000053313631463573026076 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium::Flat, '.included' do subject do Class.new do include Adamantium::Flat end end its(:ancestors) { should include(Adamantium) } it 'extends class with Adamantium::Flat' do expect(subject.singleton_class.included_modules).to include(described_class) end end adamantium-0.2.0/spec/unit/adamantium/fixtures/0000755000175100017510000000000013631463573021042 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/fixtures/classes.rb0000644000175100017510000000071213631463573023024 0ustar hackskhacksk# encoding: utf-8 module AdamantiumSpecs class Object include Adamantium public :transform, :transform_unless def argumented(foo) end def test 'test' end def public_method caller end def eql?(other) kind_of?(other.class) end protected def protected_method caller end private def private_method caller end end # class Object end # module AdamantiumSpecs adamantium-0.2.0/spec/unit/adamantium/dup_spec.rb0000644000175100017510000000045013631463573021317 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' require File.expand_path('../fixtures/classes', __FILE__) describe Adamantium, '#dup' do subject { object.dup } let(:described_class) { AdamantiumSpecs::Object } let(:object) { described_class.new } it { should equal(object) } end adamantium-0.2.0/spec/unit/adamantium/class_methods/0000755000175100017510000000000013631463573022021 5ustar hackskhackskadamantium-0.2.0/spec/unit/adamantium/class_methods/new_spec.rb0000644000175100017510000000044013631463573024147 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' require File.expand_path('../../fixtures/classes', __FILE__) describe Adamantium::ClassMethods, '#new' do subject { object.new } let(:object) { AdamantiumSpecs::Object } it { should be_instance_of(object) } it { should be_frozen } end adamantium-0.2.0/spec/support/0000755000175100017510000000000013631463573015606 5ustar hackskhackskadamantium-0.2.0/spec/support/config_alias.rb0000644000175100017510000000012513631463573020547 0ustar hackskhacksk# encoding: utf-8 require 'rbconfig' ::Config = RbConfig unless defined?(::Config) adamantium-0.2.0/spec/spec_helper.rb0000644000175100017510000000106513631463573016712 0ustar hackskhacksk# encoding: utf-8 require 'devtools/spec_helper' if ENV['COVERAGE'] == 'true' require 'simplecov' require 'coveralls' SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter ] SimpleCov.start do command_name 'spec:unit' add_filter 'config' add_filter 'spec' add_filter 'vendor' minimum_coverage 100 end end require 'adamantium' RSpec.configure do |config| config.expect_with :rspec do |expect_with| expect_with.syntax = :expect end end adamantium-0.2.0/spec/rcov.opts0000644000175100017510000000015713631463573015755 0ustar hackskhacksk--exclude-only "spec/,^/" --sort coverage --callsites --xrefs --profile --text-summary --failure-threshold 100 adamantium-0.2.0/spec/integration/0000755000175100017510000000000013631463573016415 5ustar hackskhackskadamantium-0.2.0/spec/integration/adamantium_spec.rb0000644000175100017510000000240313631463573022073 0ustar hackskhacksk# encoding: utf-8 require 'spec_helper' describe Adamantium do let(:class_under_test) do mixin = self.mixin Class.new do include mixin def initialize @attribute = Object.new end attr_reader :attribute def memoized [Object.new] end memoize :memoized end end context 'inherited' do let(:mixin) { Adamantium::Flat } subject { Class.new(class_under_test).new } it 'should return memoized value' do subject.memoized end end context 'default' do let(:mixin) { Adamantium } subject { class_under_test.new } it 'should deep freeze instance and attributes' do should be_frozen expect(subject.attribute).to be_frozen end it 'should deep freeze memoized values' do expect(subject.memoized).to be_frozen expect(subject.memoized[0]).to be_frozen end end context 'flat' do let(:mixin) { Adamantium::Flat } subject { class_under_test.new } it 'should freeze only instance' do should be_frozen expect(subject.attribute).to_not be_frozen end it 'should flat freeze memoized values' do expect(subject.memoized).to be_frozen expect(subject.memoized[0]).to_not be_frozen end end end adamantium-0.2.0/lib/0000755000175100017510000000000013631463573013706 5ustar hackskhackskadamantium-0.2.0/lib/adamantium/0000755000175100017510000000000013631463573016026 5ustar hackskhackskadamantium-0.2.0/lib/adamantium/version.rb0000644000175100017510000000014313631463573020036 0ustar hackskhacksk# encoding: utf-8 module Adamantium # Gem version VERSION = '0.2.0'.freeze end # Adamantium adamantium-0.2.0/lib/adamantium/mutable.rb0000644000175100017510000000205313631463573020004 0ustar hackskhacksk# encoding: utf-8 module Adamantium # Module fake frozen object # # This behavior sometimes is needed when a mutable # object needs to be referenced in an inmutable object tree. # # If you have to use `memoize :foo, freezer: :noop` to often you might # want to include this module into your class. # # Use wisely! A rule of thumb only a tiny fraction of your objects # typically deserves this. # module Mutable # Noop freezer # # @example # class DoesNotGetFrozen # include Adamantium::Mutable # end # # instance = DoesNotGetFrozen # instance.freeze # => instance # # @return [self] # # @api public # def freeze self end # Test if object is frozen # # @example # class DoesNotGetFrozen # include Adamantium::Mutable # end # # instance = DoesNotGetFrozen # instance.frozen? # => true # # @return [true] # # @api public # def frozen? true end end # Mutable end # Adamantium adamantium-0.2.0/lib/adamantium/module_methods.rb0000644000175100017510000000261613631463573021370 0ustar hackskhacksk# encoding: utf-8 module Adamantium # Methods mixed in to adamantium modules module ModuleMethods # Return default deep freezer # # @return [Freezer::Deep] # # @api private def freezer Freezer::Deep end # Memoize a list of methods # # @example # memoize :hash # # @param [Array<#to_s>] methods # a list of methods to memoize # # @return [self] # # @api public def memoize(*methods) options = methods.last.kind_of?(Hash) ? methods.pop : {} method_freezer = Freezer.parse(options) || freezer methods.each { |method| memoize_method(method, method_freezer) } self end private # Hook called when module is included # # @param [Module] descendant # the module including ModuleMethods # # @return [self] # # @api private def included(descendant) super descendant.module_eval { include Adamantium } end # Memoize the named method # # @param [Symbol] method_name # a method name to memoize # @param [#call] freezer # a callable object to freeze the value # # @return [undefined] # # @api private def memoize_method(method_name, freezer) memoized_methods[method_name] = Memoizable::MethodBuilder .new(self, method_name, freezer).call end end # ModuleMethods end # Adamantium adamantium-0.2.0/lib/adamantium/freezer.rb0000644000175100017510000000561113631463573020020 0ustar hackskhacksk# encoding: utf-8 module Adamantium # Abstract base class for freezers # # TODO: Use dkubb/abstract_class? # # Better pattern for singleton inheritance/shared code class Freezer private_class_method :new # Attempt to freeze an object # # @example using a value object # Adamantium.freeze_object(12345) # => noop # # @example using a normal object # Adamantium.freeze_object({}) # => duplicate & freeze object # # @param [Object] object # the object to freeze # # @return [Object] # if supported, the frozen object, otherwise the object directly # # @api public def self.call(object) case object when Numeric, TrueClass, FalseClass, NilClass, Symbol, Class, Module, UnboundMethod, Method object else freeze_value(object) end end private_class_method :call # Returns a frozen value # # @param [Object] value # a value to freeze # # @return [Object] # if frozen, the value directly, otherwise a frozen copy of the value # # @api private def self.freeze_value(value) value.frozen? ? value : freeze(value.dup) end private_class_method :freeze_value # Freezer that does not deep freeze class Flat < self # Freeze value # # @param [Object] value # # @return [undefined] # # @api private def self.freeze(value) value.freeze end public_class_method :call end # Freezer that does deep freeze class Deep < self # Deep freeze value # # @param [Object] value # # @return [undefined] # # @api private def self.freeze(value) IceNine.deep_freeze!(value) end public_class_method :call end Noop = ->(object) { object }.freeze # Error raised when freezer cannot be found class UnknownFreezerError < RuntimeError; end # Error raised when memoizer options contain unknown keys class OptionError < RuntimeError; end @freezers = { noop: Noop, deep: Deep, flat: Flat, }.freeze # Return freezer for name # # @param [Symbol] name # a freezer name # # @return [#call] # # @api private def self.get(name) @freezers.fetch(name) do fail UnknownFreezerError, "Freezer with name #{name.inspect} is unknown" end end # Parse freezer options # # @param [Hash] options # an options hash # # @return [#call] # if freezer option was present # # @return [nil] # otherwise # # @api private # def self.parse(options) keys = options.keys - [:freezer] unless keys.empty? fail OptionError, "Unknown option key(s) for memoizer #{keys.inspect}" end get(options.fetch(:freezer)) if options.key?(:freezer) end end end adamantium-0.2.0/lib/adamantium/class_methods.rb0000644000175100017510000000055313631463573021206 0ustar hackskhacksk# encoding: utf-8 module Adamantium # Methods mixed in to adamantium classes module ClassMethods # Instantiate a new frozen object # # @example # object = AdamantiumClass.new # object is frozen # # @return [Object] # # @api public def new(*) freezer.freeze(super) end end # ClassMethods end # Adamantium adamantium-0.2.0/lib/adamantium.rb0000644000175100017510000000371213631463573016356 0ustar hackskhacksk# encoding: utf-8 require 'ice_nine' require 'memoizable' # Allows objects to be made immutable module Adamantium # Defaults to less strict defaults module Flat # Return flat freezer # # @return [Freezer::Flat] # # @api private def freezer Freezer::Flat end # Hook called when module is included # # @param [Class,Module] descendant # # @return [undefined] # # @api private def self.included(descendant) super descendant.instance_exec(self) do |mod| include Adamantium extend mod end end private_class_method :included end # Flat # Hook called when module is included # # @param [Module] descendant # the module or class including Adamantium # # @return [self] # # @api private def self.included(descendant) super descendant.class_eval do include Memoizable extend ModuleMethods extend ClassMethods if kind_of?(Class) end end private_class_method :included # Alias to the original dup method # # @return [Object] # # @api private alias_method :__adamantium_dup__, :dup private :__adamantium_dup__ # A noop #dup for immutable objects # # @example # object.dup # => self # # @return [self] # # @api public def dup self end protected # Transform the object with the provided block # # @return [Object] # # @api public def transform(&block) copy = __adamantium_dup__ copy.instance_eval(&block) self.class.freezer.freeze(copy) end # Transform the object with the provided block if the condition is false # # @param [Boolean] condition # # @return [Object] # # @api public def transform_unless(condition, &block) condition ? self : transform(&block) end end # Adamantium require 'adamantium/module_methods' require 'adamantium/class_methods' require 'adamantium/freezer' require 'adamantium/mutable' require 'adamantium/version' adamantium-0.2.0/config/0000755000175100017510000000000013631463573014405 5ustar hackskhackskadamantium-0.2.0/config/yardstick.yml0000644000175100017510000000002313631463573017120 0ustar hackskhacksk--- threshold: 100 adamantium-0.2.0/config/rubocop.yml0000644000175100017510000000272013631463573016602 0ustar hackskhackskinherit_from: ../.rubocop.yml # Avoid parameter lists longer than five parameters. ParameterLists: Max: 3 CountKeywordArgs: true # Avoid more than `Max` levels of nesting. BlockNesting: Max: 3 # Align with the style guide. CollectionMethods: PreferredMethods: collect: 'map' inject: 'reduce' find: 'detect' find_all: 'select' # Do not force public/protected/private keyword to be indented at the same # level as the def keyword. My personal preference is to outdent these keywords # because I think when scanning code it makes it easier to identify the # sections of code and visually separate them. When the keyword is at the same # level I think it sort of blends in with the def keywords and makes it harder # to scan the code and see where the sections are. AccessModifierIndentation: Enabled: false # Limit line length LineLength: Max: 116 # TODO: lower to 79 # Disable documentation checking until a class needs to be documented once Documentation: Enabled: false # Do not favor modifier if/unless usage when you have a single-line body IfUnlessModifier: Enabled: false # Allow case equality operator (in limited use within the specs) CaseEquality: Enabled: false # Constants do not always have to use SCREAMING_SNAKE_CASE ConstantName: Enabled: false # Not all trivial readers/writers can be defined with attr_* methods TrivialAccessors: Enabled: false # Allow empty lines around body EmptyLinesAroundBody: Enabled: false adamantium-0.2.0/config/roodi.yml0000644000175100017510000000171313631463573016246 0ustar hackskhacksk--- AbcMetricMethodCheck: { score: 10.78 } AssignmentInConditionalCheck: { } CaseMissingElseCheck: { } ClassLineCountCheck: { line_count: 293 } ClassNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' } ClassVariableCheck: { } CyclomaticComplexityBlockCheck: { complexity: 2 } CyclomaticComplexityMethodCheck: { complexity: 4 } EmptyRescueBodyCheck: { } ForLoopCheck: { } # TODO: decrease line_count to 5 to 10 MethodLineCountCheck: { line_count: 14 } MethodNameCheck: { pattern: !ruby/regexp '/\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|[+*&|-])\z/' } ModuleLineCountCheck: { line_count: 295 } ModuleNameCheck: { pattern: !ruby/regexp '/\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/' } # TODO: decrease parameter_count to 2 or less ParameterNumberCheck: { parameter_count: 3 } adamantium-0.2.0/config/reek.yml0000644000175100017510000000361213631463573016060 0ustar hackskhacksk--- Attribute: enabled: true exclude: [] BooleanParameter: enabled: true exclude: [] ClassVariable: enabled: true exclude: [] ControlParameter: enabled: true exclude: - Adamantium#transform_unless DataClump: enabled: true exclude: [] max_copies: 2 min_clump_size: 2 DuplicateMethodCall: enabled: true exclude: [] max_calls: 1 allow_calls: [] FeatureEnvy: enabled: true exclude: [] IrresponsibleModule: enabled: true exclude: [] LongParameterList: enabled: true exclude: [] max_params: 2 overrides: initialize: max_params: 3 LongYieldList: enabled: true exclude: [] max_params: 2 NestedIterators: enabled: true exclude: - Adamantium::ModuleMethods#define_memoize_method max_allowed_nesting: 1 ignore_iterators: [] NilCheck: enabled: true exclude: [] RepeatedConditional: enabled: true exclude: [] max_ifs: 1 TooManyInstanceVariables: enabled: true exclude: [] max_instance_variables: 3 TooManyMethods: enabled: true exclude: [] max_methods: 10 TooManyStatements: enabled: true exclude: - Adamantium::ModuleMethods#define_memoize_method - Adamantium::ModuleMethods#memoize_method - each max_statements: 5 UncommunicativeMethodName: enabled: true exclude: [] reject: - !ruby/regexp /^[a-z]$/ - !ruby/regexp /[0-9]$/ - !ruby/regexp /[A-Z]/ accept: [] UncommunicativeModuleName: enabled: true exclude: [] reject: - !ruby/regexp /^.$/ - !ruby/regexp /[0-9]$/ accept: [] UncommunicativeParameterName: enabled: true exclude: [] reject: - !ruby/regexp /^.$/ - !ruby/regexp /[0-9]$/ - !ruby/regexp /[A-Z]/ accept: [] UncommunicativeVariableName: enabled: true exclude: [] reject: - !ruby/regexp /^.$/ - !ruby/regexp /[0-9]$/ - !ruby/regexp /[A-Z]/ accept: [] UnusedParameters: enabled: true exclude: [] UtilityFunction: enabled: true exclude: [] max_helper_calls: 0 adamantium-0.2.0/config/mutant.yml0000644000175100017510000000005313631463573016436 0ustar hackskhacksk--- name: adamantium namespace: Adamantium adamantium-0.2.0/config/heckle.yml0000644000175100017510000000005613631463573016364 0ustar hackskhacksk--- library: adamantium namespace: Adamantium adamantium-0.2.0/config/flog.yml0000644000175100017510000000002413631463573016053 0ustar hackskhacksk--- threshold: 15.0 adamantium-0.2.0/config/flay.yml0000644000175100017510000000004113631463573016056 0ustar hackskhacksk--- threshold: 4 total_score: 28 adamantium-0.2.0/config/devtools.yml0000644000175100017510000000003313631463573016763 0ustar hackskhacksk--- unit_test_timeout: 0.1 adamantium-0.2.0/adamantium.gemspec0000644000175100017510000000157313631463573016633 0ustar hackskhacksk# encoding: utf-8 require File.expand_path('../lib/adamantium/version', __FILE__) Gem::Specification.new do |gem| gem.name = 'adamantium' gem.version = Adamantium::VERSION.dup gem.authors = ['Dan Kubb', 'Markus Schirp'] gem.email = %w[dan.kubb@gmail.com mbj@seonic.net] gem.description = 'Immutable extensions to objects' gem.summary = gem.description gem.homepage = 'https://github.com/dkubb/adamantium' gem.license = 'MIT' gem.require_paths = %w[lib] gem.files = `git ls-files`.split("\n") gem.test_files = `git ls-files -- spec/{unit,integration}`.split("\n") gem.extra_rdoc_files = %w[LICENSE README.md CONTRIBUTING.md TODO] gem.add_runtime_dependency('ice_nine', '~> 0.11.0') gem.add_runtime_dependency('memoizable', '~> 0.4.0') gem.add_development_dependency('bundler', '~> 1.5', '>= 1.5.2') end adamantium-0.2.0/TODO0000644000175100017510000000005413631463573013627 0ustar hackskhacksk* Update #hash to be memoized automatically adamantium-0.2.0/Rakefile0000644000175100017510000000010013631463573014574 0ustar hackskhacksk# encoding: utf-8 require 'devtools' Devtools.init_rake_tasks adamantium-0.2.0/README.md0000644000175100017510000000631313631463573014422 0ustar hackskhackskadamantium ========== Create immutable objects with ease. [![Gem Version](https://badge.fury.io/rb/adamantium.png)][gem] [![Build Status](https://secure.travis-ci.org/dkubb/adamantium.png?branch=master)][travis] [![Dependency Status](https://gemnasium.com/dkubb/adamantium.png)][gemnasium] [![Code Climate](https://codeclimate.com/github/dkubb/adamantium.png)][codeclimate] [![Coverage Status](https://coveralls.io/repos/dkubb/adamantium/badge.png?branch=master)][coveralls] [gem]: https://rubygems.org/gems/adamantium [travis]: https://travis-ci.org/dkubb/adamantium [gemnasium]: https://gemnasium.com/dkubb/adamantium [codeclimate]: https://codeclimate.com/github/dkubb/adamantium [coveralls]: https://coveralls.io/r/dkubb/adamantium This is a small standalone gem featuring a module ripped out from [axiom](https://github.com/dkubb/axiom). It allows you to make objects immutable in a simple, unobtrusive way. Examples -------- ``` ruby require 'adamantium' require 'securerandom' class Example # Inclusion of Adamantium defaults to deep freeze behavior # of constructor and memoizer include Adamantium # Instance and attributes (ivars) are frozen per default # Example: # # object = Example.new # object.frozen? # => true # object.attribute.frozen? # => true # def initialize @attribute = "foo bar" end attr_reader :attribute # Memoized method with deeply frozen value (default) # Example: # # object = Example.new # object.random => ["abcdef"] # object.random => ["abcdef"] # object.random.frozen? => true # object.random[0].frozen? => true # def random [SecureRandom.hex(6)] end memoize :random # Memoized method with non frozen value # Example: # # object = Example.new # object.buffer # => # object.buffer # => # object.buffer.frozen? # => false # def buffer StringIO.new end memoize :buffer, freezer: :noop # Memoized method with shallow frozen value # Example: # # object = Example.new # object.random2 => ["abcdef"] # object.random2 => ["abcdef"] # object.random2.frozen? => true # object.random2[0].frozen? => false # def random2 [SecureRandom.hex(6)] end memoize :random2, freezer: :flat end class FlatExample # Inclusion of Adamantium::Flat defaults to shallow frozen # behavior for memoizer and constructor include Adamantium::Flat # Instance is frozen but attribute is not # object = FlatExample.new # object.frozen? # => true # object.attribute.frozen? # => false def initialize @attribute = "foo bar" end attr_reader :attribute # Memoized method with flat frozen value (default with Adamantium::Flat) # Example: # # object = Example.new # object.random => ["abcdef"] # object.random => ["abcdef"] # object.random.frozen? => true # object.random[0].frozen? => false # def random [SecureRandom.hex(6)] end memoize :random end ``` Credits ------- * Dan Kubb ([dkubb](https://github.com/dkubb)) * Markus Schirp ([mbj](https://github.com/mbj)) Contributing ------------ See [CONTRIBUTING.md](CONTRIBUTING.md) for details. Copyright --------- Copyright © 2012-2013 Dan Kubb. See LICENSE for details. adamantium-0.2.0/LICENSE0000644000175100017510000000211613631463573014145 0ustar hackskhackskCopyright (c) 2009-2012 Dan Kubb Copyright (c) 2012 Markus Schirp (packaging) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. adamantium-0.2.0/Guardfile0000644000175100017510000000233713631463573014772 0ustar hackskhacksk# encoding: utf-8 guard :bundler do watch('Gemfile') watch('Gemfile.lock') watch(%w{.+.gemspec\z}) end guard :rspec, cli: File.read('.rspec').split.join(' '), keep_failed: false do # Run all specs if configuration is modified watch('.rspec') { 'spec' } watch('Guardfile') { 'spec' } watch('Gemfile.lock') { 'spec' } watch('spec/spec_helper.rb') { 'spec' } # Run all specs if supporting files files are modified watch(%r{\Aspec/(?:fixtures|lib|support|shared)/.+\.rb\z}) { 'spec' } # Run unit specs if associated app or lib code is modified watch(%r{\Alib/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}*"] } watch(%r{\Alib/(.+)/support/(.+)\.rb\z}) { |m| Dir["spec/unit/#{m[1]}/#{m[2]}*"] } watch("lib/#{File.basename(File.expand_path('../', __FILE__))}.rb") { 'spec' } # Run a spec if it is modified watch(%r{\Aspec/(?:unit|integration)/.+_spec\.rb\z}) end guard :rubocop, cli: %w[--config config/rubocop.yml] do watch(%r{.+\.(?:rb|rake)\z}) watch(%r{\Aconfig/rubocop\.yml\z}) { |m| File.dirname(m[0]) } watch(%r{(?:.+/)?\.rubocop\.yml\z}) { |m| File.dirname(m[0]) } end adamantium-0.2.0/Gemfile.devtools0000644000175100017510000000301513631463573016270 0ustar hackskhacksk# encoding: utf-8 group :development do gem 'rake', '~> 10.1.0' gem 'rspec', '~> 2.14.1' gem 'yard', '~> 0.8.7' platform :rbx do gem 'rubysl-singleton', '~> 2.0.0' end end group :yard do gem 'kramdown', '~> 1.3.0' end group :guard do gem 'guard', '~> 2.3.0' gem 'guard-bundler', '~> 2.0.0' gem 'guard-rspec', '~> 4.2.0' gem 'guard-rubocop', '~> 1.0.0' # file system change event handling gem 'listen', '~> 2.4.0' gem 'rb-fchange', '~> 0.0.6', require: false gem 'rb-fsevent', '~> 0.9.3', require: false gem 'rb-inotify', '~> 0.9.0', require: false # notification handling gem 'libnotify', '~> 0.8.0', require: false gem 'rb-notifu', '~> 0.0.4', require: false gem 'terminal-notifier-guard', '~> 1.5.3', require: false end group :metrics do gem 'coveralls', '~> 0.7.0' gem 'flay', '~> 2.4.0' gem 'flog', '~> 4.2.0' gem 'reek', '~> 1.3.2' gem 'rubocop', '~> 0.16.0' gem 'simplecov', '~> 0.8.2' gem 'yardstick', '~> 0.9.9' platforms :mri do gem 'mutant', '~> 0.3.4' end platforms :ruby_19, :ruby_20 do gem 'yard-spellcheck', '~> 0.1.5' end platform :rbx do gem 'json', '~> 1.8.1' gem 'racc', '~> 1.4' gem 'rubysl-logger', '~> 2.0.0' gem 'rubysl-open-uri', '~> 2.0.0' gem 'rubysl-prettyprint', '~> 2.0.2' end end group :benchmarks do gem 'rbench', '~> 0.2.3' end platform :jruby do group :jruby do gem 'jruby-openssl', '~> 0.8.5' end end adamantium-0.2.0/Gemfile0000644000175100017510000000027513631463573014437 0ustar hackskhacksk# encoding: utf-8 source 'https://rubygems.org' gemspec group :development, :test do gem 'devtools', git: 'https://github.com/rom-rb/devtools.git' end eval_gemfile 'Gemfile.devtools' adamantium-0.2.0/CONTRIBUTING.md0000644000175100017510000000202413631463573015367 0ustar hackskhackskContributing ------------ * If you want your code merged into the mainline, please discuss the proposed changes with me before doing any work on it. This library is still in early development, and the direction it is going may not always be clear. Some features may not be appropriate yet, may need to be deferred until later when the foundation for them is laid, or may be more applicable in a plugin. * Fork the project. * Make your feature addition or bug fix. * Follow this [style guide](https://github.com/dkubb/styleguide). * Add specs for it. This is important so I don't break it in a future version unintentionally. Tests must cover all branches within the code, and code must be fully covered. * 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) * Run "rake ci". This must pass and not show any regressions in the metrics for the code to be merged. * Send me a pull request. Bonus points for topic branches. adamantium-0.2.0/.travis.yml0000644000175100017510000000133613631463573015254 0ustar hackskhacksklanguage: ruby before_install: gem install bundler bundler_args: --without yard guard benchmarks script: "bundle exec rake ci:metrics" rvm: - 1.9.3 - 2.0.0 - 2.1.0 - ruby-head - rbx matrix: include: - rvm: jruby-19mode env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov - rvm: jruby-20mode env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov - rvm: jruby-21mode env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov - rvm: jruby-head env: JRUBY_OPTS="$JRUBY_OPTS --debug" # for simplecov allow_failures: - rvm: 2.1.0 # buggy runtime fast_finish: true notifications: irc: channels: - irc.freenode.org#rom-rb on_success: never on_failure: change adamantium-0.2.0/.ruby-gemset0000644000175100017510000000001313631463573015376 0ustar hackskhackskadamantium adamantium-0.2.0/.rubocop.yml0000644000175100017510000000013413631463573015410 0ustar hackskhackskAllCops: Includes: - 'Gemfile' Excludes: - 'Gemfile.devtools' - 'vendor/**' adamantium-0.2.0/.rspec0000644000175100017510000000007613631463573014260 0ustar hackskhacksk--color --format progress --profile --warnings --order random adamantium-0.2.0/.gitignore0000644000175100017510000000041213631463573015125 0ustar hackskhacksk## MAC OS .DS_Store ## TEXTMATE *.tmproj tmtags ## EMACS *~ \#* .\#* ## VIM *.sw[op] ## Rubinius *.rbc .rbx ## PROJECT::GENERAL *.gem coverage profiling turbulence rdoc pkg tmp doc log .yardoc measurements ## BUNDLER .bundle Gemfile.lock ## PROJECT::SPECIFIC