Skip to content

Commit

Permalink
Add Inheritance Rules, Minor Bug Fixes
Browse files Browse the repository at this point in the history
Attributes previously were not inherited.  This adds the ability
for attributes to be inherited both through classes and modules
through the `@parent` property on the AttributeList.
  • Loading branch information
Jeremy Rodi committed Mar 7, 2017
1 parent 88982e2 commit aa69d3b
Show file tree
Hide file tree
Showing 83 changed files with 224 additions and 45 deletions.
39 changes: 35 additions & 4 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,40 @@
Style/StringLiterals:
EnforcedStyle: double_quotes
AllCops:
TargetRubyVersion: 2.3

Metrics/LineLength:
Enabled: false
Metrics/MethodLength:
Max: 15
Metrics/BlockLength:
Exclude:
- 'spec/*.rb'
- 'spec/**/*.rb'
Metrics/AbcSize:
Max: 25

Style/AlignParameters:
EnforcedStyle: with_fixed_indentation
Style/SignalException:
EnforcedStyle: only_fail
Style/AccessModifierIndentation:
EnforcedStyle: outdent
Style/Documentation:
Enabled: false
Style/Encoding:
Enabled: true
EnforcedStyle: always
AutoCorrectEncodingComment: "# encoding: utf-8\n"
Style/RegexpLiteral:
EnforcedStyle: mixed
Metrics/MethodLength:
Max: 15
Style/StringLiterals:
EnforcedStyle: double_quotes
Style/AlignHash:
EnforcedLastArgumentHashStyle: ignore_implicit
Style/AlignArray:
Enabled: false
Style/IndentArray:
Enabled: false
Style/Alias:
EnforcedStyle: prefer_alias_method
Style/ParallelAssignment:
Enabled: false
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# encoding: utf-8
# frozen_string_literal: true

source "https://rubygems.org"

# These are in here for reasons.
gem "coveralls"
gem "pry"
gem "rubocop"
gem "coveralls"

gemspec
1 change: 1 addition & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

require "bundler/gem_tasks"
require "rspec/core/rake_task"
Expand Down
1 change: 1 addition & 0 deletions lib/mixture.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

require "set"
require "time"
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/attribute.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
# An attribute for a mixture object.
Expand Down
48 changes: 46 additions & 2 deletions lib/mixture/attribute_list.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

require "set"
require "forwardable"
Expand All @@ -13,7 +14,36 @@ class AttributeList
# If it quacks like a duck...
include Comparable
# Then it must be a duck.
delegate [:fetch, :[], :[]=, :key?, :each, :<=>] => :@list
delegate [:<=>, :[]=, :to_h] => :@list

# @!method fetch(key, value = Undefined, &block)
# Fetches the given key from the attribute list. If the current attribute
# list does not have the key, it passes it up to the parent, if there is
# one. If there is no parent, it behaves as normal.
#
# @param key [::Symbol]
# @param value [::Object]
# @return [Attribute]
# @!method [](key)
# Looks up the given key. If the current attribute list does not have the
# key, it passes it up to the parent, if there is one. Otherwise, it
# returns the default value for the list (`nil`).
#
# @param key [::Symbol]
# @return [Attribute]
# @!method key?(key)
# Returns if the attribute exists. If it doesn't exist on this list,
# it passes it up to the parent.
#
# @param key [::Symbol]
# @return [::Boolean]
# @!method each(&block)
# Iterates over the attribute list. If there is a parent, the current
# attribute list is merged into the parent, then iterated over; otherwise,
# it iterates over the current.
#
# @return [void]
delegate [:fetch, :[], :key?, :keys, :values, :each, :has_key?] => :with_parent

# Returns a set of options used for the attributes. This isn't
# used at the moment.
Expand All @@ -27,10 +57,18 @@ class AttributeList
# @return [Hash{Symbol => Array<Proc>}]
attr_reader :callbacks

# The parent of this attribute list. This is "merged" into this attribute
# list.
attr_reader :parent

# Initializes the attribute list.
def initialize
#
# @param parent [AttributeList] The parent {AttributeList}. This is used
# primarily for inheritance.
def initialize(parent = nil)
@list = {}
@options = {}
@parent = parent
@callbacks = Hash.new { |h, k| h[k] = Set.new }
end

Expand All @@ -42,5 +80,11 @@ def initialize
def create(name, options = {})
@list[name] = Attribute.new(name, self, options)
end

