koine-attributes 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2bc29763169638e976dbce7e8a8fd34e64fa1661
4
- data.tar.gz: 8c92de7bb50fc754426854fdafca7e74a8820105
3
+ metadata.gz: deb27fe7217e17e870a86be97914caa6b8d06694
4
+ data.tar.gz: 6b64a5897f5e12eb03d5e62551697733711df8b8
5
5
  SHA512:
6
- metadata.gz: 9142060ec750bffb182c31d71cde2983bf4507f4867f6dfd72491c165b15ac78d3ef15e7f1b9abb581717d3542a9ca422a378009d3ff224b3952f5da0a16506f
7
- data.tar.gz: e37af654cd64875ab1b39b2197de56aff89870da607403df12e7676291b43a5ef1c4cafb53d8fb643bc9d7075f92cacb1bd6167e5c4b48e1084cc8e7283c15a2
6
+ metadata.gz: 38fec92fd56ebd54e19cccfc2ed7c640b20f4d453f23ef25fed90c8ed63b168ba43548fe98e7edf3d26dd6e6f262f8c1ba0bfda07a6b538631f7460d0397ed51
7
+ data.tar.gz: f0f593389f582857f67bec046cb7338a593282e9f2dee18e489c31cf63b5db9193af87d678252771c6f57a6c70a7759d009e4f2ae2eda432bd9ae3b53d467bd7
data/.travis.yml CHANGED
@@ -4,7 +4,10 @@ rvm:
4
4
  - 2.1
5
5
  - 2.2
6
6
  - 2.3.1
7
+ - 2.3.5
7
8
  - 2.4.0
9
+ - 2.4.1
10
+ - 2.4.2
8
11
  script:
9
12
  - bundle exec rake test:coveralls
10
13
  before_install: gem install bundler -v 1.14.6
data/Rakefile CHANGED
@@ -1,19 +1,18 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
7
7
 
8
8
  namespace :test do
9
-
10
- desc "run tests and generate invoke coveralls"
9
+ desc 'run tests and generate invoke coveralls'
11
10
  task :coveralls do
12
11
  ENV['COVERALLS'] = 'true'
13
12
  Rake::Task['test:coverage'].invoke
14
13
  end
15
14
 
16
- desc "run tests and generate code coverage"
15
+ desc 'run tests and generate code coverage'
17
16
  task :coverage do
18
17
  ENV['COVERAGE'] = 'true'
19
18
  Rake::Task[:spec].invoke
data/bin/console CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "bundler/setup"
4
- require "koine/attributes"
3
+ require 'bundler/setup'
4
+ require 'koine/attributes'
5
5
 
6
6
  # You can add fixtures and/or initialization code here to make experimenting
7
7
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +10,5 @@ require "koine/attributes"
10
10
  # require "pry"
11
11
  # Pry.start
12
12
 
13
- require "irb"
13
+ require 'irb'
14
14
  IRB.start(__FILE__)
@@ -36,4 +36,6 @@ Gem::Specification.new do |spec|
36
36
  spec.add_development_dependency 'rspec', '~> 3.0'
37
37
  spec.add_development_dependency 'coveralls'
38
38
  spec.add_development_dependency 'simplecov'
39
+ spec.add_development_dependency 'rubocop'
40
+ spec.add_development_dependency 'reek'
39
41
  end
@@ -1,5 +1,5 @@
1
+ require 'forwardable'
1
2
  require 'koine/attributes/version'
2
- require 'koine/attributes/builder'
3
3
  require 'koine/attributes/adapter/base'
4
4
 
5
5
  # provides the following API
@@ -101,6 +101,7 @@ require 'koine/attributes/adapter/base'
101
101
  module Koine
102
102
  module Attributes
103
103
  autoload :Attributes, 'koine/attributes/attributes'
104
+ autoload :AttributesFactory, 'koine/attributes/attributes_factory'
104
105
 
105
106
  module Adapter
106
107
  autoload :Boolean, 'koine/attributes/adapter/boolean'
@@ -114,42 +115,44 @@ module Koine
114
115
  Error = Class.new(StandardError)
115
116
 
116
117
  def self.included(base)
118
+ base.extend(Forwardable)
117
119
  base.extend(ClassMethods)
118
120
  end
119
121
 
120
122
  module ClassMethods
121
123
  def attributes(options = {}, &block)
