anima-0.3.2/0000755000004100000410000000000013766271756012655 5ustar www-datawww-dataanima-0.3.2/README.md0000644000004100000410000000575713766271756014152 0ustar www-datawww-dataanima ===== ![CI](https://github.com/mbj/anima/workflows/CI/badge.svg) 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.2/anima.gemspec0000644000004100000410000000357713766271756015323 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: anima 0.3.2 ruby lib Gem::Specification.new do |s| s.name = "anima".freeze s.version = "0.3.2" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Markus Schirp".freeze] s.date = "2020-09-10" s.email = "mbj@schirp-dso.com".freeze s.extra_rdoc_files = ["README.md".freeze] s.files = ["README.md".freeze, "lib/anima.rb".freeze, "lib/anima/attribute.rb".freeze, "lib/anima/error.rb".freeze] s.homepage = "http://github.com/mbj/anima".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.1.0".freeze) s.rubygems_version = "2.5.2.1".freeze s.summary = "Initialize object attributes via attributes hash".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q.freeze, ["~> 0.0.7"]) s.add_runtime_dependency(%q.freeze, ["~> 0.2"]) s.add_development_dependency(%q.freeze, ["~> 0.1.24"]) s.add_runtime_dependency(%q.freeze, ["~> 0.0.11"]) else s.add_dependency(%q.freeze, ["~> 0.0.7"]) s.add_dependency(%q.freeze, ["~> 0.2"]) s.add_dependency(%q.freeze, ["~> 0.1.24"]) s.add_dependency(%q.freeze, ["~> 0.0.11"]) end else s.add_dependency(%q.freeze, ["~> 0.0.7"]) s.add_dependency(%q.freeze, ["~> 0.2"]) s.add_dependency(%q.freeze, ["~> 0.1.24"]) s.add_dependency(%q.freeze, ["~> 0.0.11"]) end end anima-0.3.2/lib/0000755000004100000410000000000013766271756013423 5ustar www-datawww-dataanima-0.3.2/lib/anima/0000755000004100000410000000000013766271756014510 5ustar www-datawww-dataanima-0.3.2/lib/anima/attribute.rb0000644000004100000410000000205113766271756017036 0ustar www-datawww-dataclass 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.2/lib/anima/error.rb0000644000004100000410000000100713766271756016164 0ustar www-datawww-dataclass 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.2/lib/anima.rb0000644000004100000410000000751213766271756015042 0ustar www-datawww-datarequire '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'