immutability 0.0.1 → 0.0.2

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,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- YTQ3MDdmODU2NDZkZmYxMzJjY2E2OTExYTAzMmRlNzY1NmU5NmU4Mw==
5
- data.tar.gz: !binary |-
6
- MTc4MTk4OGVkOWY5NmQzMTNjNGRlMGRmMzk1ZTQwNzdmYzI5MjhhMQ==
2
+ SHA1:
3
+ metadata.gz: 6d2abac3eae8fc1483402fc718dd899c83c929b1
4
+ data.tar.gz: 6dc9066776003a52641d4f2f0fbefd2a75363628
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- Yzc0NTMyOTM1MmY4ZTllOTMyZmE0N2QzOGFhNzg1YjBiZTE2ZmE1ZGQ4OWU1
10
- N2QzMzVmZjQ2NWU0YzUzYmU1YTU3MTM1ZDE3OTU3OTg0NzhlZDIwYTAyN2Yx
11
- NWU4NzM0ZjI5YWQ5MTRkOWIyOGY2MWM5Y2UxZDliZDE2ZWJkYTE=
12
- data.tar.gz: !binary |-
13
- ODg1NDUxY2UzM2VhOGE0M2E2NDQ0ZjlmMzk4NTRiMmM2NzgxN2RjMGM3MTIw
14
- MjdlY2Q4M2NlYzZkMzU5NzE3MzE3MTFmMWYzNWNkNzcxNTEyMjc5ZmUwM2I4
15
- YjM0NmZhODQ3ODA1MDk3MDczYzBjNTQ4ZDRkYzRmMWVmYzFjNDM=
6
+ metadata.gz: 8639bcd519dc339e392c5fc1b0614aeec9a9008a3457cba1c7adb8b24b0e918d3b4c669193b34a74ee945bbf7293869f58def5cb09c2b0530812f1660b6902aa
7
+ data.tar.gz: 4f4e84e41a2933701d3858a3d6293c2b8a74e7e7fc208247c02f67b2d98a2155e1b1db5566801627e98515c203129fd3fe04aca8066e3f92c39051e168daac43
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## v0.0.2 2015-09-24
2
+
3
+ The version fixes some bugs and adds `#at` method fild past states of objects with memory.
4
+
5
+ ### Added
6
+
7
+ - New decorator `Immutabitlity::Object` to iterate via past states of instance (nepalez)
8
+ - New method `Imutability::WithMemory#at` to return a state in the past (nepalez)
9
+ - Allow block to be send to initializer (nepalez)
10
+
11
+ ### Bugs fixed
12
+
13
+ - Bug in rspec matcher `be_immutable` under rbx (nepalez)
14
+
15
+ [Compare v0.0.1...v0.0.2](https://github.com/nepalez/immutability/compare/v0.0.1...v0.0.2)
16
+
1
17
  ## v0.0.1 2015-09-20
2
18
 
3
19
  First public release.
data/README.md CHANGED
@@ -108,6 +108,25 @@ elder_andrew.version # => 1
108
108
  elder_andrew.parent.equal? young_andrew # => true
109
109
  ```
110
110
 
111
+ You can check the previous state of the object using method `#at`:
112
+
113
+ ```ruby
114
+ # relative from the current version
115
+ elder_andrew.at(-2) == nil # => true
116
+ elder_andrew.at(-1) == young_andrew # => true
117
+
118
+ # at some version in the past
119
+ elder_andrew.at(0) == young_andrew # => true
120
+ elder_andrew.at(1) == elder_andrew # => true
121
+ elder_andrew.at(2) == nil # => true
122
+ ```
123
+
124
+ This can be used to check whether two instances has a [cenancestor][cenancestor]:
125
+
126
+ ```ruby
127
+ elder_andrew.at(0) == young_andrew.at(0) # => true
128
+ ```
129
+
111
130
  Notice, than no instances in the sequence can be garbage collected (they still refer to each other).
112
131
 
113
132
  Use `#forget_history` methods to reset version and free old instances for GC:
@@ -187,6 +206,7 @@ See the [MIT LICENSE](LICENSE).
187
206
 
188
207
  [are_we_there_yet]: http://www.infoq.com/presentations/Are-We-There-Yet-Rich-Hickey
189
208
  [aversion]: https://github.com/txus/aversion
209
+ [cenancestor]: https://en.wikipedia.org/wiki/Last_universal_ancestor
190
210
  [codeclimate]: https://codeclimate.com/github/nepalez/immutability
191
211
  [coveralls]: https://coveralls.io/r/nepalez/immutability
192
212
  [gem]: https://rubygems.org/gems/immutability
data/Rakefile CHANGED
@@ -22,12 +22,13 @@ end
22
22
 
23
23
  desc "Runs mutation metric for testing"
24
24
  task :mutant do
25
- system "mutant -r immutability --use rspec Immutability* --fail-fast"
25
+ system "MUTANT=true mutant -r immutability --use rspec Immutability*" \
26
+ " --fail-fast"
26
27
  end
27
28
 
28
29
  desc "Exhort all evils"
29
30
  task :exhort do
30
- system "mutant -r immutability --use rspec Immutability*"
31
+ system "MUTANT=true mutant -r immutability --use rspec Immutability*"
31
32
  end
32
33
 
33
34
  desc "Runs all the necessary metrics before making a commit"
data/lib/immutability.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "ice_nine"
4
4
 
5
+ require_relative "immutability/object"
5
6
  require_relative "immutability/with_memory"
6
7
 
7
8
  # Makes the object immutable (deeply frozen) with possibility to remember
@@ -78,20 +79,13 @@ module Immutability
78
79
  # @api private
79
80
  #
80
81
  # @param [Object, Array<Object>] args
82
+ # @param [Proc] block
81
83
  #
82
84
  # @return [Object]
83
85
  #
84
- def new(*args)
85
- IceNine.deep_freeze __new__(*args)
86
- end
87
-
88
- private
89
-
90
- def __new__(*args, &block)
91
- allocate.tap do |instance|
92
- instance.__send__(:initialize, *args)
93
- instance.instance_eval(&block) if block_given?
94
- end
86
+ def new(*args, &block)
87
+ instance = allocate.tap { |obj| obj.__send__(:initialize, *args, &block) }
88
+ IceNine.deep_freeze(instance)
95
89
  end
96
90
 
97
91
  end # module ClassMethods
@@ -105,14 +99,7 @@ module Immutability
105
99
  # @return [Object] the updated instance
106
100
  #
107
101
  def update(&block)
108
- instance = dup
109
- instance.instance_eval(&block) if block_given?
110
- IceNine.deep_freeze(instance)
111
- end
112
-
113
- # @private
114
- def self.included(klass)
115
- klass.instance_exec(ClassMethods) { |mod| extend(mod) }
102
+ IceNine.deep_freeze dup.tap { |obj| obj.instance_eval(&block) if block }
116
103
  end
117
104
 
118
105
  # Returns the module extended by features to record history
@@ -123,4 +110,10 @@ module Immutability
123
110
  WithMemory
124
111
  end
125
112
 
113
+ private
114
+
115
+ def self.included(klass)
116
+ klass.instance_eval { extend ClassMethods }
117
+ end
118
+
126
119
  end # module Immutability
@@ -0,0 +1,65 @@
1
+ # encoding: utf-8
2
+ module Immutability
3
+
4
+ # Describes the continuous object as a sequence of immutable snapshots
5
+ # with an option of searching the past state of the object
6
+ #
7
+ # @api private
8
+ #
9
+ # @author Andrew Kozin <Andrew.Kozin@gmail.com>
10
+ #
11
+ class Object
12
+
13
+ include Enumerable
14
+
15
+ # Initializes the object from the current state (snapshot)
16
+ #
17
+ # @param [#version, #parent] current
18
+ #
19
+ def initialize(current)
20
+ @current = current
21
+ end
22
+
23
+ # The current (last) version of the object
24
+ #
25
+ # The object knows nothing about its future
26
+ #
27
+ # @return [Integer]
28
+ #
29
+ def version
30
+ @current.version
31
+ end
32
+
33
+ # Iterates via object's snapshots from the current state to the past
34
+ #
35
+ # @return [Enumerator]
36
+ #
37
+ def each
38
+ return to_enum unless block_given?
39
+ state = @current
40
+ while state
41
+ yield(state)
42
+ state = state.parent
43
+ end
44
+ end
45
+
46
+ # Returns the state of the object at some point in the past
47
+ #
48
+ # @param [#to_i] point
49
+ # Either a positive number of target version,
50
+ # or a negative number of version (snapshots) before the current one
51
+ # +0+ stands for the first version.
52
+ #
53
+ # @return [Object, nil]
54
+ #
55
+ def at(point)
56
+ ipoint = point.to_i
57
+ target = (ipoint < 0) ? (version + ipoint) : ipoint
58
+ return unless (0..version).include? target
59
+
60
+ detect { |state| target.equal? state.version }
61
+ end
62
+
63
+ end # class Object
64
+
65
+ end # module Immutability
@@ -4,6 +4,6 @@ module Immutability
4
4
 
5
5
  # The semantic version of the gem.
6
6
  # @see http://semver.org/ Semantic versioning 2.0
7
- VERSION = "0.0.1".freeze
7
+ VERSION = "0.0.2".freeze
8
8
 
9
9
  end # module Immutability
@@ -8,39 +8,34 @@ module Immutability
8
8
  #
9
9
  module WithMemory
10
10
 
11
- # @private
12
- class << self
13
- private
11
+ include Immutability
14
12
 
15
- def included(klass)
16
- klass.__send__ :include, Immutability
17
- klass.__send__ :extend, ClassMethods
18
- klass.__send__ :define_method, :update, update
19
- end
13
+ # Methods to be added to class, that included the `Immutability` module
14
+ #
15
+ # @api private
16
+ #
17
+ module ClassMethods
20
18
 
21
- # Redefines the +update+ so that it increment version and refer
22
- # to the previous snapshot of the continuous object
19
+ # Reloads instance's constructor to add version and parent and make
20
+ # the whole instance immutable
21
+ #
22
+ # @api private
23
+ #
24
+ # @param [Object, Array<Object>] args
25
+ # @param [Proc] block
23
26
  #
24
- def update
25
- proc do |&block|
26
- current = [(version + 1), self]
27
- super() do
28
- @version, @parent = current
29
- instance_eval(&block) if block
30
- end
27
+ # @return [Object]
28
+ #
29
+ def new(*args, &block)
30
+ instance = allocate.tap do |obj|
31
+ obj.__send__(:initialize, *args, &block)
32
+ obj.instance_variable_set(:@version, 0)
33
+ obj.instance_variable_set(:@parent, nil)
31
34
  end
35
+ IceNine.deep_freeze(instance)
32
36
  end
33
- end
34
37
 
35
- # Adds version and parent variables to newly created instance
36
- #
37
- module ClassMethods
38
- private
39
-
40
- def __new__(*args)
41
- super(*args) { @version, @parent = 0 }
42
- end
43
- end
38
+ end # module ClassMethods
44
39
 
45
40
  # @!attribute [r] version
46
41
  #
@@ -54,6 +49,19 @@ module Immutability
54
49
  #
55
50
  attr_reader :parent
56
51
 
52
+ # Redefines the +update+ so that it increment version and refer
53
+ # to the previous snapshot of the continuous object
54
+ #
55
+ # @param [Proc] block
56
+ #
57
+ def update(&block)
58
+ current = [version + 1, self]
59
+ super do
60
+ @version, @parent = current
61
+ instance_eval(&block) if block
62
+ end
63
+ end
64
+
57
65
  # Forgets the previous history of the object
58
66
  #
59
67
  # Returns a new instance with the same variables,
@@ -65,6 +73,23 @@ module Immutability
65
73
  update { @version, @parent = 0 }
66
74
  end
67
75
 
76
+ # Returns an ancestor of the instance at some point in the past
77
+ #
78
+ # @param [#to_i] point
79
+ # Either a positive number of version, or a negative number of versions
80
+ # (snapshots) before now. +0+ stands for the first version.
81
+ #
82
+ # @return [Object, nil]
83
+ #
84
+ def at(point)
85
+ Object.new(self).at(point)
86
+ end
87
+
88
+ # @private
89
+ def self.included(klass)
90
+ klass.instance_eval { extend ClassMethods }
91
+ end
92
+
68
93
  end # module WithMemory
69
94
 
70
95
  end # module Immutability
data/spec/shared/user.rb CHANGED
@@ -3,11 +3,12 @@
3
3
  shared_examples :user do
4
4
  before do
5
5
  class User
6
- attr_reader :name, :age
6
+ attr_reader :name, :age, :role
7
7
 
8
8
  def initialize(name, age)
9
9
  @name = name
10
10
  @age = age
11
+ @role = block_given? ? yield : nil
11
12
  end
12
13
  end
13
14
  end
data/spec/spec_helper.rb CHANGED
@@ -16,3 +16,11 @@ require "immutability/rspec"
16
16
 
17
17
  # Loads shared examples
18
18
  require_relative "shared/user"
19
+
20
+ # @todo Remove after resolving of mutant PR#444
21
+ # @see https://github.com/mbj/mutant/issues/444
22
+ if ENV["MUTANT"]
23
+ RSpec.configure do |config|
24
+ config.around { |example| Timeout.timeout(0.5, &example) }
25
+ end
26
+ end
@@ -9,7 +9,7 @@
9
9
  #
10
10
  RSpec::Matchers.define :be_immutable do
11
11
  match do |instance|
12
- if instance.class.respond_to? :new
12
+ if instance && instance.class.respond_to?(:new)
13
13
  expect(instance).to be_frozen
14
14
  instance
15
15
  .instance_variables.map(&instance.method(:instance_variable_get))
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+ describe Immutability::Object do
3
+ let(:object) { described_class.new(instance) }
4
+
5
+ let(:instance) { double :instance, version: 2, parent: parent }
6
+ let(:parent) { double :parent, version: 1, parent: grandparent }
7
+ let(:grandparent) { double :grandparent, version: 0, parent: nil }
8
+
9
+ describe "#version" do
10
+ subject { object.version }
11
+
12
+ it "is delegated to instance" do
13
+ expect(subject).to eql instance.version
14
+ end
15
+ end # describe #version
16
+
17
+ describe "#each" do
18
+ context "without block" do
19
+ subject { object.each }
20
+
21
+ it { is_expected.to be_kind_of Enumerator }
22
+ end
23
+
24
+ context "with a block" do
25
+ subject { object.to_a }
26
+
27
+ it "iterates via ancestors" do
28
+ expect(subject).to eql [instance, parent, grandparent]
29
+ end
30
+ end
31
+ end # describe #each
32
+
33
+ describe "#at" do
34
+ subject { object.at(point) }
35
+
36
+ context "0" do
37
+ let(:point) { 0 }
38
+
39
+ it { is_expected.to eql grandparent }
40
+ end
41
+
42
+ context "positive number less than version" do
43
+ let(:point) { "1" }
44
+
45
+ it { is_expected.to eql parent }
46
+ end
47
+
48
+ context "positive number equal to version" do
49
+ let(:point) { 2 }
50
+
51
+ it { is_expected.to eql instance }
52
+ end
53
+
54
+ context "positive number greater than version" do
55
+ let(:point) { 3 }
56
+
57
+ it { is_expected.to be_nil }
58
+
59
+ it "doesn't check #parent" do
60
+ expect(instance).not_to receive(:parent)
61
+ subject
62
+ end
63
+ end
64
+
65
+ context "negative number less than version" do
66
+ let(:point) { -1 }
67
+
68
+ it { is_expected.to eql parent }
69
+ end
70
+
71
+ context "negative number equal to version" do
72
+ let(:point) { -2 }
73
+
74
+ it { is_expected.to eql grandparent }
75
+ end
76
+
77
+ context "negative number greater than version" do
78
+ let(:point) { -3 }
79
+
80
+ it { is_expected.to be_nil }
81
+
82
+ it "doesn't check #parent" do
83
+ expect(instance).not_to receive(:parent)
84
+ subject
85
+ end
86
+ end
87
+ end # describe #at
88
+
89
+ end # describe Immutability::Object
@@ -4,16 +4,22 @@ describe Immutability::WithMemory do
4
4
  include_context :user
5
5
  before { User.send :include, described_class }
6
6
 
7
- let(:user) { User.new "Andrew", 44 }
7
+ let(:user) { User.new("Andrew", 44) { "admin" } }
8
8
 
9
9
  describe ".new" do
10
10
  subject { user }
11
11
 
12
+ it "calls the initializer" do
13
+ expect(subject.name).to eql "Andrew"
14
+ expect(subject.age).to eql 44
15
+ expect(subject.role).to eql "admin"
16
+ end
17
+
12
18
  it { is_expected.to be_immutable }
13
19
 
14
20
  it "doesn't add hidden variables" do
15
21
  expect(subject.instance_variables).to contain_exactly(
16
- :@name, :@age, :@version, :@parent
22
+ :@name, :@age, :@role, :@version, :@parent
17
23
  )
18
24
  end
19
25
  end # describe .new
@@ -23,7 +29,7 @@ describe Immutability::WithMemory do
23
29
 
24
30
  it "are expected" do
25
31
  expect(subject).to contain_exactly(
26
- :name, :age, :version, :parent, :update, :forget_history
32
+ :name, :age, :role, :version, :parent, :update, :forget_history, :at
27
33
  )
28
34
  end
29
35
  end # describe #methods
@@ -98,4 +104,43 @@ describe Immutability::WithMemory do
98
104
  end
99
105
  end # describe #forget_history
100
106
 
107
+ describe "#at" do
108
+ subject { three.at(point) }
109
+
110
+ let(:zero) { user }
111
+ let(:one) { zero.update }
112
+ let(:two) { one.update }
113
+ let(:three) { two.update }
114
+
115
+ context "0" do
116
+ let(:point) { 0 }
117
+
118
+ it { is_expected.to eql zero }
119
+ end
120
+
121
+ context "positive number less than version" do
122
+ let(:point) { 2 }
123
+
124
+ it { is_expected.to eql two }
125
+ end
126
+
127
+ context "positive number greater than version" do
128
+ let(:point) { 4 }
129
+
130
+ it { is_expected.to be_nil }
131
+ end
132
+
133
+ context "negative number less than version" do
134
+ let(:point) { -2 }
135
+
136
+ it { is_expected.to eql one }
137
+ end
138
+
139
+ context "negative number greater than version" do
140
+ let(:point) { -4 }
141
+
142
+ it { is_expected.to be_nil }
143
+ end
144
+ end # describe #at
145
+
101
146
  end # describe Immutability::WithMemory
@@ -4,7 +4,7 @@ describe Immutability do
4
4
  include_context :user
5
5
  before { User.send :include, described_class }
6
6
 
7
- let(:user) { User.new name, 44 }
7
+ let(:user) { User.new(name, 44) { "admin" } }
8
8
  let(:name) { "Andrew" }
9
9
 
10
10
  describe "::with_memory" do
@@ -16,13 +16,19 @@ describe Immutability do
16
16
  describe ".new" do
17
17
  subject { user }
18
18
 
19
+ it "calls the initializer" do
20
+ expect(subject.name).to eql name
21
+ expect(subject.age).to eql 44
22
+ expect(subject.role).to eql "admin"
23
+ end
24
+
19
25
  it { is_expected.to be_immutable }
20
26
  end # describe .new
21
27
 
22
28
  describe "#methods" do
23
29
  subject { user.methods - Object.instance_methods }
24
30
 
25
- it { is_expected.to contain_exactly(:name, :age, :update) }
31
+ it { is_expected.to contain_exactly(:name, :age, :role, :update) }
26
32
  end # describe #methods
27
33
 
28
34
  describe "#update" do
metadata CHANGED
@@ -1,41 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: immutability
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kozin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-20 00:00:00.000000000 Z
11
+ date: 2015-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ice_nine
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: 0.11.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.11.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: hexx-rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0.5'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.5'
41
41
  description:
@@ -46,13 +46,13 @@ extra_rdoc_files:
46
46
  - README.md
47
47
  - LICENSE
48
48
  files:
49
- - .coveralls.yml
50
- - .gitignore
51
- - .metrics
52
- - .rspec
53
- - .rubocop.yml
54
- - .travis.yml
55
- - .yardopts
49
+ - ".coveralls.yml"
50
+ - ".gitignore"
51
+ - ".metrics"
52
+ - ".rspec"
53
+ - ".rubocop.yml"
54
+ - ".travis.yml"
55
+ - ".yardopts"
56
56
  - CHANGELOG.md
57
57
  - Gemfile
58
58
  - Guardfile
@@ -72,12 +72,14 @@ files:
72
72
  - config/metrics/yardstick.yml
73
73
  - immutability.gemspec
74
74
  - lib/immutability.rb
75
+ - lib/immutability/object.rb
75
76
  - lib/immutability/rspec.rb
76
77
  - lib/immutability/version.rb
77
78
  - lib/immutability/with_memory.rb
78
79
  - spec/shared/user.rb
79
80
  - spec/spec_helper.rb
80
81
  - spec/support/immutable.rb
82
+ - spec/unit/immutability/object_spec.rb
81
83
  - spec/unit/immutability/with_memory_spec.rb
82
84
  - spec/unit/immutability_spec.rb
83
85
  homepage: https://github.com/nepalez/immutability
@@ -90,23 +92,24 @@ require_paths:
90
92
  - lib
91
93
  required_ruby_version: !ruby/object:Gem::Requirement
92
94
  requirements:
93
- - - ! '>='
95
+ - - ">="
94
96
  - !ruby/object:Gem::Version
95
97
  version: 1.9.3
96
98
  required_rubygems_version: !ruby/object:Gem::Requirement
97
99
  requirements:
98
- - - ! '>='
100
+ - - ">="
99
101
  - !ruby/object:Gem::Version
100
102
  version: '0'
101
103
  requirements: []
102
104
  rubyforge_project:
103
- rubygems_version: 2.4.6
105
+ rubygems_version: 2.4.8
104
106
  signing_key:
105
107
  specification_version: 4
106
108
  summary: Make instances immutable (deeply frozen) and versioned
107
109
  test_files:
108
110
  - spec/spec_helper.rb
109
111
  - spec/unit/immutability_spec.rb
112
+ - spec/unit/immutability/object_spec.rb
110
113
  - spec/unit/immutability/with_memory_spec.rb
111
114
  - spec/support/immutable.rb
112
115
  - spec/shared/user.rb