xavier 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 497980d580ff1c8ec587dd58c3e645cf3a3544c4
4
+ data.tar.gz: 6b36d63bbafa06107025eb9072f24f987638f4bf
5
+ SHA512:
6
+ metadata.gz: ca1e35db421d915af1e168df52aba56d49958633abb3513182709b2e97a376e17b8fd5e28a0e2339684c29f365995340358d71fae0d88d3cd335febfdf9f0907
7
+ data.tar.gz: c130c40a35ac7684a5bcadaa4d843fc98a2cfe89bc9da55faa9668b794ef5fe9ec0cf7eb334954a22f58808450d1c2b101b2d569353c183c09ac5e82d133ecf1
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /measurements
10
+
11
+ # rspec failure tracking
12
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,41 @@
1
+ require: rubocop-rspec
2
+
3
+ AllCops:
4
+ DisplayCopNames: true
5
+
6
+ # ---------------------- Layout -----------------------
7
+
8
+ Layout/SpaceInsideHashLiteralBraces:
9
+ Enabled: false
10
+
11
+ # ---------------------- Metrics ----------------------
12
+
13
+ Metrics/BlockLength:
14
+ Exclude:
15
+ - spec/**/*_spec.rb
16
+ - xavier.gemspec
17
+
18
+ Metrics/LineLength:
19
+ Max: 120
20
+
21
+ # ----------------------- RSpec -----------------------
22
+
23
+ RSpec/NestedGroups:
24
+ Enabled: false
25
+
26
+ # ----------------------- Style -----------------------
27
+
28
+ Style/FrozenStringLiteralComment:
29
+ Exclude:
30
+ - spec/**/*.rb
31
+
32
+ Style/ClassVars:
33
+ Exclude:
34
+ - spec/support/*.rb
35
+
36
+ # ----------------------- Naming ----------------------
37
+
38
+ Naming/UncommunicativeMethodParamName:
39
+ Exclude:
40
+ - lib/xavier/mutation_strategies/*.rb
41
+ - lib/xavier/mutator.rb
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.2
5
+ before_install: gem install bundler -v 1.16.0
@@ -0,0 +1 @@
1
+ --plugin junk
@@ -0,0 +1,31 @@
1
+ threshold: 100
2
+ rules:
3
+ ApiTag::Presence:
4
+ enabled: true
5
+ exclude: []
6
+ ApiTag::Inclusion:
7
+ enabled: true
8
+ exclude: []
9
+ ApiTag::ProtectedMethod:
10
+ enabled: true
11
+ exclude: []
12
+ ApiTag::PrivateMethod:
13
+ enabled: true
14
+ exclude: []
15
+ ExampleTag:
16
+ enabled: true
17
+ exclude: []
18
+ ReturnTag:
19
+ enabled: true
20
+ exclude: []
21
+ Summary::Presence:
22
+ enabled: true
23
+ exclude: []
24
+ Summary::Length:
25
+ enabled: false
26
+ Summary::Delimiter:
27
+ enabled: false
28
+ exclude: []
29
+ Summary::SingleLine:
30
+ enabled: true
31
+ exclude: []
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
+ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+ Nothing.
9
+
10
+ ## 0.1.0 - 2018-03-17
11
+ ### Added
12
+ - Initial version of the gem.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
6
+
7
+ # Specify your gem's dependencies in xavier.gemspec
8
+ gemspec
@@ -0,0 +1,73 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ xavier (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ ast (2.4.0)
10
+ backports (3.11.1)
11
+ coderay (1.1.2)
12
+ diff-lcs (1.3)
13
+ method_source (0.9.0)
14
+ parallel (1.12.1)
15
+ parser (2.5.0.4)
16
+ ast (~> 2.4.0)
17
+ powerpack (0.1.1)
18
+ pry (0.11.3)
19
+ coderay (~> 1.1.0)
20
+ method_source (~> 0.9.0)
21
+ rainbow (3.0.0)
22
+ rake (10.5.0)
23
+ rspec (3.7.0)
24
+ rspec-core (~> 3.7.0)
25
+ rspec-expectations (~> 3.7.0)
26
+ rspec-mocks (~> 3.7.0)
27
+ rspec-core (3.7.1)
28
+ rspec-support (~> 3.7.0)
29
+ rspec-expectations (3.7.0)
30
+ diff-lcs (>= 1.2.0, < 2.0)
31
+ rspec-support (~> 3.7.0)
32
+ rspec-mocks (3.7.0)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.7.0)
35
+ rspec-support (3.7.1)
36
+ rubocop (0.53.0)
37
+ parallel (~> 1.10)
38
+ parser (>= 2.5)
39
+ powerpack (~> 0.1)
40
+ rainbow (>= 2.2.2, < 4.0)
41
+ ruby-progressbar (~> 1.7)
42
+ unicode-display_width (~> 1.0, >= 1.0.1)
43
+ rubocop-rspec (1.24.0)
44
+ rubocop (>= 0.53.0)
45
+ ruby-progressbar (1.9.0)
46
+ tty-color (0.4.2)
47
+ unicode-display_width (1.3.0)
48
+ yard (0.9.12)
49
+ yard-junk (0.0.7)
50
+ backports
51
+ rainbow
52
+ tty-color
53
+ yard
54
+ yardstick (0.9.9)
55
+ yard (~> 0.8, >= 0.8.7.2)
56
+
57
+ PLATFORMS
58
+ ruby
59
+
60
+ DEPENDENCIES
61
+ bundler (~> 1.16)
62
+ pry (~> 0.11)
63
+ rake (~> 10.0)
64
+ rspec (~> 3.7)
65
+ rubocop (~> 0.53)
66
+ rubocop-rspec (~> 1.24)
67
+ xavier!
68
+ yard (~> 0.9)
69
+ yard-junk (~> 0.0.7)
70
+ yardstick (~> 0.9)
71
+
72
+ BUNDLED WITH
73
+ 1.16.0
@@ -0,0 +1,92 @@
1
+ # Xavier
2
+
3
+ Xavier tracks and reverts state mutations (changes in `instance`, `class`, and `class instance` variables).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'xavier'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install xavier
20
+
21
+ ## Usage
22
+
23
+ Observing a class:
24
+
25
+ ```ruby
26
+ class EvilSingleton
27
+ @@mutated = false
28
+ @mutated = false
29
+
30
+ def self.mutate
31
+ @@mutated = true
32
+ @mutated = true
33
+ end
34
+
35
+ def self.mutated?
36
+ @@mutated && @mutated
37
+ end
38
+ end
39
+
40
+ Xavier.observe(EvilSingleton) do
41
+ EvilSingleton.mutated? # => false
42
+ EvilSingleton.mutate
43
+ EvilSingleton.mutated? # => true
44
+ end
45
+
46
+ EvilSingleton.mutated? # => false
47
+ ```
48
+
49
+ Observing an instance:
50
+
51
+ ```ruby
52
+ class InstanceSingleton
53
+ def initialize
54
+ @mutated = false
55
+ end
56
+
57
+ def mutate
58
+ @mutated = true
59
+ end
60
+
61
+ def mutated?
62
+ @mutated
63
+ end
64
+ end
65
+
66
+ evil_singleton = InstanceSingleton.new
67
+
68
+ Xavier.observe(evil_singleton) do
69
+ evil_singleton.mutated? # => false
70
+ evil_singleton.mutate
71
+ evil_singleton.mutated? # => true
72
+ end
73
+
74
+ evil_singleton.mutated? # => false
75
+ ```
76
+
77
+ ## Development
78
+
79
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
80
+ You can also run:
81
+ * `bin/console` for an interactive prompt that will allow you to experiment.
82
+ * `rake rubocop` to lint the code.
83
+ * `rake verify_measurements` to generate a report of the Yard documentation.
84
+ * `rake yard:junk` to lint the Yard documentation.
85
+
86
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
87
+ version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
88
+ push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
89
+
90
+ ## Contributing
91
+
92
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/xavier.
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'yard/rake/yardoc_task'
6
+ require 'yard-junk/rake'
7
+ require 'yardstick/rake/measurement'
8
+ require 'yardstick/rake/verify'
9
+
10
+ yardstick_options = YAML.load_file('.yardstick.yml')
11
+
12
+ RSpec::Core::RakeTask.new(:spec)
13
+ YARD::Rake::YardocTask.new
14
+ YardJunk::Rake.define_task
15
+ Yardstick::Rake::Measurement.new(:yardstick_measure, yardstick_options)
16
+ Yardstick::Rake::Verify.new
17
+
18
+ # Remove the report on rake clobber
19
+ CLEAN.include('measurements')
20
+
21
+ task default: :spec
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'xavier'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xavier/observer'
4
+ require 'xavier/version'
5
+
6
+ # Wraps the gem logic in an accessible way.
7
+ module Xavier
8
+ # Raised when attempting to observe a class or instance already under observation.
9
+ AlreadyObserved = Class.new(RuntimeError)
10
+
11
+ # Observes an object, yields a block and then reverts the observed object's class and instance variables.
12
+ #
13
+ # @example Observing a class
14
+ # class EvilSingleton
15
+ # @@mutated = false
16
+ # @mutated = false
17
+ #
18
+ # def self.mutate
19
+ # @@mutated = true
20
+ # @mutated = true
21
+ # end
22
+ #
23
+ # def self.mutated?
24
+ # @@mutated && @mutated
25
+ # end
26
+ # end
27
+ #
28
+ # Xavier.observe(EvilSingleton) do
29
+ # EvilSingleton.mutated? # => false
30
+ # EvilSingleton.mutate
31
+ # EvilSingleton.mutated? # => true
32
+ # end
33
+ #
34
+ # EvilSingleton.mutated? # => false
35
+ #
36
+ # @example Observing an instance
37
+ # class InstanceSingleton
38
+ # def initialize
39
+ # @mutated = false
40
+ # end
41
+ #
42
+ # def mutate
43
+ # @mutated = true
44
+ # end
45
+ #
46
+ # def mutated?
47
+ # @mutated
48
+ # end
49
+ # end
50
+ #
51
+ # evil_singleton = InstanceSingleton.new
52
+ #
53
+ # Xavier.observe(evil_singleton) do
54
+ # evil_singleton.mutated? # => false
55
+ # evil_singleton.mutate
56
+ # evil_singleton.mutated? # => true
57
+ # end
58
+ #
59
+ # evil_singleton.mutated? # => false
60
+ #
61
+ # @raise [AlreadyObserved] When attempting to observe the same object twice before the block returning.
62
+ #
63
+ # @param observable The object whose state should be observed. It can be a class or an instance.
64
+ #
65
+ # @yield The block to be executed before the observable's state is reverted.
66
+ #
67
+ # @return [Integer] The object_id of the observable.
68
+ #
69
+ # @api public
70
+ def self.observe(observable, &block)
71
+ @observer ||= Observer.new
72
+ @observer.observe(observable, &block)
73
+ end
74
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xavier
4
+ module MutationStrategies
5
+ # Copies the class variables from one object to other.
6
+ #
7
+ # @api private
8
+ module ClassCopy
9
+ # Copies the class variables from one object to other.
10
+ #
11
+ # @param from An object where the state will be copied from.
12
+ # @param to An object where the state will be copied to.
13
+ #
14
+ # @return [Array<String>] A list of variable names that were copied.
15
+ def self.copy(from:, to:)
16
+ vars = from.class_variables.map(&:to_s)
17
+ vars.each { |name| to.class_variable_set(name, from.class_variable_get(name)) }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xavier
4
+ module MutationStrategies
5
+ # Copies the instance variables from one object to other.
6
+ #
7
+ # @api private
8
+ module InstanceCopy
9
+ # Copies the instance variables from one object to other.
10
+ #
11
+ # @param from An object where the state will be copied from.
12
+ # @param to An object where the state will be copied to.
13
+ #
14
+ # @return [Array<String>] A list of variable names that were copied.
15
+ def self.copy(from:, to:)
16
+ vars = from.instance_variables.map(&:to_s)
17
+ vars.each { |name| to.instance_variable_set(name, from.instance_variable_get(name)) }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xavier/state'
4
+ require 'xavier/mutation_strategies/class_copy'
5
+ require 'xavier/mutation_strategies/instance_copy'
6
+
7
+ module Xavier
8
+ # Applies and unapplies state mutations to objects.
9
+ #
10
+ # @api private
11
+ class Mutator
12
+ # Returns a state representation from a given +observable+.
13
+ #
14
+ # @param observable The class or instance to copy the state from.
15
+ # @param [Array] strategies Array of mutation mutation_strategies that define how the state should be copied.
16
+ #
17
+ # @return [State] The state representation of the given +observable+
18
+ def create_state_from(observable, strategies:)
19
+ state = State.new(observable.object_id)
20
+ strategies.each { |strategy| strategy.copy(from: observable, to: state) }
21
+ state
22
+ end
23
+
24
+ # Applies the given +state+ to the given +observable+.
25
+ #
26
+ # @param from An object where the state will be copied from.
27
+ # @param to An object where the state will be copied to.
28
+ # @param [Array] strategies Array of mutation mutation_strategies that define how the state should be copied.
29
+ #
30
+ # @return [Array] An array of mutation_strategies.
31
+ def apply_state(from:, to:, strategies:)
32
+ strategies.each { |strategy| strategy.copy(from: from, to: to) }
33
+ end
34
+
35
+ # Finds the mutation mutation_strategies for a given +observable+.
36
+ #
37
+ # @param observable Any class or instance
38
+ #
39
+ # @return [Array] An array of mutation mutation_strategies that define how the state should be copied.
40
+ def mutation_strategies_for(observable)
41
+ [MutationStrategies::InstanceCopy].tap do |strategies|
42
+ strategies.push(MutationStrategies::ClassCopy) if observable.is_a?(Class)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xavier/mutator'
4
+ require 'xavier/states'
5
+
6
+ module Xavier
7
+ # Observes an object for state mutations.
8
+ #
9
+ # @api private
10
+ class Observer
11
+ # Creates an instance of +Observer+.
12
+ #
13
+ # @param [Mutator] mutator Applies and unapplies state modifications.
14
+ # @param [States] states States of objects under observation. Empty by default.
15
+ def initialize(mutator = Mutator.new, states = States.new)
16
+ @states = states
17
+ @mutator = mutator
18
+ end
19
+
20
+ # Observes an object, yields a block and then reverts the observed object's class and instance variables.
21
+ #
22
+ # @example Observing a class
23
+ # class EvilSingleton
24
+ # @@mutated = false
25
+ # @mutated = false
26
+ #
27
+ # def self.mutate
28
+ # @@mutated = true
29
+ # @mutated = true
30
+ # end
31
+ #
32
+ # def self.mutated?
33
+ # @@mutated && @mutated
34
+ # end
35
+ # end
36
+ #
37
+ # Xavier.observe(EvilSingleton) do
38
+ # EvilSingleton.mutated? # => false
39
+ # EvilSingleton.mutate
40
+ # EvilSingleton.mutated? # => true
41
+ # end
42
+ #
43
+ # EvilSingleton.mutated? # => false
44
+ #
45
+ # @example Observing an instance
46
+ # class InstanceSingleton
47
+ # def initialize
48
+ # @mutated = false
49
+ # end
50
+ #
51
+ # def mutate
52
+ # @mutated = true
53
+ # end
54
+ #
55
+ # def mutated?
56
+ # @mutated
57
+ # end
58
+ # end
59
+ #
60
+ # evil_singleton = InstanceSingleton.new
61
+ #
62
+ # Xavier.observe(evil_singleton) do
63
+ # evil_singleton.mutated? # => false
64
+ # evil_singleton.mutate
65
+ # evil_singleton.mutated? # => true
66
+ # end
67
+ #
68
+ # evil_singleton.mutated? # => false
69
+ #
70
+ # @raise [AlreadyObserved] When attempting to observe the same object twice before the block returning.
71
+ #
72
+ # @param observable The object whose state should be observed. It can be a class or an instance.
73
+ #
74
+ # @yield The block to be executed before the observable's state is reverted.
75
+ #
76
+ # @return [Integer] The object_id of the observable.
77
+ #
78
+ # @api private
79
+ def observe(observable)
80
+ raise ArgumentError, 'This method expects a block. Without a block it is useless.' unless block_given?
81
+ raise AlreadyObserved, 'Objects can only be observed once per block.' if being_observed?(observable)
82
+
83
+ strategies = mutator.mutation_strategies_for(observable)
84
+ original_state = mutator.create_state_from(observable, strategies: strategies)
85
+ save_state(original_state)
86
+
87
+ yield
88
+
89
+ mutator.apply_state(from: original_state, to: observable, strategies: strategies)
90
+ delete_state(original_state)
91
+ end
92
+
93
+ private
94
+
95
+ # Returns the state mutator.
96
+ #
97
+ # @return [Xavier::Mutator] Applies and unapplies state modifications.
98
+ attr_reader :mutator
99
+
100
+ # Returns the observed objects states.
101
+ #
102
+ # @return [Xavier::States] States of objects under observation. Used to revert the object to its original state.
103
+ attr_reader :states
104
+
105
+ # Returns whether the given +observable+ is already under observation.
106
+ #
107
+ # @param observable A class or instance to be checked.
108
+ #
109
+ # @return [Boolean] Whether the object is being observed.
110
+ def being_observed?(observable)
111
+ states.contain?(observable.object_id)
112
+ end
113
+
114
+ # Stores a state representation of an observable.
115
+ #
116
+ # @param [State] state The state to be stored.
117
+ #
118
+ # @return [States] The whole states collection.
119
+ def save_state(state)
120
+ states.add(state)
121
+ end
122
+
123
+ # Deletes the state representation of an observable.
124
+ #
125
+ # @param [State] state State representation of an object under observation.
126
+ #
127
+ # @return [Integer] The object_id of the observable.
128
+ def delete_state(state)
129
+ states.remove(state)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xavier
4
+ # A storage for an observed class or object's internal state.
5
+ # @api private
6
+ class State < BasicObject
7
+ # Returns the object id of the observed object
8
+ #
9
+ # @return [Integer] The +object_id+ of the observed object.
10
+ attr_reader :observed_object_id
11
+
12
+ # Creates a new state representation.
13
+ #
14
+ # @param [Integer] observed_object_id The +object_id+ of the observed object.
15
+ def initialize(observed_object_id)
16
+ @observed_object_id = observed_object_id
17
+ @class_variables = {}
18
+ @instance_variables = {}
19
+ end
20
+
21
+ # Sets the instance variable named by +name+ to the given object.
22
+ #
23
+ # @param [String|Symbol] name Name of the instance variable.
24
+ # @param value Value of the instance variable.
25
+ #
26
+ # @return The value of the given instance variable.
27
+ def instance_variable_set(name, value)
28
+ @instance_variables[name.to_sym] = value
29
+ end
30
+
31
+ # Returns the value of the given instance variable, or +nil+ if the instance variable is not set.
32
+ #
33
+ # @param [String|Symbol] name Name of the instance variable.
34
+ #
35
+ # @return The value of the given instance variable.
36
+ def instance_variable_get(name)
37
+ @instance_variables[name.to_sym]
38
+ end
39
+
40
+ # Sets the class variable named by +name+ to the given object.
41
+ #
42
+ # @param [String|Symbol] name Name of the instance variable.
43
+ # @param value Value of the instance variable.
44
+ #
45
+ # @return The value of the given class variable.
46
+ def class_variable_set(name, value)
47
+ @class_variables[name.to_sym] = value
48
+ end
49
+
50
+ # Returns the value of the given class variable, or nil if the class variable is not set.
51
+ #
52
+ # @param [String|Symbol] name Name of the class variable.
53
+ #
54
+ # @return The value of the given class variable.
55
+ def class_variable_get(name)
56
+ @class_variables[name.to_sym]
57
+ end
58
+
59
+ # Returns true if class is the class of +self+, or if +clazz+ is one of the superclasses of +self+ or modules.
60
+ #
61
+ # @return [Boolean] Whether the given class is the class, superclass or modules of +self+.
62
+ def is_a?(clazz)
63
+ return true if clazz == ::Class
64
+ self.class == clazz
65
+ end
66
+
67
+ # Returns the class +State+.
68
+ #
69
+ # @return [State] The class +State+.
70
+ def class
71
+ State
72
+ end
73
+
74
+ # Returns an array of instance variable names for the receiver.
75
+ #
76
+ # @return [Array<Symbol>] Array of instance variable names.
77
+ def instance_variables
78
+ @instance_variables.keys
79
+ end
80
+
81
+ # Returns an array of the names of class variables in mod.
82
+ #
83
+ # @return [Array<Symbol>] Array of class variable names.
84
+ def class_variables
85
+ @class_variables.keys
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xavier
4
+ # Collection of state representations of objects under observation.
5
+ #
6
+ # @api private
7
+ class States
8
+ # Creates an instance of States.
9
+ def initialize
10
+ @collection = {}
11
+ end
12
+
13
+ # Stores the state representation of an object.
14
+ #
15
+ # @param [State] state State representation of an object under observation.
16
+ #
17
+ # @return [States] The whole states collection.
18
+ def add(state)
19
+ collection[state.observed_object_id] = state
20
+ self
21
+ end
22
+
23
+ # Deletes the state representation of an object.
24
+ #
25
+ # @param [State] state State representation of an object under observation.
26
+ #
27
+ # @return [Integer] The object_id of the observable.
28
+ def remove(state)
29
+ collection.delete(state.observed_object_id)
30
+ end
31
+
32
+ # Returns whether the states collection contains a state representation of an object, using its unique object_id.
33
+ #
34
+ # @param [Integer] object_id The object_id of the observable.
35
+ #
36
+ # @return [Boolean] Whether the states collection contains a state representation of an object.
37
+ def contain?(object_id)
38
+ collection.key?(object_id)
39
+ end
40
+
41
+ private
42
+
43
+ # Returns a hash where the state representations are stored.
44
+ # @return [Hash] A hash where the state representations are stored.
45
+ attr_reader :collection
46
+ end
47
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Xavier
4
+ # The version of the gem
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+
6
+ require 'xavier/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'xavier'
10
+ spec.version = Xavier::VERSION
11
+ spec.authors = ['Wilson Silva']
12
+ spec.email = ['me@wilsonsilva.net']
13
+
14
+ spec.summary = 'Reverts state mutations.'
15
+ spec.description = 'Reverts state mutations.'
16
+ spec.homepage = 'https://github.com/wilsonsilva/xavier'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
19
+ f.match(%r{^(test|spec|features)/})
20
+ end
21
+
22
+ spec.metadata['yard.run'] = 'yri' # use "yard" to build full HTML docs.
23
+
24
+ spec.bindir = 'exe'
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ['lib']
27
+
28
+ spec.add_development_dependency 'bundler', '~> 1.16'
29
+ spec.add_development_dependency 'pry', '~> 0.11'
30
+ spec.add_development_dependency 'rake', '~> 10.0'
31
+ spec.add_development_dependency 'rspec', '~> 3.7'
32
+ spec.add_development_dependency 'rubocop', '~> 0.53'
33
+ spec.add_development_dependency 'rubocop-rspec', '~> 1.24'
34
+ spec.add_development_dependency 'yard', '~> 0.9'
35
+ spec.add_development_dependency 'yard-junk', '~> 0.0.7'
36
+ spec.add_development_dependency 'yardstick', '~> 0.9'
37
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xavier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Wilson Silva
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2018-03-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.16'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.16'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.11'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.7'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.7'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.53'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.53'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.24'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.24'
97
+ - !ruby/object:Gem::Dependency
98
+ name: yard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.9'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
111
+ - !ruby/object:Gem::Dependency
112
+ name: yard-junk
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.0.7
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.0.7
125
+ - !ruby/object:Gem::Dependency
126
+ name: yardstick
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.9'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.9'
139
+ description: Reverts state mutations.
140
+ email:
141
+ - me@wilsonsilva.net
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".rspec"
148
+ - ".rubocop.yml"
149
+ - ".travis.yml"
150
+ - ".yardopts"
151
+ - ".yardstick.yml"
152
+ - CHANGELOG.md
153
+ - Gemfile
154
+ - Gemfile.lock
155
+ - README.md
156
+ - Rakefile
157
+ - bin/console
158
+ - bin/setup
159
+ - lib/xavier.rb
160
+ - lib/xavier/mutation_strategies/class_copy.rb
161
+ - lib/xavier/mutation_strategies/instance_copy.rb
162
+ - lib/xavier/mutator.rb
163
+ - lib/xavier/observer.rb
164
+ - lib/xavier/state.rb
165
+ - lib/xavier/states.rb
166
+ - lib/xavier/version.rb
167
+ - xavier.gemspec
168
+ homepage: https://github.com/wilsonsilva/xavier
169
+ licenses: []
170
+ metadata:
171
+ yard.run: yri
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 2.6.12
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Reverts state mutations.
192
+ test_files: []