122
- @builder = Builder::ClassBuilder.new(target: self)
124
+ @builder = true
125
+ @_attributes_factory ||= AttributesFactory.new(options)
123
126
  class_eval(&block)
124
127
 
125
- if options[:initializer]
126
- initializer_options = options[:initializer]
128
+ instance_eval do
129
+ define_method :attributes do
130
+ @_attributes ||= self.class.instance_variable_get(:@_attributes_factory).create(self)
131
+ end
127
132
 
128
- initializer_options = {} unless initializer_options.is_a?(Hash)
129
-
130
- @builder.build_constructor(initializer_options)
131
- else
132
- @builder.build_lazy_attributes
133
+ define_method(:initialize) { |*args| attributes.initialize_values(*args) }
133
134
  end
134
135
 
136
+ @_attributes_factory.freeze
137
+
135
138
  @builder = nil
136
139
  end
137
140
 
138
- def attribute(name, adapter, lambda_constructor = nil, &block)
141
+ def attribute(name, adapter, lambda_arg = nil, &block)
139
142
  unless @builder
140
143
  raise Error, 'You must call .attribute inside the .attributes block'
141
144
  end
142
145
 
143
- adapter = coerce_adapter(adapter, lambda_constructor, &block)
144
- @builder.build(name, adapter)
145
- end
146
+ block = lambda_arg || block
147
+ instance_variable_get(:@_attributes_factory).add_attribute(name, adapter, &block)
146
148
 
147
- private def coerce_adapter(adapter, lambda_constructor, &block)
148
- return adapter unless adapter.instance_of?(::Symbol)
149
- adapter = const_get("Koine::Attributes::Adapter::#{adapter.to_s.capitalize}").new
150
- lambda_constructor.call(adapter) if lambda_constructor
151
- yield(adapter) if block
152
- adapter
149
+ instance_eval do
150
+ def_delegators :attributes, name, "#{name}=", "with_#{name}"
151
+
152
+ define_method :== do |other|
153
+ attributes == other.attributes
154
+ end
155
+ end
153
156
  end
154
157
  end
155
158
  end
@@ -9,4 +9,3 @@ module Koine
9
9
  end
10
10
  end
11
11
  end
12
-
@@ -1,17 +1,120 @@
1
1
  module Koine
2
2
  module Attributes
3
3
  class Attributes
4
- def initialize(object, attributes:)
4
+ def initialize(object, adapters:, options: {})
5
5
  @object = object
6
- @attributes = attributes
6
+ @adapters = adapters
7
+ @values = {}
8
+ @initializer = { strict: true, freeze: false, initialize: !options[:initializer].nil? }
9
+
10
+ if options[:initializer].is_a?(Hash)
11
+ @initializer = @initializer.merge(options[:initializer])
12
+ end
13
+ end
14
+
15
+ def initialize_values(values = {})
16
+ if !@initializer[:initialize] && !values.empty?
17
+ raise ArgumentError, "wrong number of arguments (given #{values.length}, expected 0)"
18
+ end
19
+
20
+ return unless @initializer[:initialize]
21
+ set_values(values) && @initializer[:freeze] && freeze
22
+ end
23
+
24
+ def set_values(values)
25
+ invalid_attributes = []
26
+
27
+ if @initializer[:strict]
28
+ attributes = values.keys.map(&:to_sym)
29
+ invalid_attributes = attributes - valid_attributes
30
+ end
31
+
32
+ unless invalid_attributes.empty?
33
+ raise ArgumentError, "Invalid attributes (#{invalid_attributes.join(', ')})"
34
+ end
35
+
36
+ values.each do |attribute, value|
37
+ set(attribute, value) if has_attribute?(attribute)
38
+ end
39
+ end
40
+
41
+ def set(attribute, value)
42
+ @values[attribute.to_sym] = adapter_for(attribute).coerce(value)
43
+ end
44
+
45
+ def with_attribute(attribute, value)
46
+ new_attributes = to_h.merge(attribute => value)
47
+ @object.class.new(new_attributes)
48
+ end
49
+
50
+ def get(attribute)
51
+ @values[attribute.to_sym] || adapter_for(attribute).default_value
52
+ end
53
+
54
+ def ==(other)
55
+ other.to_h == to_h
7
56
  end
8
57
 
9
58
  def to_h