protected

def with_parent
@parent ? @list.merge(@parent.with_parent) : @list
end
end
end
7 changes: 3 additions & 4 deletions lib/mixture/coerce.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

require "mixture/coerce/base"
require "mixture/coerce/array"
Expand Down Expand Up @@ -65,13 +66,11 @@ def self.perform(type, value)
begin
block.call(value, to)
rescue CoercionError
raise
fail
rescue StandardError => e
raise CoercionError, "#{e.class}: #{e.message}", e.backtrace
fail CoercionError, "#{e.class}: #{e.message}", e.backtrace
end
end

# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength

# Registers the default coercions.
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/array.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
16 changes: 9 additions & 7 deletions lib/mixture/coerce/base.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

require "singleton"

Expand Down Expand Up @@ -101,12 +102,11 @@ def self.coerce_to(to, data = Undefined, &block)
# @yieldreturn (see .coerce_to)
# @return [void]
def self.data_block(data, &block)
case
when data.is_a?(::Symbol)
if data.is_a?(::Symbol)
proc { |value| value.public_send(data) }
when data.is_a?(::Proc)
elsif data.is_a?(::Proc)
data
when block_given?
elsif block_given?
block
else
fail ArgumentError, "Expected a block, got #{data.inspect}"
Expand All @@ -127,9 +127,11 @@ def self.to(type)
def to(type)
coercions = self.class.coercions
coercable = type.inheritable
.find { |ancestor| coercions.key?(ancestor) }
fail CoercionError, "Undefined coercion #{self.class.type} " \
"=> #{type}" unless coercable
.find { |ancestor| coercions.key?(ancestor) }
unless coercable
fail CoercionError, "Undefined coercion #{self.class.type} " \
"=> #{type}"
end

public_send(coercions[coercable])
end
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/boolean.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
7 changes: 5 additions & 2 deletions lib/mixture/coerce/class.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand All @@ -9,8 +10,10 @@ class Class < Base
coerce_to(Types::Object, Itself)
coerce_to(Types::Class) do |value, type|
member = type.options.fetch(:members).first
fail CoercionError, "Cannot coerce #{value.class} =>" \
" #{member}" unless value.is_a?(member)
unless value.is_a?(member)
fail CoercionError, "Cannot coerce #{value.class} =>" \
" #{member}"
end
value
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/date.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/datetime.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/float.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/hash.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/integer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/nil.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
8 changes: 3 additions & 5 deletions lib/mixture/coerce/object.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand All @@ -13,11 +14,8 @@ class Object < Base
TryMethods = proc do |*methods|
proc do |value|
method = methods.find { |m| value.respond_to?(m) }
if method
value.public_send(method)
else
fail CoercionError, "Could not coerce #{value.class}"
end
fail CoercionError, "Could not coerce #{value.class}" unless method
value.public_send(method)
end
end

Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/range.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/rational.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/set.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/string.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/symbol.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/coerce/time.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Coerce
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/errors.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
# All mixture errors inherit this.
Expand Down
1 change: 1 addition & 0 deletions lib/mixture/extensions.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

require "mixture/extensions/attributable"
require "mixture/extensions/coercable"
Expand Down
14 changes: 11 additions & 3 deletions lib/mixture/extensions/attributable.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Extensions
Expand All @@ -16,8 +17,10 @@ module ClassMethods
def attribute(name, options = {})
name = name.to_s.intern
attr = attributes.create(name, options)
define_method(attr.getter) { attribute(name) }
define_method(attr.setter) { |v| attribute(name, v) }
define_method(attr.getter) { attribute(name) } unless
options[:wo] || options[:write_only]
define_method(attr.setter) { |v| attribute(name, v) } unless
options[:ro] || options[:read_only]
attr
end

Expand All @@ -26,7 +29,12 @@ def attribute(name, options = {})
# @see AttributeList
# @return [AttributeList]
def attributes
@_attributes ||= AttributeList.new
return @_attributes if @_attributes
available = ancestors[1..-1]
.select { |c| c.respond_to?(:attributes) }
.first
parent = available ? available.attributes : nil
@_attributes = AttributeList.new(parent)
end
end

Expand Down
1 change: 1 addition & 0 deletions lib/mixture/extensions/coercable.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# encoding: utf-8
# frozen_string_literal: true

module Mixture
module Extensions
Expand Down
Loading

0 comments on commit aa69d3b

Please sign in to comment.