contracted_value 0.1.0.alpha.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1475b67e1a9de52ca20803fc2b32de21ce69a061b772c0d089bfc3e11dceea1f
4
+ data.tar.gz: 8b80844c114b4224ac252cbcb5694d418a31b586ee69a12c22ef194cba05dc6e
5
+ SHA512:
6
+ metadata.gz: 5956bdb3fc30b074a6443bd014ab8be70e431676d5c4c3b552568539e364c8b2f2f53679991bd840035c8011364204de205cb22283ff5a8eac84988da81bbc3d
7
+ data.tar.gz: a1ee38d4e9a23d95397133e6e6b1fe28884c122cf453a508ead4b1ffb49f2ee949e52d53a545a2410d14c3a1485a2d591df6352d13e0f40fe8ab4da1f35e9fdb
data/.codeclimate.yml ADDED
@@ -0,0 +1,17 @@
1
+ ---
2
+ engines:
3
+ duplication:
4
+ enabled: true
5
+ config:
6
+ languages:
7
+ - ruby
8
+ fixme:
9
+ enabled: true
10
+ rubocop:
11
+ enabled: true
12
+ channel: rubocop-0-70
13
+ ratings:
14
+ paths:
15
+ - "**.rb"
16
+ exclude_paths:
17
+ - spec/
data/.editorconfig ADDED
@@ -0,0 +1,24 @@
1
+ # EditorConfig is awesome: http://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ # default configuration
7
+ [*]
8
+ indent_style = space
9
+ indent_size = 2
10
+ insert_final_newline = true
11
+ trim_trailing_whitespace = true
12
+
13
+ # Unix-style newlines with a newline ending every file
14
+ end_of_line = lf
15
+
16
+ # Set default charset
17
+ charset = utf-8
18
+
19
+ # Tab indentation (no size specified)
20
+ [Makefile]
21
+ indent_style = tab
22
+
23
+ [*.{md,markdown}]
24
+ trim_trailing_whitespace = false
data/.gitignore ADDED
@@ -0,0 +1,35 @@
1
+ # Because this is a gem, ignore Gemfile.lock:
2
+
3
+ Gemfile.lock
4
+
5
+ # And because this is Ruby, ignore the following
6
+ # (source: https://github.com/github/gitignore/blob/master/Ruby.gitignore):
7
+
8
+ *.gem
9
+ *.rbc
10
+ .bundle
11
+ .config
12
+ coverage
13
+ InstalledFiles
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
20
+ tmp
21
+
22
+ # YARD artifacts
23
+ .yardoc
24
+ _yardoc
25
+ doc/
26
+
27
+ # IDEA
28
+ /.idea/
29
+
30
+ # RSpec
31
+ /.rspec
32
+
33
+ # Appraisal
34
+ /gemfiles/.bundle/
35
+ /gemfiles/*.gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,41 @@
1
+ ---
2
+ AllCops:
3
+ Exclude:
4
+ - "Gemfile*"
5
+ - "gemfiles/**/*"
6
+ - "spec/**/*"
7
+ - "node_modules/**/*"
8
+ TargetRubyVersion: 2.4
9
+
10
+ Layout/DotPosition:
11
+ Enabled: true
12
+ Severity: warning
13
+ EnforcedStyle: trailing
14
+
15
+ Metrics/MethodLength:
16
+ Enabled: false
17
+
18
+ Style/Documentation:
19
+ Enabled: false
20
+ Style/RaiseArgs:
21
+ Enabled: false
22
+ Style/StringLiterals:
23
+ Enabled: true
24
+ Severity: warning
25
+ EnforcedStyle: double_quotes
26
+ Style/StringLiteralsInInterpolation:
27
+ Enabled: true
28
+ Severity: warning
29
+ EnforcedStyle: double_quotes
30
+ Style/TrailingCommaInArguments:
31
+ Enabled: true
32
+ Severity: warning
33
+ EnforcedStyleForMultiline: consistent_comma
34
+ Style/TrailingCommaInArrayLiteral:
35
+ Enabled: true
36
+ Severity: warning
37
+ EnforcedStyleForMultiline: consistent_comma
38
+ Style/TrailingCommaInHashLiteral:
39
+ Enabled: true
40
+ Severity: warning
41
+ EnforcedStyleForMultiline: consistent_comma
data/.travis.yml ADDED
@@ -0,0 +1,25 @@
1
+ # Send builds to container-based infrastructure
2
+ # http://docs.travis-ci.com/user/workers/container-based-infrastructure/
3
+ sudo: false
4
+ language: ruby
5
+ before_install:
6
+ # This is required since 2.6.8 has issue when installing `rainbow`
7
+ # https://github.com/sickill/rainbow/issues/48
8
+ - gem update --system
9
+ - gem --version
10
+ cache:
11
+ - bundler
12
+ rvm:
13
+ - 2.4
14
+ - 2.5
15
+ - 2.6
16
+ - 2.7
17
+ - ruby-head
18
+ gemfile:
19
+ - gemfiles/contracts_15_0.gemfile
20
+ - gemfiles/contracts_16_0.gemfile
21
+ matrix:
22
+ fast_finish: true
23
+ allow_failures:
24
+ - rvm: 2.7
25
+ - rvm: ruby-head
data/Appraisals ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ appraise "contracts_15_0" do
4
+ gem "contracts", "~> 0.15.0"
5
+ end
6
+
7
+ appraise "contracts_16_0" do
8
+ gem "contracts", "~> 0.16.0"
9
+ end
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT LICENSE
2
+
3
+ Copyright (c) PikachuEXE <pikachuexe@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,346 @@
1
+ # ContractedValue
2
+
3
+ Library for creating contracted immutable(by default) value objects
4
+
5
+ [![Build Status](http://img.shields.io/travis/PikachuEXE/contracted_value.svg?style=flat-square)](https://travis-ci.org/PikachuEXE/contracted_value)
6
+
7
+ [![Code Climate](https://img.shields.io/codeclimate/maintainability/PikachuEXE/contracted_value.svg?style=flat-square)](https://codeclimate.com/github/PikachuEXE/contracted_value)
8
+ [![Coverage Status](https://img.shields.io/codecov/c/github/PikachuEXE/contracted_value.svg?style=flat-square)](https://codecov.io/gh/PikachuEXE/contracted_value)
9
+ [![Inch CI](https://inch-ci.org/github/PikachuEXE/contracted_value.svg?branch=master)](https://inch-ci.org/github/PikachuEXE/contracted_value)
10
+
11
+
12
+ This gem allows creation of value objects which are
13
+ - contracted (enforced by [`contracts.ruby`](https://github.com/egonSchiele/contracts.ruby))
14
+ - immutable (enforced by [`ice_nine`](https://github.com/dkubb/ice_nine))
15
+
16
+ See details explanation in below sections
17
+
18
+
19
+ ## Installation
20
+
21
+ Add this line to your application's Gemfile:
22
+
23
+ ```ruby
24
+ # `require` can be set to `true` safely without too much side effect
25
+ # (except having additional modules & classes defined which could be wasting memory).
26
+ # But there is no point requiring it unless in test
27
+ # Also maybe add it inside a "group"
28
+ gem "contracted_value", require: false
29
+ ```
30
+
31
+ And then execute:
32
+
33
+ ```bash
34
+ $ bundle
35
+ ```
36
+
37
+ Or install it yourself as:
38
+
39
+ ```bash
40
+ $ gem install contracted_value
41
+ ```
42
+
43
+
44
+ ## Usage
45
+
46
+ The examples below might contain some of my habbits,
47
+ like including [`contracts.ruby`](https://github.com/egonSchiele/contracts.ruby) modules in class
48
+ You **don't** have to do it
49
+
50
+
51
+ ### Attribute Declaration
52
+
53
+ You can declare with or without contract/default value
54
+ But an attribute **cannot** be declared twice
55
+
56
+ ```ruby
57
+ module ::Geometry
58
+ end
59
+
60
+ module ::Geometry::LocationRange
61
+ class Entry < ::ContractedValue::Value
62
+ include ::Contracts::Core
63
+ include ::Contracts::Builtin
64
+
65
+ attribute(
66
+ :latitude,
67
+ contract: Numeric,
68
+ )
69
+ attribute(
70
+ :longitude,
71
+ contract: Numeric,
72
+ )
73
+
74
+ attribute(
75
+ :radius_in_meter,
76
+ contract: And[Numeric, Send[:positive?]],
77
+ )
78
+
79
+ attribute(
80
+ :latitude,
81
+ ) # => error, declared already
82
+ end
83
+ end
84
+
85
+ location_range = ::Geometry::LocationRange::Entry.new(
86
+ latitude: 22.2,
87
+ longitude: 114.4,
88
+ radius_in_meter: 1234,
89
+ )
90
+ ```
91
+
92
+
93
+ ### Attribute Assignment
94
+
95
+ Only `Hash` and `ContractedValue::Value` can be passed to `.new`
96
+
97
+ ```ruby
98
+ module ::Geometry
99
+ end
100
+
101
+ module ::Geometry::Location
102
+ class Entry < ::ContractedValue::Value
103
+ include ::Contracts::Core
104
+ include ::Contracts::Builtin
105
+
106
+ attribute(
107
+ :latitude,
108
+ contract: Numeric,
109
+ )
110
+ attribute(
111
+ :longitude,
112
+ contract: Numeric,
113
+ )
114
+ end
115
+ end
116
+
117
+ module ::Geometry::LocationRange
118
+ class Entry < ::ContractedValue::Value
119
+ include ::Contracts::Core
120
+ include ::Contracts::Builtin
121
+
122
+ attribute(
123
+ :latitude,
124
+ contract: Numeric,
125
+ )
126
+ attribute(
127
+ :longitude,
128
+ contract: Numeric,
129
+ )
130
+
131
+ attribute(
132
+ :radius_in_meter,
133
+ contract: Maybe[And[Numeric, Send[:positive?]]],
134
+ default_value: nil,
135
+ )
136
+ end
137
+ end
138
+
139
+ location = ::Geometry::Location::Entry.new(
140
+ latitude: 22.2,
141
+ longitude: 114.4,
142
+ )
143
+ location_range = ::Geometry::LocationRange::Entry.new(location)
144
+
145
+ ```
146
+
147
+
148
+ ### Input Validation
149
+
150
+ Input values are validated on object creation (instead of on attribute value access) with 2 validations:
151
+ - Value contract
152
+ - Value presence
153
+
154
+ #### Value contract
155
+ An attribute can be declared without any contract, and any input value would be pass the validation
156
+ But you can pass a contract via `contract` option (must be a [`contracts.ruby`](https://github.com/egonSchiele/contracts.ruby) contract)
157
+ Passing input value violating an attribute's contract would cause an error
158
+
159
+ ```ruby
160
+ class YetAnotherRationalNumber < ::ContractedValue::Value
161
+ include ::Contracts::Core
162
+ include ::Contracts::Builtin
163
+
164
+ attribute(
165
+ :numerator,
166
+ contract: ::Integer,
167
+ )
168
+ attribute(
169
+ :denominator,
170
+ contract: And[::Integer, Not[Send[:zero?]]],
171
+ )
172
+ end
173
+
174
+ YetAnotherRationalNumber.new(
175
+ numerator: 1,
176
+ denominator: 0,
177
+ ) # => Error
178
+
179
+ ```
180
+
181
+ #### Value presence
182
+ An attribute declared should be provided a value on object creation, even the input value is `nil`
183
+ Otherwise an error is raised
184
+ You can pass default value via option `default_value`
185
+ The default value will need to confront to the contract passed in `contract` option too
186
+
187
+
188
+ ```ruby
189
+ module ::WhatIsThis
190
+ class Entry < ::ContractedValue::Value
191
+ include ::Contracts::Core
192
+ include ::Contracts::Builtin
193
+
194
+ attribute(
195
+ :something_required,
196
+ )
197
+ attribute(
198
+ :something_optional,
199
+ default_value: nil,
200
+ )
201
+ attribute(
202
+ :something_with_error,
203
+ contract: NatPos,
204
+ default_value: 0,
205
+ ) # => error
206
+ end
207
+ end
208
+
209
+ WhatIsThis::Entry.new(
210
+ something_required: 123,
211
+ ).something_optional # => nil
212
+ ```
213
+
214
+
215
+ ### Object Freezing
216
+ All input values are frozen using [`ice_nine`](https://github.com/dkubb/ice_nine) by default
217
+ But some objects won't work properly when deeply frozen (rails obviously)
218
+ So you can specify how input value should be frozen (or not frozen) with option `refrigeration_mode`
219
+ Possible values are:
220
+ - `:deep` (default)
221
+ - `:shallow`
222
+ - `:none`
223
+
224
+ However the value object itself is always frozen
225
+ Any lazy method caching with use of instance var would cause `FrozenError`
226
+ (Many Rails classes use lazy caching heavily so most rails object can't be frozen to work properly)
227
+
228
+ ```ruby
229
+ class SomeDataEntry < ::ContractedValue::Value
230
+ include ::Contracts::Core
231
+ include ::Contracts::Builtin
232
+
233
+ attribute(
234
+ :cold_hash,
235
+ contract: ::Hash,
236
+ )
237
+ attribute(
238
+ :cool_hash,
239
+ contract: ::Hash,
240
+ refrigeration_mode: :shallow,
241
+ )
242
+ attribute(
243
+ :warm_hash,
244
+ contract: ::Hash,
245
+ refrigeration_mode: :none,
246
+ )
247
+
248
+ def cached_hash
249
+ @cached_hash ||= {}
250
+ end
251
+ end
252
+
253
+ entry = SomeDataEntry.new(
254
+ cold_hash: {a: {b: 0}},
255
+ cool_hash: {a: {b: 0}},
256
+ warm_hash: {a: {b: 0}},
257
+ )
258
+
259
+ entry.cold_hash[:a].delete(:b) # => `FrozenError`
260
+
261
+ entry.cool_hash[:a].delete(:b) # => fine
262
+ entry.cool_hash.delete(:a) # => `FrozenError`
263
+
264
+ entry.warm_hash.delete(:a) # => fine
265
+
266
+ entry.cached_hash # => `FrozenError`
267
+
268
+ ```
269
+
270
+ Beware that the value passed to `default_value` option when declaring an attribute is always deeply frozen
271
+ This is to avoid any in-place change which changes the default value of any value object class attribute
272
+
273
+
274
+ ### Value Object Class Inheritance
275
+ You can create a value object class inheriting an existing value class instead of `::ContractedValue::Value`
276
+
277
+ #### All existing attributes can be used
278
+ No need to explain right?
279
+ ```ruby
280
+ class Pokemon < ::ContractedValue::Value
281
+ attribute(:name)
282
+ end
283
+
284
+ class Pikachu < ::Pokemon
285
+ attribute(:type, default_value: "Thunder")
286
+ end
287
+
288
+ # Ya I love using pokemon as examples, problem?
289
+ pikachu = Pikachu.new(name: "PikaPika")
290
+ ```
291
+
292
+ #### All existing attributes can be redeclared
293
+ Within the same class you cannot redefine an attribute
294
+ But in subclasses you can
295
+ ```ruby
296
+ class Pokemon < ::ContractedValue::Value
297
+ attribute(:name)
298
+ end
299
+
300
+ class Pikachu < ::Pokemon
301
+ include ::Contracts::Core
302
+ include ::Contracts::Builtin
303
+
304
+ attribute(
305
+ :name,
306
+ contract: And[::String, Not[Send[:empty?]]],
307
+ default_value: String.new("Pikachu"),
308
+ refrigeration_mode: :none,
309
+ )
310
+ end
311
+
312
+ # Ya I love using pokemon as examples, problem?
313
+ Pikachu.new.name # => "Pikachu"
314
+ Pikachu.new.name.frozen? # => true, as mentioned above default value are always deeply frozen
315
+ Pikachu.new(name: "Pikaaaachuuu").name.frozen? # => false
316
+ ```
317
+
318
+
319
+ ## Related gems
320
+ Here is a list of gems which I found and I have tried some of them.
321
+ But eventually I am unsatisfied so I build this gem.
322
+
323
+ - [values](https://github.com/tcrayford/values)
324
+ - [active_attr](https://github.com/cgriego/active_attr)
325
+ - [dry-struct](https://github.com/dry-rb/dry-struct)
326
+
327
+ ### [values](https://github.com/tcrayford/values)
328
+ I used to use this a bit
329
+ But I keep having to write the attribute names in `Values.new`,
330
+ then the same attribute names again with `attr_reader` + contract (since I want to use contract)
331
+ Also the input validation happens on attribute value access instead of on object creation
332
+
333
+ ### [active_attr](https://github.com/cgriego/active_attr)
334
+ Got similar issue as `values`
335
+
336
+ ### [dry-struct](https://github.com/dry-rb/dry-struct)
337
+ Seems more suitable for form objects instead of just value objects (for me)
338
+
339
+
340
+ ## Contributing
341
+
342
+ 1. Fork it ( https://github.com/PikachuEXE/contracted_value/fork )
343
+ 2. Create your branch (Preferred to be prefixed with `feature`/`fix`/other sensible prefixes)
344
+ 3. Commit your changes (No version related changes will be accepted)
345
+ 4. Push to the branch on your forked repo
346
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "appraisal"
4
+ require "rubygems"
5
+ require "bundler/gem_tasks"
6
+ require "rspec/core/rake_task"
7
+
8
+ RSpec::Core::RakeTask.new(:spec)
9
+
10
+ if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
11
+ task :default do
12
+ sh "appraisal install && rake appraisal spec"
13
+ end
14
+ else
15
+ task default: :spec
16
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable all
4
+ $LOAD_PATH.push File.expand_path("../lib", __FILE__)
5
+
6
+ author_name = "PikachuEXE"
7
+ gem_name = "contracted_value"
8
+
9
+ require "#{gem_name}/version"
10
+
11
+ Gem::Specification.new do |s|
12
+ s.platform = Gem::Platform::RUBY
13
+ s.name = gem_name
14
+ s.version = ContractedValue::VERSION
15
+ s.summary = "Some Tweaks for ActiveRecord"
16
+ s.description = <<-DOC
17
+ ActiveRecord is great, but could be better. Here are some tweaks for it.
18
+ DOC
19
+
20
+ s.license = "MIT"
21
+
22
+ s.authors = [author_name]
23
+ s.email = ["pikachuexe@gmail.com"]
24
+ s.homepage = "http://github.com/#{author_name}/#{gem_name}"
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
28
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
29
+ s.require_paths = ["lib"]
30
+
31
+ s.add_dependency "contracts", "~> 0.15"
32
+ s.add_dependency "ice_nine"
33
+
34
+ s.add_development_dependency "bundler", ">= 1.0.0"
35
+ s.add_development_dependency "rake", ">= 10.0", "<= 13.0"
36
+ s.add_development_dependency "pry"
37
+
38
+ s.add_development_dependency "appraisal", "~> 2.0"
39
+
40
+ s.add_development_dependency "rspec", "~> 3.0"
41
+ s.add_development_dependency "rspec-its", "~> 1.0"
42
+
43
+ s.add_development_dependency "simplecov"
44
+ s.add_development_dependency "codecov"
45
+
46
+ s.add_development_dependency "gem-release", ">= 0.7"
47
+
48
+ s.add_development_dependency "inch", "~> 0.6"
49
+
50
+ s.add_development_dependency "rubocop", ">= 0.70"
51
+
52
+ s.required_ruby_version = ">= 2.3.0"
53
+
54
+ s.required_rubygems_version = ">= 1.4.0"
55
+ end
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "contracts", "~> 0.15.0"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "contracts", "~> 0.16.0"
6
+
7
+ gemspec path: "../"