10
- {}.tap do |hash|
11
- @attributes.each do |name|
12
- hash[name.to_sym] = @object.send(name)
13
- end
59
+ valid_attributes.map do |name|
60
+ [name.to_sym, @object.send(name)]
61
+ end.to_h
62
+ end
63
+
64
+ def respond_to?(method, _include_private = nil)
65
+ method = method.to_s
66
+
67
+ # getter
68
+ return true if has_attribute?(method)
69
+
70
+ # {attribute_name}=value
71
+ matches = method.match(/^(.*)=$/)
72
+ return has_attribute?(matches[1]) if matches
73
+
74
+ # with_{attribute}(value)
75
+ matches = method.match(/^with_(.*)$/)
76
+ return has_attribute?(matches[1]) if matches
77
+
78
+ false
79
+ end
80
+
81
+ def method_missing(method_name, *args)
82
+ unless respond_to?(method_name)
83
+ raise NoMethodError, "Undefined method #{method_name} for attributed object #{@object}"
84
+ end
85
+
86
+ method_name = method_name.to_s
87
+
88
+ if method_name.to_s =~ /=$/
89
+ attribute = method_name.to_s.delete('=')
90
+ return set(attribute, *args)
14
91
  end
92
+
93
+ matches = method_name.match(/^with_(.*)$/)
94
+ return with_attribute(matches[1], *args) if matches
95
+
96
+ get(method_name)
97
+ end
98
+
99
+ private
100
+
101
+ def valid_attributes
102
+ @adapters.keys
103
+ end
104
+
105
+ def has_attribute?(attribute)
106
+ @adapters.key?(attribute.to_sym)
107
+ end
108
+
109
+ def adapter_for(attribute)
110
+ @adapters.fetch(attribute.to_sym)
111
+ end
112
+
113
+ def freeze
114
+ @object.freeze
115
+ @adapters.freeze
116
+ @values.freeze
117
+ super
15
118
  end
16
119
  end
17
120
  end
@@ -0,0 +1,31 @@
1
+ module Koine
2
+ module Attributes
3
+ class AttributesFactory
4
+ def initialize(options = {})
5
+ @adapters = {}
6
+ @options = options
7
+ end
8
+
9
+ def create(target_object)
10
+ Attributes.new(target_object, adapters: @adapters, options: @options)
11
+ end
12
+
13
+ def add_attribute(name, adapter, &block)
14
+ adapter = coerce_adapter(adapter)
15
+ yield(adapter) if block
16
+ @adapters[name.to_sym] = adapter.freeze
17
+ end
18
+
19
+ def coerce_adapter(adapter)
20
+ return adapter unless adapter.instance_of?(::Symbol)
21
+ Object.const_get("Koine::Attributes::Adapter::#{adapter.to_s.capitalize}").new
22
+ end
23
+
24
+ def freeze
25
+ super
26
+ @adapters.freeze
27
+ @options.freeze
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  module Koine
2
2
  module Attributes
3
- VERSION = "0.1.4"
3
+ VERSION = '0.2.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: koine-attributes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcelo Jacobus
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-10-20 00:00:00.000000000 Z
11
+ date: 2017-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: reek
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description: Stronger getter and setters for your ruby classes
84
112
  email:
85
113
  - marcelo.jacobus@xing.com
@@ -107,7 +135,7 @@ files:
107
135
  - lib/koine/attributes/adapter/string.rb
108
136
  - lib/koine/attributes/adapter/time.rb
109
137
  - lib/koine/attributes/attributes.rb
110
- - lib/koine/attributes/builder.rb
138
+ - lib/koine/attributes/attributes_factory.rb
111
139
  - lib/koine/attributes/hash_helper.rb
112
140
  - lib/koine/attributes/version.rb
113
141
  homepage: https://github.com/mjacobus/koine-attributes
@@ -131,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
131
159
  version: '0'
132
160
  requirements: []
133
161
  rubyforge_project:
134
- rubygems_version: 2.5.2.1
162
+ rubygems_version: 2.6.13
135
163
  signing_key:
136
164
  specification_version: 4
137
165
  summary: Stronger setters and getters
