anima-0.3.1/0000755000175000017500000000000013715565132014030 5ustar debbiecocoadebbiecocoaanima-0.3.1/README.md0000644000175000017500000000650113715565132015311 0ustar debbiecocoadebbiecocoaanima ===== [![Build Status](https://secure.travis-ci.org/mbj/anima.png?branch=master)](http://travis-ci.org/mbj/anima) [![Dependency Status](https://gemnasium.com/mbj/anima.png)](https://gemnasium.com/mbj/anima) [![Code Climate](https://codeclimate.com/github/mbj/anima.png)](https://codeclimate.com/github/mbj/anima) [![Gem Version](https://img.shields.io/gem/v/anima.svg)](https://rubygems.org/gems/anima) Simple library to declare read only attributes on value-objects that are initialized via attributes hash. Installation ------------ Install the gem `anima` via your preferred method. Examples -------- ```ruby require 'anima' # Definition class Person include Anima.new(:salutation, :firstname, :lastname) end # Every day operation a = Person.new( salutation: 'Mr', firstname: 'Markus', lastname: 'Schirp' ) # Returns expected values a.salutation # => "Mr" a.firstname # => "Markus" a.lastname # => "Schirp" a.frozen? # => false b = Person.new( salutation: 'Mr', firstname: 'John', lastname: 'Doe' ) c = Person.new( salutation: 'Mr', firstname: 'Markus', lastname: 'Schirp' ) # Equality based on attributes a == b # => false a.eql?(b) # => false a.equal?(b) # => false a == c # => true a.eql?(c) # => true a.equal?(c) # => false # Functional-style updates d = b.with( salutation: 'Mrs', firstname: 'Sue', ) # It returns copies, no inplace modification d.equal?(b) # => false # Hash representation d.to_h # => { salutation: 'Mrs', firstname: 'Sue', lastname: 'Doe' } # Disallows initialization with incompatible attributes Person.new( # :saluatation key missing "firstname" => "Markus", # does NOT coerce this by intention :lastname => "Schirp" ) # raises Anima::Error with message "Person attributes missing: [:salutation, :firstname], unknown: ["firstname"] ``` Credits ------- * Markus Schirp Contributing ------------- * Fork the project. * Make your feature addition or bug fix. * Add tests for it. This is important so I don't break it in a future version unintentionally. * Commit, do not mess with Rakefile or version (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) * Send me a pull request. Bonus points for topic branches. License ------- Copyright (c) 2013 Markus Schirp 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. anima-0.3.1/.gitignore0000644000175000017500000000005513715565132016020 0ustar debbiecocoadebbiecocoa/.rspec /Gemfile.lock /.rbx /.bundle /vendor anima-0.3.1/config/0000755000175000017500000000000013715565132015275 5ustar debbiecocoadebbiecocoaanima-0.3.1/config/reek.yml0000644000175000017500000000357013715565132016753 0ustar debbiecocoadebbiecocoa--- detectors: UncommunicativeParameterName: accept: [] exclude: [] enabled: true reject: - '/^.$/' - '/[0-9]$/' - '/[A-Z]/' TooManyMethods: max_methods: 9 exclude: [] enabled: true UncommunicativeMethodName: accept: [] exclude: [] enabled: true reject: - '/^[a-z]$/' - '/[0-9]$/' - '/[A-Z]/' LongParameterList: max_params: 2 # TODO: decrease max_params to 2 exclude: - Anima::Error#initialize # 3 params enabled: true overrides: {} FeatureEnvy: exclude: - Anima#attributes_hash enabled: true ClassVariable: exclude: [] enabled: true BooleanParameter: exclude: [] enabled: true # Buggy like hell IrresponsibleModule: exclude: [] enabled: false UncommunicativeModuleName: accept: [] exclude: [] enabled: true reject: - '/^.$/' - '/[0-9]$/' NestedIterators: ignore_iterators: [] exclude: - Anima::Attribute#define_reader # 2 levels - Anima#included # 2 levels enabled: true max_allowed_nesting: 1 TooManyStatements: max_statements: 7 # TODO: decrease max_statements to 5 or less exclude: [] enabled: true DuplicateMethodCall: allow_calls: [] exclude: [] enabled: true max_calls: 1 UtilityFunction: exclude: [] enabled: true Attribute: exclude: [] enabled: false UncommunicativeVariableName: accept: [] exclude: [] enabled: true reject: - '/^.$/' - '/[0-9]$/' - '/[A-Z]/' RepeatedConditional: exclude: [] enabled: true max_ifs: 1 DataClump: exclude: [] enabled: true max_copies: 1 min_clump_size: 3 ControlParameter: exclude: [] enabled: true LongYieldList: max_params: 1 exclude: [] enabled: true # Thats the whole point of this lib, oOo. ModuleInitialize: enabled: false anima-0.3.1/config/yardstick.yml0000644000175000017500000000002313715565132020010 0ustar debbiecocoadebbiecocoa--- threshold: 100 anima-0.3.1/config/flog.yml0000644000175000017500000000002413715565132016743 0ustar debbiecocoadebbiecocoa--- threshold: 11.9 anima-0.3.1/config/flay.yml0000644000175000017500000000004113715565132016746 0ustar debbiecocoadebbiecocoa--- threshold: 6 total_score: 29 anima-0.3.1/config/mutant.yml0000644000175000017500000000005713715565132017332 0ustar debbiecocoadebbiecocoa--- name: anima namespace: Anima zombify: true anima-0.3.1/config/rubocop.yml0000644000175000017500000000626313715565132017500 0ustar debbiecocoadebbiecocoainherit_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' Exclude: # The Pathname#find method is mistakenly identified as Enumberable#find - lib/tasks/assets_precompile_nodigest.rake # 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: 79 # Disable documentation checking until a class needs to be documented once Documentation: Enabled: false # Do not always use &&/|| instead of and/or. AndOr: 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 class body EmptyLinesAroundClassBody: Enabled: false # Allow empty lines around module body EmptyLinesAroundModuleBody: Enabled: false # Allow empty lines around block body EmptyLinesAroundBlockBody: Enabled: false # Allow multiple line operations to not require indentation MultilineOperationIndentation: Enabled: false # Prefer String#% over Kernel#sprintf FormatString: Enabled: false # Use square brackets for literal Array objects PercentLiteralDelimiters: PreferredDelimiters: '%': '{}' '%i': '[]' '%q': () '%Q': () '%r': '{}' '%s': () '%w': '[]' '%W': '[]' '%x': () # Use %i[...] for arrays of symbols SymbolArray: Enabled: true # Align if/else blocks with the variable assignment EndAlignment: EnforcedStyleAlignWith: variable # Do not always align parameters when it is easier to read AlignParameters: Exclude: - spec/**/*_spec.rb # Prefer #kind_of? over #is_a? ClassCheck: EnforcedStyle: kind_of? # Do not prefer double quotes to be used when %q or %Q is more appropriate UnneededPercentQ: Enabled: false # Allow a maximum ABC score Metrics/AbcSize: Max: 21.02 # Do not prefer lambda.call(...) over lambda.(...) LambdaCall: Enabled: false # Buggy cop, returns false positive for our code base NonLocalExitFromIterator: Enabled: false # To allow alignment of similar expressions we want to allow more than one # space around operators: # # let(:a) { bar + something } # let(:b) { foobar + something } # SpaceAroundOperators: Enabled: false # We use parallel assignments with great success ParallelAssignment: Enabled: false anima-0.3.1/.rspec0000644000175000017500000000003613715565132015144 0ustar debbiecocoadebbiecocoa--color --require spec_helper anima-0.3.1/.rubocop.yml0000644000175000017500000000013413715565132016300 0ustar debbiecocoadebbiecocoaAllCops: Include: - 'Gemfile' Exclude: - 'Gemfile.devtools' - 'vendor/**/*' anima-0.3.1/.travis.yml0000644000175000017500000000036313715565132016143 0ustar debbiecocoadebbiecocoalanguage: ruby cache: bundler bundler_args: --without yard guard benchmarks script: "bundle exec rake ci:metrics" rvm: - '2.1' - '2.2' matrix: allowed_failures: - jruby # Travis jruby does not run in >= 2.1 ruby yet - rbx # segfault anima-0.3.1/.circleci/0000755000175000017500000000000013715565132015663 5ustar debbiecocoadebbiecocoaanima-0.3.1/.circleci/config.yml0000644000175000017500000000117113715565132017653 0ustar debbiecocoadebbiecocoadefaults: &defaults working_directory: ~/anima docker: - image: circleci/ruby:2.6.0 version: 2 jobs: unit_specs: <<: *defaults steps: - checkout - run: bundle install - run: bundle exec rspec spec/unit metrics: <<: *defaults steps: - checkout - run: bundle install - run: bundle exec rake metrics:rubocop - run: bundle exec rake metrics:reek mutant: <<: *defaults steps: - checkout - run: bundle install - run: bundle exec rake metrics:mutant workflows: version: 2 test: jobs: - unit_specs - metrics - mutant anima-0.3.1/lib/0000755000175000017500000000000013715565132014576 5ustar debbiecocoadebbiecocoaanima-0.3.1/lib/anima/0000755000175000017500000000000013715565132015663 5ustar debbiecocoadebbiecocoaanima-0.3.1/lib/anima/attribute.rb0000644000175000017500000000205113715565132020211 0ustar debbiecocoadebbiecocoaclass Anima # An attribute class Attribute include Adamantium::Flat, Equalizer.new(:name) # Initialize attribute # # @param [Symbol] name def initialize(name) @name, @instance_variable_name = name, :"@#{name}" end # Return attribute name # # @return [Symbol] attr_reader :name # Return instance variable name # # @return [Symbol] attr_reader :instance_variable_name # Load attribute # # @param [Object] object # @param [Hash] attributes # # @return [self] def load(object, attributes) set(object, attributes.fetch(name)) end # Get attribute value from object # # @param [Object] object # # @return [Object] def get(object) object.public_send(name) end # Set attribute value in object # # @param [Object] object # @param [Object] value # # @return [self] def set(object, value) object.instance_variable_set(instance_variable_name, value) self end end # Attribute end # Anima anima-0.3.1/lib/anima/error.rb0000644000175000017500000000100713715565132017337 0ustar debbiecocoadebbiecocoaclass Anima # Abstract base class for anima errors class Error < RuntimeError FORMAT = '%s attributes missing: %s, unknown: %s'.freeze private_constant(*constants(false)) # Initialize object # # @param [Class] klass # the class being initialized # @param [Enumerable] missing # @param [Enumerable] unknown # # @return [undefined] def initialize(klass, missing, unknown) super(FORMAT % [klass, missing, unknown]) end end # Error end # Anima anima-0.3.1/lib/anima.rb0000644000175000017500000000751213715565132016215 0ustar debbiecocoadebbiecocoarequire 'adamantium' require 'equalizer' require 'abstract_type' # Main library namespace and mixin # @api private class Anima < Module include Adamantium::Flat, Equalizer.new(:attributes) # Return names # # @return [AttributeSet] attr_reader :attributes # Initialize object # # @return [undefined] def initialize(*names) @attributes = names.uniq.map(&Attribute.method(:new)).freeze end # Return new anima with attributes added # # @return [Anima] # # @example # anima = Anima.new(:foo) # anima.add(:bar) # equals Anima.new(:foo, :bar) # def add(*names) new(attribute_names + names) end # Return new anima with attributes removed # # @return [Anima] # # @example # anima = Anima.new(:foo, :bar) # anima.remove(:bar) # equals Anima.new(:foo) # def remove(*names) new(attribute_names - names) end # Return attributes hash for instance # # @param [Object] object # # @return [Hash] def attributes_hash(object) attributes.each_with_object({}) do |attribute, attributes_hash| attributes_hash[attribute.name] = attribute.get(object) end end # Return attribute names # # @return [Enumerable] def attribute_names attributes.map(&:name) end memoize :attribute_names # Initialize instance # # @param [Object] object # # @param [Hash] attribute_hash # # @return [self] def initialize_instance(object, attribute_hash) assert_known_attributes(object.class, attribute_hash) attributes.each do |attribute| attribute.load(object, attribute_hash) end self end # Static instance methods for anima infected classes module InstanceMethods # Initialize an anima infected object # # @param [#to_h] attributes # a hash that matches anima defined attributes # # @return [undefined] def initialize(attributes) self.class.anima.initialize_instance(self, attributes) end # Return a hash representation of an anima infected object # # @example # anima.to_h # => { :foo => : bar } # # @return [Hash] # # @api public def to_h self.class.anima.attributes_hash(self) end # Return updated instance # # @example # klass = Class.new do # include Anima.new(:foo, :bar) # end # # foo = klass.new(:foo => 1, :bar => 2) # updated = foo.with(:foo => 3) # updated.foo # => 3 # updated.bar # => 2 # # @param [Hash] attributes # # @return [Anima] # # @api public def with(attributes) self.class.new(to_h.update(attributes)) end end # InstanceMethods private # Infect the instance with anima # # @param [Class, Module] scope # # @return [undefined] def included(descendant) descendant.instance_exec(self, attribute_names) do |anima, names| # Define anima method define_singleton_method(:anima) { anima } # Define instance methods include InstanceMethods # Define attribute readers attr_reader(*names) # Define equalizer include Equalizer.new(*names) end end # Fail unless keys in +attribute_hash+ matches #attribute_names # # @param [Class] klass # the class being initialized # # @param [Hash] attribute_hash # the attributes to initialize +object+ with # # @return [undefined] # # @raise [Error] def assert_known_attributes(klass, attribute_hash) keys = attribute_hash.keys unknown = keys - attribute_names missing = attribute_names - keys unless unknown.empty? && missing.empty? fail Error.new(klass, missing, unknown) end end # Return new instance # # @param [Enumerable] attributes # # @return [Anima] def new(attributes) self.class.new(*attributes) end end # Anima require 'anima/error' require 'anima/attribute' anima-0.3.1/Gemfile0000644000175000017500000000004713715565132015324 0ustar debbiecocoadebbiecocoasource 'https://rubygems.org' gemspec anima-0.3.1/Rakefile0000644000175000017500000000054213715565132015476 0ustar debbiecocoadebbiecocoarequire 'devtools' Devtools.init_rake_tasks Rake.application.load_imports task('metrics:mutant').clear namespace :metrics do task :mutant => :coverage do system(*%w[ bundle exec mutant --include lib --require anima --use rspec --zombie -- Anima* ]) or fail "Mutant task failed" end end anima-0.3.1/Changelog.md0000644000175000017500000000153513715565132016245 0ustar debbiecocoadebbiecocoa# v0.3.1 2018-02-15 * Fix falsy key match detection # v0.3.0 2015-09-04 Changes: * Drop support for ruby < 2.1 * Drop support for Anima::Update * Add #with as a replacement that is active by default [#13] # v0.2.1 2014-04-10 Changes: * Require ruby version >= 1.9.3 in gemspec. # v0.2.0 2014-01-13 Breaking changes: * Remove AnimaInfectedClass.attributes_hash(instance) * Replace with AnimaInfectedClass#to_h # v0.1.1 2013-09-08 * [change] Refactor internals. # v0.1.0 2013-09-08 * [change] Update dependencies # v0.0.6 2013-02-18 * [add] Support for updates via Anima::Update mixin * [change] Update dependencies # v0.0.5 2013-02-17 * [changed] Update dependencies # v0.0.4 2013-01-21 * [changed] Update dependencies # v0.0.3 2012-12-13 * [changed] Use the attributes_hash naming consistently. # v0.0.2 2012-12-13 First public release! anima-0.3.1/anima.gemspec0000644000175000017500000000131113715565132016456 0ustar debbiecocoadebbiecocoaGem::Specification.new do |s| s.name = 'anima' s.version = '0.3.1' s.authors = ['Markus Schirp'] s.email = 'mbj@schirp-dso.com' s.summary = 'Initialize object attributes via attributes hash' s.homepage = 'http://github.com/mbj/anima' s.license = 'MIT' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {spec,features}/*`.split("\n") s.require_paths = %w(lib) s.extra_rdoc_files = %w(README.md) s.required_ruby_version = '>= 2.1.0' s.add_dependency('adamantium', '~> 0.2') s.add_dependency('equalizer', '~> 0.0.11') s.add_dependency('abstract_type', '~> 0.0.7') s.add_development_dependency('devtools', '~> 0.1.23') end anima-0.3.1/spec/0000755000175000017500000000000013715565132014762 5ustar debbiecocoadebbiecocoaanima-0.3.1/spec/unit/0000755000175000017500000000000013715565132015741 5ustar debbiecocoadebbiecocoaanima-0.3.1/spec/unit/anima_spec.rb0000644000175000017500000001142213715565132020365 0ustar debbiecocoadebbiecocoadescribe Anima do let(:object) { described_class.new(:foo) } describe '#attributes_hash' do let(:value) { double('Value') } let(:instance) { double(foo: value) } subject { object.attributes_hash(instance) } it { should eql(foo: value) } end describe '#remove' do let(:object) { described_class.new(:foo, :bar) } context 'with single attribute' do subject { object.remove(:bar) } it { should eql(described_class.new(:foo)) } end context 'with multiple attributes' do subject { object.remove(:foo, :bar) } it { should eql(described_class.new) } end context 'with inexisting attribute' do subject { object.remove(:baz) } it { should eql(object) } end end describe '#add' do context 'with single attribute' do subject { object.add(:bar) } it { should eql(described_class.new(:foo, :bar)) } end context 'with multiple attributes' do subject { object.add(:bar, :baz) } it { should eql(described_class.new(:foo, :bar, :baz)) } end context 'with duplicate attribute ' do subject { object.add(:foo) } it { should eql(object) } end end describe '#attributes' do subject { object.attributes } it { should eql([Anima::Attribute.new(:foo)]) } it { should be_frozen } end describe '#included' do let(:target) do object = self.object Class.new do include object end end let(:value) { double('Value') } let(:instance) { target.new(foo: value) } let(:instance_b) { target.new(foo: value) } let(:instance_c) { target.new(foo: double('Bar')) } context 'on instance' do subject { instance } its(:foo) { should be(value) } it { should eql(instance_b) } it { should_not eql(instance_c) } end context 'on singleton' do subject { target } it 'should define attribute hash reader' do expect(instance.to_h).to eql(foo: value) end its(:anima) { should be(object) } end end describe '#initialize_instance' do let(:object) { Anima.new(:foo, :bar) } let(:target) { Object.new } let(:foo) { double('Foo') } let(:bar) { double('Bar') } subject { object.initialize_instance(target, attribute_hash) } context 'when all keys are present in attribute hash' do let(:attribute_hash) { { foo: foo, bar: bar } } it 'should initialize target instance variables' do subject expect( target .instance_variables .map(&:to_sym) .to_set ).to eql(%i[@foo @bar].to_set) expect(target.instance_variable_get(:@foo)).to be(foo) expect(target.instance_variable_get(:@bar)).to be(bar) end it_should_behave_like 'a command method' end context 'when an extra key is present in attribute hash' do let(:attribute_hash) { { foo: foo, bar: bar, baz: double('Baz') } } it 'should raise error' do expect { subject }.to raise_error( Anima::Error, Anima::Error.new(target.class, [], [:baz]).message ) end context 'and the extra key is falsy' do let(:attribute_hash) { { foo: foo, bar: bar, nil => double('Baz') } } it 'should raise error' do expect { subject }.to raise_error( Anima::Error, Anima::Error.new(target.class, [], [nil]).message ) end end end context 'when a key is missing in attribute hash' do let(:attribute_hash) { { bar: bar } } it 'should raise error' do expect { subject }.to raise_error( Anima::Error.new(target.class, [:foo], []).message ) end end end describe 'using super in initialize' do subject { klass.new } let(:klass) do Class.new do include Anima.new(:foo) def initialize(attributes = { foo: :bar }) super end end end its(:foo) { should eql(:bar) } end describe '#to_h on an anima infected instance' do subject { instance.to_h } let(:instance) { klass.new(params) } let(:params) { Hash[foo: :bar] } let(:klass) do Class.new do include Anima.new(:foo) end end it { should eql(params) } end describe '#with' do subject { object.with(attributes) } let(:klass) do Class.new do include Anima.new(:foo, :bar) end end let(:object) { klass.new(foo: 1, bar: 2) } context 'with empty attributes' do let(:attributes) { {} } it { should eql(object) } end context 'with updated attribute' do let(:attributes) { { foo: 3 } } it { should eql(klass.new(foo: 3, bar: 2)) } end end end anima-0.3.1/spec/unit/anima/0000755000175000017500000000000013715565132017026 5ustar debbiecocoadebbiecocoaanima-0.3.1/spec/unit/anima/attribute_spec.rb0000644000175000017500000000246313715565132022375 0ustar debbiecocoadebbiecocoadescribe Anima::Attribute do let(:object) { described_class.new(:foo) } describe '#get' do subject { object.get(target) } let(:target_class) do Class.new do attr_reader :foo def initialize(foo) @foo = foo end end end let(:target) { target_class.new(value) } let(:value) { double('Value') } it 'should return value' do should be(value) end end describe '#load' do subject { object.load(target, attribute_hash) } let(:target) { Object.new } let(:value) { double('Value') } let(:attribute_hash) { { foo: value } } it 'should set value as instance variable' do subject expect(target.instance_variable_get(:@foo)).to be(value) end it_should_behave_like 'a command method' end describe '#instance_variable_name' do subject { object.instance_variable_name } it { should be(:@foo) } it_should_behave_like 'an idempotent method' end describe '#set' do subject { object.set(target, value) } let(:target) { Object.new } let(:value) { double('Value') } it_should_behave_like 'a command method' it 'should set value as instance variable' do subject expect(target.instance_variable_get(:@foo)).to be(value) end end end anima-0.3.1/spec/unit/anima/error_spec.rb0000644000175000017500000000063113715565132021516 0ustar debbiecocoadebbiecocoadescribe Anima::Error do describe '#message' do let(:object) { described_class.new(Anima, missing, unknown) } let(:missing) { %i[missing] } let(:unknown) { %i[unknown] } subject { object.message } it 'should return the message string' do should eql('Anima attributes missing: [:missing], unknown: [:unknown]') end it_should_behave_like 'an idempotent method' end end anima-0.3.1/spec/spec_helper.rb0000644000175000017500000000005713715565132017602 0ustar debbiecocoadebbiecocoarequire 'anima' require 'devtools/spec_helper' anima-0.3.1/spec/integration/0000755000175000017500000000000013715565132017305 5ustar debbiecocoadebbiecocoaanima-0.3.1/spec/integration/simple_spec.rb0000644000175000017500000000203113715565132022131 0ustar debbiecocoadebbiecocoaclass TestClass include Anima.new(:firstname, :lastname) end describe Anima, 'simple integration' do subject { TestClass.new(attributes) } context 'when instantiated with all attributes' do let(:attributes) do { firstname: 'Markus', lastname: 'Schirp' } end its(:firstname) { should eql('Markus') } its(:lastname) { should eql('Schirp') } end context 'with instantiated with extra attributes' do let(:attributes) do { firstname: 'Markus', lastname: 'Schirp', extra: 'Foo' } end it 'should raise error' do expect { subject }.to raise_error( Anima::Error, 'TestClass attributes missing: [], unknown: [:extra]' ) end end context 'when instantiated with missing attributes' do let(:attributes) { {} } it 'should raise error' do expect { subject }.to raise_error( Anima::Error, 'TestClass attributes missing: [:firstname, :lastname], unknown: []' ) end end end