koine-attributes 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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