@@ -1,167 +0,0 @@
1
- require 'koine/attributes/hash_helper'
2
-
3
- module Koine
4
- module Attributes
5
- module Builder
6
- class ClassBuilder
7
- attr_reader :attributes
8
- attr_reader :target
9
-
10
- def initialize(target:)
11
- @target = target
12
- @attributes = []
13
- @getter = GetterBuilder.new(attributes: attributes, target: target)
14
- @setter = SetterBuilder.new(attributes: attributes, target: target)
15
- @constructor = ConstructorBuilder.new(attributes: attributes, target: target)
16
- @lazy_attributes = LazyAttributesBuilder.new(attributes: attributes, target: target)
17
- end
18
-
19
- def build(name, driver)
20
- attributes.push(name.to_sym)
21
- @getter.build(name, driver)
22
- @setter.build(name, driver)
23
- end
24
-
25
- def build_constructor(strict: true, freeze: false)
26
- @constructor.build(strict: strict, freeze: freeze)
27
- end
28
-
29
- def build_lazy_attributes
30
- @lazy_attributes.build
31
- end
32
- end
33
-
34
- class BaseBuilder
35
- attr_reader :attributes, :target
36
-
37
- def initialize(attributes:, target:)
38
- @attributes = attributes
39
- @target = target
40
- end
41
- end
42
-
43
- class GetterBuilder < BaseBuilder
44
- def build(name, driver)
45
- target.class_eval do
46
- define_method(name) do
47
- instance_variable_get("@#{name}") || driver.default_value
48
- end
49
- end
50
- end
51
- end
52
-
53
- class SetterBuilder < BaseBuilder
54
- def build(name, driver)
55
- setter = "#{name}="
56
-
57
- target.class_eval do
58
- define_method(setter) do |*args|
59
- instance_variable_set("@#{name}", driver.coerce(*args))
60
- end
61
- end
62
- end
63
- end
64
-
65
- class LazyAttributesBuilder < BaseBuilder
66
- def build
67
- valid_attributes = attributes
68
-
69
- target.class_eval do
70
- define_method :attributes do
71
- @_koine_attributes ||= Koine::Attributes::Attributes.new(self, attributes: valid_attributes)
72
- end
73
- end
74
- end
75
- end
76
-
77
- class ConstructorBuilder < BaseBuilder
78
- def build(strict: true, freeze: false)
79
- valid_attributes = attributes
80
-
81
- target.class_eval do
82
- define_method :initialize do |args = {}|
83
- @_koine_attributes ||= Koine::Attributes::Attributes.new(self, attributes: valid_attributes)
84
- initialize_attributes(args)
85
- end
86
-
87
- def attributes
88
- @_koine_attributes
89
- end
90
-
91
- protected
92
-
93
- define_method(:initialize_attributes) do |constructor_args|
94
- invalid_attributes = []
95
- constructor_args = HashHelper.new.symbolize_keys(constructor_args)
96
-
97
- constructor_args.each do |name, value|
98
- if valid_attributes.include?(name)
99
- setter = "#{name}=".to_sym
100
- send(setter, value)
101
- next
102
- end
103
-
104
- next unless strict
105
- invalid_attributes << name
106
- end
107
-
108
- unless invalid_attributes.empty?
109
- attributes = invalid_attributes.join(', ')
110
- raise ArgumentError, "Invalid attributes (#{attributes})"
111
- end
112
-
113
- self.freeze if freeze
114
- end
115
- end
116
-
117
- if strict
118
- make_setters_private
119
- create_with_methods
120
- create_comparator
121
- end
122
- end
123
-
124
- protected
125
-
126
- def make_setters_private
127
- attrs = attributes
128
-
129
- target.instance_eval do
130
- attrs.each do |attr|
131
- private "#{attr}="
132
- end
133
- end
134
- end
135
-
136
- def create_with_methods
137
- attributes.each do |attr|
138
- create_with_method(attr)
139
- end
140
- end
141
-
142
- def create_with_method(attr)
143
- target.class_eval do
144
- define_method "with_#{attr}" do |*args|
145
- dup.tap do |new_object|
146
- new_object.send("#{attr}=", *args)
147
- new_object.freeze
148
- end
149
- end
150
- end
151
- end
152
-
153
- def create_comparator
154
- attrs = attributes
155
-
156
- target.class_eval do
157
- define_method :== do |that|
158
- a = attrs.map { |attr| send(attr) }
159
- b = attrs.map { |attr| that.send(attr) }
160
- a == b
161
- end
162
- end
163
- end
164
- end
165
- end
166
- end
167
- end