key-vortex 0.2.5 → 1.0.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
  SHA256:
3
- metadata.gz: 9696a877879c820dd96922f5f300b3367fae4cc7ce37c92db6efffe32e5f3f11
4
- data.tar.gz: 46ac4d73acc9dbef6d60e767f8d0f8a0dcfd7a44baf56a874b60aaa3634cdc97
3
+ metadata.gz: ccc7720fa67e489fa24c0158866cfde5be930f1b0f68ccc04b94b77e193b69f1
4
+ data.tar.gz: 350476269a57fed2af54c84ccd887d828f0804d287445b1133b67d2a4b66ce7c
5
5
  SHA512:
6
- metadata.gz: e2401b6a3d3d73d7122054d7511c466865d12ae2572439c9aa3905c8a255e16b3aceeb38565c214ec33c991d6b8525252c35614273f96be094b50d155b8d3235
7
- data.tar.gz: 9ad900f47bd8756ac9a9bdbd2d2810bc76d96660fdbecdad0c6762ee305b4ea7f4054610119b4fb752dbdb8aac52f1f76efc229e4c6dc735beecec51c7005650
6
+ metadata.gz: 21a080f1f96c1d2aa6dde92071f3b5df4d73e96d11207a4cca025ad4c01be5f8f93fc4567da53087645c58f3d2d74afbede919e3ef45bc2cc63e3fe85a0341b0
7
+ data.tar.gz: 2c73b0c9eeae66681633c15d887c595c86aa468afed4dec676ca5527466908f33e4d084494da2dae682ed87cae82e2255ba82941d303fad57d504836225bea5a
data/.reek.yml CHANGED
@@ -1,9 +1,13 @@
1
1
  detectors:
2
- # TODO: Remove once design is set
3
2
  IrresponsibleModule:
4
- enabled: false
3
+ exclude:
4
+ - AnotherRecordOne
5
+ - RecordOne
6
+ - RecordTwo
7
+ - BasicRecord
8
+ - BigStringRecord
5
9
 
6
10
  DuplicateMethodCall:
7
11
  exclude:
8
- - KeyVortex::Record#self.define_getter
9
- - KeyVortex::Record#self.define_setter
12
+ - KeyVortex::Record#define_getter
13
+ - KeyVortex::Record#define_setter
data/.rubocop.yml CHANGED
@@ -13,10 +13,6 @@ Style/StringLiteralsInInterpolation:
13
13
  Layout/LineLength:
14
14
  Max: 120
15
15
 
16
- # TODO: Remove once design is set
17
- Style/Documentation:
18
- Enabled: false
19
-
20
16
  Metrics/BlockLength:
21
17
  AllowedMethods:
22
18
  - describe
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --no-private
data/Gemfile CHANGED
@@ -15,8 +15,10 @@ gem "guard"
15
15
  gem "guard-reek"
16
16
  gem "guard-rspec"
17
17
  gem "guard-rubocop"
18
+ gem "guard-yard"
18
19
 
19
20
  gem "byebug"
20
21
  gem "key_vortex-contract"
21
22
 
23
+ gem "webrick"
22
24
  gem "yard"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- key-vortex (0.2.5)
4
+ key-vortex (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -32,8 +32,11 @@ GEM
32
32
  guard-rubocop (1.5.0)
33
33
  guard (~> 2.0)
34
34
  rubocop (< 2.0)
35
+ guard-yard (2.2.1)
36
+ guard (>= 1.1.0)
37
+ yard (>= 0.7.0)
35
38
  json (2.6.3)
36
- key_vortex-contract (0.2.4)
39
+ key_vortex-contract (1.0.0)
37
40
  rantly (~> 2.0.0)
38
41
  rspec (~> 3.0)
39
42
  kwalify (0.7.2)
@@ -97,6 +100,7 @@ GEM
97
100
  shellany (0.0.1)
98
101
  thor (1.2.2)
99
102
  unicode-display_width (2.4.2)
103
+ webrick (1.8.1)
100
104
  yard (0.9.34)
101
105
 
102
106
  PLATFORMS
@@ -108,12 +112,14 @@ DEPENDENCIES
108
112
  guard-reek
109
113
  guard-rspec
110
114
  guard-rubocop
115
+ guard-yard
111
116
  key-vortex!
112
117
  key_vortex-contract
113
118
  rake (~> 13.0)
114
119
  reek (~> 6.1.4)
115
120
  rspec (~> 3.0)
116
121
  rubocop (~> 1.21)
122
+ webrick
117
123
  yard
118
124
 
119
125
  BUNDLED WITH
data/Guardfile CHANGED
@@ -26,6 +26,12 @@
26
26
  # * zeus: 'zeus rspec' (requires the server to be started separately)
27
27
  # * 'just' rspec: 'rspec'
28
28
 
29
+ guard "yard" do
30
+ watch(%r{app/.+\.rb})
31
+ watch(%r{lib/.+\.rb})
32
+ watch(%r{ext/.+\.c})
33
+ end
34
+
29
35
  group :red_green_refactor, halt_on_fail: true do
30
36
  guard :rspec, cmd: "bundle exec rspec", failed_mode: :focus, all_on_pass: true do
31
37
  require "guard/rspec/dsl"
data/README.md CHANGED
@@ -2,67 +2,58 @@
2
2
 
3
3
  KeyVortex provides a common abstraction around storing records in various datastores. It allows for the use of different adapters depending on the environment, and provides constraints to protect programmatically against differing constraints between them.
4
4
 
5
- To start using KeyVortex, you'll need to define a record:
6
-
7
- ```ruby
8
- require "key_vortex/record"
9
-
10
- class ExampleRecord < KeyVortex::Record
11
- field :a, String, length: 20
12
- field :b, Integer, maximum: 100
13
- end
14
- ```
5
+ ## Installation
15
6
 
16
- Now you can use this object in various ways:
7
+ Add this line to your application's Gemfile:
17
8
 
18
- ```
19
- > record = ExampleRecord.new(key: "foo", a: "bar", b: 10)
20
- => #<ExampleRecord:0x000055fe0b5fe538 @values={:key=>"foo", :a=>"bar", :b=>10}>
21
- > record.a
22
- => "bar"
23
- > record.a = "baz"
24
- => "baz"
25
- > record.a
26
- => "baz"
27
- > record.b = 1000
28
- Invalid value 1000 for b (KeyVortex::Error)
29
- ```
9
+ gem 'key-vortex'
30
10
 
31
- You may notice that a `key` field was defined as well. This can be a String up to 36 characters long, to accomodate a GUID if that's what you wish to use.
11
+ And then execute:
32
12
 
33
- In order to save the record somewhere, you'll need to choose an adapter. To keep dependencies down, these will generally be implemented in other gems, but an in memory adapter does ship with this gem.
13
+ $ bundle install
34
14
 
35
- ```
36
- > require "key_vortex/adapter/memory"
37
- > vortex = KeyVortex.vortex(:memory, ExampleRecord)
38
- > vortex.save(ExampleRecord.new(key: "foo", a: "a", b: 10))
39
- > vortex.find("foo")
40
- => #<ExampleRecord:0x0000560781f480b0 @values={:key=>"foo", :a=>"a", :b=>10}>
41
- ```
15
+ Or install it yourself as:
42
16
 
43
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/key_vortex`. To experiment with that code, run `bin/console` for an interactive prompt.
17
+ $ gem install key-vortex
44
18
 
45
- TODO: Delete this and the text above, and describe your gem
19
+ ## Usage
46
20
 
47
- ## Installation
21
+ To start using KeyVortex, you'll need to define a [record](https://rubydoc.info/gems/key-vortex/KeyVortex/Record/):
48
22
 
49
- Add this line to your application's Gemfile:
23
+ require "key_vortex/record"
50
24
 
51
- ```ruby
52
- gem 'key-vortex'
53
- ```
25
+ class ExampleRecord < KeyVortex::Record
26
+ field :a, String, length: 20
27
+ field :b, Integer, maximum: 100
28
+ end
54
29
 
55
- And then execute:
30
+ Now you can use this object in various ways:
56
31
 
57
- $ bundle install
32
+ > record = ExampleRecord.new(key: "foo", a: "bar", b: 10)
33
+ => #<ExampleRecord:0x000055fe0b5fe538 @values={:key=>"foo", :a=>"bar", :b=>10}>
34
+ > record.a
35
+ => "bar"
36
+ > record.a = "baz"
37
+ => "baz"
38
+ > record.a
39
+ => "baz"
40
+ > record.b = 1000
41
+ Invalid value 1000 for b (KeyVortex::Error)
58
42
 
59
- Or install it yourself as:
43
+ You may notice that a `key` field was defined as well. This can be a String up to 36 characters long, to accomodate a GUID if that's what you wish to use.
60
44
 
61
- $ gem install key-vortex
45
+ In order to save the record somewhere, you'll need to choose an [adapter](https://rubydoc.info/gems/key-vortex/KeyVortex/Adapter).
62
46
 
63
- ## Usage
47
+ > require "key_vortex/adapter/memory"
48
+ > vortex = KeyVortex.vortex(:memory, ExampleRecord)
49
+ > vortex.save(ExampleRecord.new(key: "foo", a: "a", b: 10))
50
+ > vortex.find("foo")
51
+ => #<ExampleRecord:0x0000560781f480b0 @values={:key=>"foo", :a=>"a", :b=>10}>
52
+ > vortex.remove("foo")
53
+ > vortex.find("foo")
54
+ => nil
64
55
 
65
- TODO: Write usage instructions here
56
+ As you can see, you have the ability to `save`, `find` and `remove` records. Once a record is saved, it can be referenced by the `key`.
66
57
 
67
58
  ## Development
68
59
 
@@ -72,7 +63,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
72
63
 
73
64
  ## Contributing
74
65
 
75
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/key-vortex. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/key-vortex/blob/main/CODE_OF_CONDUCT.md).
66
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Lambda-Null/key-vortex. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/Lambda-Null/key-vortex/blob/main/CODE_OF_CONDUCT.md).
76
67
 
77
68
  ## License
78
69
 
@@ -80,4 +71,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
80
71
 
81
72
  ## Code of Conduct
82
73
 
83
- Everyone interacting in the KeyVortex project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/key-vortex/blob/main/CODE_OF_CONDUCT.md).
74
+ Everyone interacting in the KeyVortex project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Lambda-Null/key-vortex/blob/main/CODE_OF_CONDUCT.md).
@@ -5,25 +5,45 @@ require "key_vortex/adapter"
5
5
 
6
6
  class KeyVortex
7
7
  class Adapter
8
+ # An in-memory implementation of an adapter. While typically not
9
+ # useful in production, it can provide a convenient implementation
10
+ # for unit testing.
8
11
  class Memory < KeyVortex::Adapter
12
+ # A pass-through to the constructor which is used by
13
+ # {KeyVortex.vortex}.
14
+ # @param items [Hash]
15
+ # @param limitations [Array of KeyVortex::Limitation]
16
+ # @return [KeyVortex::Adapter::Memory]
9
17
  def self.build(items: {}, limitations: [])
10
18
  new(items, limitations: limitations)
11
19
  end
12
20
 
21
+ # Create a new instance of the in-memory adapter. By default, it
22
+ # has no limitations, but since it's most likely testing an
23
+ # adapter which is more restrictive, it accepts a list of
24
+ # limitations to apply.
25
+ # @param items [Hash] The seed values that should be considered already saved.
26
+ # @param limitations [Array of KeyVortex::Limitation]
13
27
  def initialize(items, limitations: [])
14
28
  super()
15
29
  @items = items
16
30
  limitations.each { |limit| register_limitation(limit) }
17
31
  end
18
32
 
33
+ # Save the record to the provided hash using {Record#key} as the
34
+ # key.
35
+ # @param record [Record]
19
36
  def save(record)
20
37
  @items[record.key] = record
21
38
  end
22
39
 
23
- def find(id)
24
- @items[id]
40
+ # @param key [String]
41
+ # @return [Record, nil] The record with {Record#key} set to key
42
+ def find(key)
43
+ @items[key]
25
44
  end
26
45
 
46
+ # Remove the record with {Record#key} set to key
27
47
  def remove(key)
28
48
  @items.delete(key)
29
49
  end
@@ -1,19 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class KeyVortex
4
+ # Adapters bridge the gap between the main logic of KeyVortex and
5
+ # individual backends. When using a backend, it can have various
6
+ # limitations on particular data types. KeyVortex will guarantee that
7
+ # these limitations are more permissive then the Records used to
8
+ # interact with it.
9
+ #
10
+ # To keep dependencies down, the subclasses you'll actually use will
11
+ # be implemented in other gems, but an in memory adapter does ship
12
+ # with this gem. Here are some that are available:
13
+ #
14
+ # * {https://github.com/Lambda-Null/key_vortex-stashify Stashify}: File/blob storage
15
+ #
16
+ # == Building Adapters
17
+ #
18
+ # For an adapter to work with KeyVortex, it needs to inherit from
19
+ # this class. In addition, it needs to override the following
20
+ # methods:
21
+ #
22
+ # * save({KeyVortex::Record}) => Anything
23
+ # * find(String) => {KeyVortex::Record}
24
+ # * remove(String) => Anything
25
+ #
26
+ # There is {https://github.com/Lambda-Null/key_vortex-contract a separate gem},
27
+ # which provides a set of shared examples enforcing the correct
28
+ # behavior of those methods. PRs to include your gem in the list in
29
+ # the previous section will be accepted if they conform to that
30
+ # contract.
4
31
  class Adapter
5
32
  def initialize
6
33
  @limitations = {}
7
34
  end
8
35
 
36
+ # Get the limitation for the type of a given field.
37
+ # @param field [KeyVortex::Field]
38
+ # @return [KeyVortex::Limitation]
9
39
  def limitation_for(field)
10
40
  @limitations[field.limitation.type]
11
41
  end
12
42
 
43
+ # Apply the provided limitation to the adapter.
44
+ # @param limitation [KeyVortex::Limitation]
13
45
  def register_limitation(limitation)
14
46
  @limitations[limitation.type] = limitation
15
47
  end
16
48
 
49
+ # Provide the symbol used when using the factory method
50
+ # {KeyVortex.vortex}.
51
+ # @return [Symbol] A symbol representing this class
17
52
  def self.symbol
18
53
  name.split(":").last.downcase.to_sym
19
54
  end
@@ -1,16 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class KeyVortex
4
- class Constraint
4
+ module Constraint
5
+ # Base class all other constraints inherit from. Does not define
6
+ # all of the properties necessary for a constraint, and so is
7
+ # inappropriate to be used directly.
5
8
  class Base
9
+ # Comparing constraints is only valid when compared to other
10
+ # instances of the same constraint. This helps other parts of
11
+ # the system determine if this is true.
12
+ # @param constraint [Base]
13
+ # @return [Boolean]
6
14
  def applies_to?(constraint)
7
15
  attribute == constraint.attribute
8
16
  end
9
17
 
18
+ # The individual constraint is responsible for making the
19
+ # ultimate determination of if it is within another
20
+ # constraint. What's common to all of them, though, is that they
21
+ # must be the same class.
10
22
  def within?(constraint)
11
23
  constraint.instance_of?(self.class)
12
24
  end
13
25
 
26
+ # A text description of the constraint. Assumes subclasses
27
+ # define the methods attribute and limit.
28
+ # @return [String]
14
29
  def to_s
15
30
  "#{attribute}: #{limit}"
16
31
  end
@@ -3,23 +3,32 @@
3
3
  require "key_vortex/constraint/base"
4
4
 
5
5
  class KeyVortex
6
- class Constraint
6
+ module Constraint
7
+ # Enforces that objects which respond to #length less than the
8
+ # specified limit.
7
9
  class Length < KeyVortex::Constraint::Base
10
+ # @return [Integer] The upper bound allowed when calling #length on a value
8
11
  attr_reader :limit
9
12
 
13
+ # @param limit [Integer] The maximum allowed value
10
14
  def initialize(limit)
11
15
  super()
12
16
  @limit = limit
13
17
  end
14
18
 
19
+ # @return [Symbol] :length
15
20
  def attribute
16
21
  :length
17
22
  end
18
23
 
24
+ # @param constraint [Length]
25
+ # @return [Boolean] True if limit <= constraint.limit
19
26
  def within?(constraint)
20
27
  super && limit <= constraint.limit
21
28
  end
22
29
 
30
+ # @param value [Object] Must respond to #length
31
+ # @return [Boolean] True if length <= limit
23
32
  def accepts?(value)
24
33
  value.length <= limit
25
34
  end
@@ -3,23 +3,32 @@
3
3
  require "key_vortex/constraint/base"
4
4
 
5
5
  class KeyVortex
6
- class Constraint
6
+ module Constraint
7
+ # Enforces that objects which respond to #<= are less than the
8
+ # defined limit.
7
9
  class Maximum < KeyVortex::Constraint::Base
10
+ # @return [Integer] The maximum allowed value
8
11
  attr_reader :limit
9
12
 
13
+ # @param limit [Integer] The maximum allowed value
10
14
  def initialize(limit)
11
15
  super()
12
16
  @limit = limit
13
17
  end
14
18
 
19
+ # @return [Symbol] :maximum
15
20
  def attribute
16
21
  :maximum
17
22
  end
18
23
 
24
+ # @param constraint [Maximum]
25
+ # @return [Boolean] True if limit <= constraint.limit
19
26
  def within?(constraint)
20
27
  super && limit <= constraint.limit
21
28
  end
22
29
 
30
+ # @param value [Object] Must respond to #<=
31
+ # @return [Boolean] True if value <= limit
23
32
  def accepts?(value)
24
33
  value <= limit
25
34
  end
@@ -3,23 +3,32 @@
3
3
  require "key_vortex/constraint/base"
4
4
 
5
5
  class KeyVortex
6
- class Constraint
6
+ module Constraint
7
+ # Enforces that objects which respond to #>= are greater than the
8
+ # defined limit.
7
9
  class Minimum < KeyVortex::Constraint::Base
10
+ # @return [Integer] The minimum allowed value
8
11
  attr_reader :limit
9
12
 
13
+ # @param limit [Integer] The minimum allowed value
10
14
  def initialize(limit)
11
15
  super()
12
16
  @limit = limit
13
17
  end
14
18
 
19
+ # @return [Symbol] :minimum
15
20
  def attribute
16
- :maximum
21
+ :minimum
17
22
  end
18
23
 
24
+ # @param constraint [Minimum]
25
+ # @return [Boolean] True if limit >= constraint.limit
19
26
  def within?(constraint)
20
27
  super && limit >= constraint.limit
21
28
  end
22
29
 
30
+ # @param value [Object] Must respond to #>=
31
+ # @return [Boolean] True if value >= limit
23
32
  def accepts?(value)
24
33
  value >= limit
25
34
  end
@@ -6,18 +6,55 @@ require "key_vortex/constraint/maximum"
6
6
  require "key_vortex/constraint/minimum"
7
7
 
8
8
  class KeyVortex
9
- class Constraint
10
- def self.build(attribute, value)
9
+ # Constraints define a restriction on the values which can be used
10
+ # for {KeyVortex::Record} and {KeyVortex::Adapter}. Although they
11
+ # are somewhat varied, they do have some common properties.
12
+ #
13
+ # All constraints have some sort of attribute name and limit. In
14
+ # most situations, instead of the constructor, this pair will be
15
+ # used to specify the constraint. For example, you would typically
16
+ # specify a length like this:
17
+ #
18
+ # length: 10
19
+ #
20
+ # All constraints will inherit from
21
+ # {KeyVortex::Constraint::Base}. Because of the common ancestor,
22
+ # along with the need to have a single file loading all constraints,
23
+ # {KeyVortex::Constraint} is a module instead of the base class to
24
+ # avoid a circular dependency.
25
+ #
26
+ # All constraints can determine if a value is valid for them.
27
+ #
28
+ # All constraints, when compared with other instances of themselves,
29
+ # it can always be determined if ones limitations fit within the
30
+ # others. More formally, given an instance x of a constraint, an
31
+ # instance y of the same constraint fits within x if and only if for
32
+ # all values v that are valid for y, v is also valid for x.
33
+ module Constraint
34
+ # Factory enabling the specification of constraints by attribute
35
+ # names instead of constructors.
36
+ # @param attribute [Symbol]
37
+ # @param limit [Object]
38
+ # @return [KeyVortex::Constraint::Base] Subclass specified by attribute
39
+ # @raise [KeyVortex::Error] If the attribute is unknown
40
+ def self.build(attribute, limit)
11
41
  case attribute
12
42
  when :length
13
- KeyVortex::Constraint::Length.new(value)
43
+ KeyVortex::Constraint::Length.new(limit)
14
44
  when :maximum
15
- KeyVortex::Constraint::Maximum.new(value)
45
+ KeyVortex::Constraint::Maximum.new(limit)
16
46
  when :minimum
17
- KeyVortex::Constraint::Minimum.new(value)
47
+ KeyVortex::Constraint::Minimum.new(limit)
18
48
  else
19
49
  raise KeyVortex::Error, "Unexpected attribute: #{attribute}"
20
50
  end
21
51
  end
22
52
  end
23
53
  end
54
+
55
+ #
56
+ # Adapters can also have constraints, though, and if a particular
57
+ # type is more constrained then the record KeyVortex will refuse to
58
+ # use that record. If you don't specify any constraints, for
59
+ # example, the adapter will throw an error unless it is also
60
+ # unconstrained on that type.
@@ -4,9 +4,23 @@ require "key_vortex/constraint"
4
4
  require "key_vortex/limitation"
5
5
 
6
6
  class KeyVortex
7
+ # Defines a value that can be set on a {Record}. This generally
8
+ # isn't built directly, you should use the {Record#field} directive
9
+ # most of the time. This class ties a name to a {Limitation}, and
10
+ # delegates most questions on to the latter.
7
11
  class Field
8
- attr_reader :name, :limitation
12
+ # @return [Symbol] the name of the field
13
+ attr_reader :name
14
+ # @return [Limitation] the restrictions placed upon this field
15
+ attr_reader :limitation
9
16
 
17
+ # Creates an instance of this class and converts any
18
+ # attribute/limit constraint pairs into {KeyVortex::Constraint}
19
+ # objects.
20
+ # @param name [Symbol] Name of the field
21
+ # @param type [Class] Type used for limitation
22
+ # @param constraints_array [Constraint::Base] Any constraints already constructed can be passed in as well
23
+ # @param constraints_hash [Hash] key/value pairs that will be passed on to {Constraint#build}
10
24
  def initialize(name, type, *constraints_array, **constraints_hash)
11
25
  @name = name
12
26
  @limitation = KeyVortex::Limitation.new(type)
@@ -17,15 +31,20 @@ class KeyVortex
17
31
  end)
18
32
  end
19
33
 
34
+ # @param adapter [Adapter]
35
+ # @return [Boolean] true if the limitations for this field fit within the corresponding limitations for the adapter
20
36
  def within?(adapter)
21
37
  limitation = adapter.limitation_for(self)
22
38
  !limitation || self.limitation.within?(limitation)
23
39
  end
24
40
 
41
+ # @param value [Object]
42
+ # @return [Boolean] true if the value is valid for {#limitation}
25
43
  def accepts?(value)
26
44
  limitation.accepts?(value)
27
45
  end
28
46
 
47
+ # Enable JSON additions for the class used for this field.
29
48
  def enable_json_additions
30
49
  @limitation.enable_json_additions
31
50
  end
@@ -1,14 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class KeyVortex
4
+ # Represents a collection of {KeyVortex::Constraint constraints}
5
+ # which apply to a given class. While these can be constructed
6
+ # directly, the intended mechanism for applying these to a record is
7
+ # through {KeyVortex::Record.field}.
8
+ #
9
+ # Two independent concepts which commonly use the same terminology
10
+ # exist in this project. Consider the term "allows", is that
11
+ # referring to what values are allowed or what constraints are
12
+ # allowed? To help navigate these two concepts, the following
13
+ # terminology convention is used throughout this codebase:
14
+ # * Encompass/Within: Used when comparing limitations and constraints to each other
15
+ # * Accepts/Rejects: Used when determining if a value is valid for constraints
4
16
  class Limitation
5
- attr_reader :type, :constraints
17
+ # @return [Class] The class these constraints applies to.
18
+ attr_reader :type
6
19
 
20
+ # @return [Array] The constraints which apply to this class.
21
+ attr_reader :constraints
22
+
23
+ # @param type [Class]
24
+ # @param constraints [KeyValue::Constraint]
7
25
  def initialize(type, *constraints)
8
26
  @type = type
9
- @constraints = constraints
27
+ @constraints = []
28
+ add_constraint(*constraints)
10
29
  end
11
30
 
31
+ # Add constraints to this limitation. It is also possible to do
32
+ # this through the constructor, but it is sometimes easier to do
33
+ # it one at a time.
34
+ # @param constraints [KeyVortex::Constraint::Base]
12
35
  def add_constraint(*constraints)
13
36
  constraints.each do |constraint|
14
37
  unless constraint.is_a?(KeyVortex::Constraint::Base)
@@ -20,24 +43,42 @@ class KeyVortex
20
43
  @constraints += constraints
21
44
  end
22
45
 
23
- def encompasses?(limitation)
24
- @constraints.all? do |constraint|
25
- limitation.encompasses_constraint?(constraint)
46
+ # Determine if any of the constraints in the provided limitation
47
+ # exceed this limitation's constraints.
48
+ # @param limitation [KeyVortex::Limitation]
49
+ # @return [Boolean]
50
+ def within?(limitation)
51
+ limitation.constraints.all? do |constraint|
52
+ within_constraint?(constraint)
26
53
  end
27
54
  end
28
55
 
29
- def encompasses_constraint?(constraint)
30
- applicable_constraints(constraint).any? do |con|
31
- con.within?(constraint)
32
- end
56
+ # Determine if a given value meets all of the constraints.
57
+ # @param value [type]
58
+ # @return [Boolean]
59
+ def accepts?(value)
60
+ value.is_a?(type) && @constraints.all? { |constraint| constraint.accepts?(value) }
33
61
  end
34
62
 
35
- def within?(limitation)
36
- limitation.constraints.all? do |constraint|
37
- within_constraint?(constraint)
38
- end
63
+ # Ensure that JSON additions for {type} are loaded in case the
64
+ # record needs to be serialized.
65
+ def enable_json_additions
66
+ path = JSON_ADDITIONS[@type.class.name]
67
+ require path if path
68
+ end
69
+
70
+ # Return information about this limitation. The information will
71
+ # be formatted as follows:
72
+ # Limitation: String
73
+ # minimum: 10
74
+ # maximum: 100
75
+ # @return [String]
76
+ def to_s
77
+ "Limitation: #{@type}\n\t#{@constraints.join('\n\t')}"
39
78
  end
40
79
 
80
+ private
81
+
41
82
  def within_constraint?(constraint)
42
83
  applicable_constraints(constraint).any? do |con|
43
84
  con.within?(constraint)
@@ -50,10 +91,6 @@ class KeyVortex
50
91
  end
51
92
  end
52
93
 
53
- def accepts?(value)
54
- value.is_a?(type) && @constraints.all? { |constraint| constraint.accepts?(value) }
55
- end
56
-
57
94
  JSON_ADDITIONS = {
58
95
  BigDecimal: "json/add/bigdecimal",
59
96
  Complex: "json/add/complex",
@@ -69,14 +106,5 @@ class KeyVortex
69
106
  Symbol: "json/add/symbol",
70
107
  Time: "json/add/time"
71
108
  }.freeze
72
-
73
- def enable_json_additions
74
- path = JSON_ADDITIONS[@type.class.name]
75
- require path if path
76
- end
77
-
78
- def to_s
79
- "Limitation: #{@type}\n\t#{@constraints.join('\n\t')}"
80
- end
81
109
  end
82
110
  end
@@ -6,54 +6,135 @@ require "key_vortex/field"
6
6
  require "key_vortex/limitation"
7
7
 
8
8
  class KeyVortex
9
+ # To represent a structured piece of information you want to save,
10
+ # it has to be a subclass of KeyVortex::Record. There will always be
11
+ # field named {#key}, which is at most 36 characters long, but keys
12
+ # with any other name can also be added.
13
+ #
14
+ # class ExampleRecord < KeyVortex::Record
15
+ # field :a, Integer, minimum: 10, maximum: 100
16
+ # field :b, String, length: 100
17
+ # field :c, Integer
18
+ # end
19
+ #
20
+ # See {field} for more details on how those directives are
21
+ # structured. When you're ready to use the class, values can be
22
+ # provided either in the constructor like this:
23
+ #
24
+ # ExampleRecord.new(
25
+ # a: 15,
26
+ # b: "foo",
27
+ # c: 1000,
28
+ # )
29
+ #
30
+ # Or set after the object is created like this:
31
+ #
32
+ # r = ExampleRecord.new
33
+ # r.a = 15
34
+ # r.b = "foo"
35
+ # r.c = 1000
36
+ #
37
+ # In either case, the values can be retrieved as attributes after
38
+ # they're set:
39
+ #
40
+ # r.a # 15
41
+ # r.b # "foo"
42
+ # r.c # 1000
9
43
  class Record
10
- def self.fields
11
- field_hash.values
12
- end
44
+ class << self
45
+ # @return [Array] All of the fields declared for this class.
46
+ def fields
47
+ field_hash.values
48
+ end
13
49
 
14
- def self.field_hash
15
- @field_hash ||= {}
16
- end
50
+ # Declares what values are tracked by this record.
51
+ # When declaring a field, it follows this general format:
52
+ #
53
+ # field :field_name, ClassName, constraint1: contraint1_value, constraint2: constraint2_value
54
+ #
55
+ # From a record's perspective any number of
56
+ # {KeyVortex::Constraint constraints} are valid, including none
57
+ # at all.
58
+ # @return [Field] The created field
59
+ def field(name, type, **constraints)
60
+ field = KeyVortex::Field.new(name, type, **constraints)
61
+ register_field(field)
62
+ field
63
+ end
17
64
 
18
- def self.field(name, type, **constraints_hash)
19
- register_field(KeyVortex::Field.new(name, type, **constraints_hash))
20
- end
65
+ # Convert a json parse result into a record for this class. This
66
+ # is what's called when this command is used:
67
+ #
68
+ # JSON.parse(json, create_additions: true)
69
+ #
70
+ # See {#to_json} for an example of the JSON structure this
71
+ # example expects.
72
+ # @return [Record] Subclasses will return an instance of themselves
73
+ def json_create(object)
74
+ new(object["values"])
75
+ end
21
76
 
22
- def self.field_constraints(field)
23
- @field_hash[field]
24
- end
77
+ protected
25
78
 
26
- def self.register_field(field)
27
- field_hash[field.name] = field
28
- field.enable_json_additions
29
- define_getter(field)
30
- define_setter(field)
31
- end
79
+ def register_field(field)
80
+ field_hash[field.name] = field
81
+ field.enable_json_additions
82
+ define_getter(field)
83
+ define_setter(field)
84
+ end
32
85
 
33
- def self.define_getter(field)
34
- define_method(field.name) { @values[field.name] }
35
- end
86
+ private
36
87
 
37
- def self.define_setter(field)
38
- define_method("#{field.name}=") do |val|
39
- raise KeyVortex::Error, "Invalid value #{val} for #{field.name}" unless field.accepts?(val)
88
+ def field_hash
89
+ @field_hash ||= {}
90
+ end
40
91
 
41
- @values[field.name] = val
92
+ def define_getter(field)
93
+ define_method(field.name) { @values[field.name] }
42
94
  end
43
- end
44
95
 
45
- def self.inherited(subclass)
46
- super
47
- fields.each do |field|
48
- subclass.register_field(field)
96
+ def define_setter(field)
97
+ define_method("#{field.name}=") do |val|
98
+ raise KeyVortex::Error, "Invalid value #{val} for #{field.name}" unless field.accepts?(val)
99
+
100
+ @values[field.name] = val
101
+ end
102
+ end
103
+
104
+ def inherited(subclass)
105
+ super
106
+ fields.each do |field|
107
+ subclass.register_field(field)
108
+ end
49
109
  end
50
110
  end
51
111
 
52
- # Long enough to accomodate a GUID
112
+ # @!attribute key
113
+ # The key used to save and retrieve this record. Every field has
114
+ # a key defined, it can be a String up to 36 characters long,
115
+ # which is enough to accomodate a GUID if that's what you want
116
+ # to use.
117
+ # @return [String]
53
118
  field :key, String, length: 36
54
119
 
120
+ # A hash of all of the attributes, in which the keys are symbols.
121
+ # @example
122
+ # {
123
+ # a: 15,
124
+ # b: "foo",
125
+ # c: 1000,
126
+ # }
127
+ #
128
+ # @return [Hash]
55
129
  attr_reader :values
56
130
 
131
+ # Values can optionally be specified when the object is created.
132
+ # @example
133
+ # ExampleRecord.new(
134
+ # a: 15,
135
+ # b: "foo",
136
+ # c: 1000,
137
+ # )
57
138
  def initialize(values = {})
58
139
  @values = {}
59
140
  values.each do |name, value|
@@ -61,19 +142,33 @@ class KeyVortex
61
142
  end
62
143
  end
63
144
 
145
+ # Two records are equal if they are the same class and have the
146
+ # same values.
147
+ # @return [Boolean]
64
148
  def ==(other)
65
149
  self.class == other.class && values == other.values
66
150
  end
67
151
 
152
+ # Converts the record into a JSON string appropriate for parsing
153
+ # using the create_additions option. Here is an example of the
154
+ # format that's produced.
155
+ #
156
+ # {
157
+ # "json_class": "ExampleRecord",
158
+ # "values": {
159
+ # "key": "foo",
160
+ # "a": 15,
161
+ # "b": "bar",
162
+ # "c": 1000
163
+ # }
164
+ # }
165
+ #
166
+ # @return [String] JSON representation of the object
68
167
  def to_json(*args)
69
168
  {
70
169
  JSON.create_id => self.class.name,
71
170
  "values" => @values
72
171
  }.to_json(*args)
73
172
  end
74
-
75
- def self.json_create(object)
76
- new(object["values"])
77
- end
78
173
  end
79
174
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class KeyVortex
4
- VERSION = "0.2.5"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/key_vortex.rb CHANGED
@@ -2,14 +2,29 @@
2
2
 
3
3
  require_relative "key_vortex/version"
4
4
 
5
+ # Defines the API you'll interact with when using this gem. Much of
6
+ # the functionality is delegated to other classes.
5
7
  class KeyVortex
8
+ # General purpose error used when issues arise within this gem.
6
9
  class Error < StandardError; end
7
10
 
11
+ # Register an adapter class so that {.vortex} knows about it.
12
+ # @param adapter_class [Class]
8
13
  def self.register(adapter_class)
9
14
  @adapter_registry ||= {}
10
15
  @adapter_registry[adapter_class.symbol] = adapter_class
11
16
  end
12
17
 
18
+ # Creates an instance of {KeyVortex} with an appropriate
19
+ # {Adapter}. An adapter class must have been associated with the
20
+ # adapter_symbol by calling {.register}, this should have been done
21
+ # by the file which defined that class. So, when you require
22
+ # "key_vortex/adapter/memory" the :memory symbol will be defined for
23
+ # use here.
24
+ # @param adapter_symbol [Symbol]
25
+ # @param record_class [Class] The class must inherit from {Record}
26
+ # @param options [Hash] Options that will be passed through to .build on the adapter
27
+ # @return [KeyVortex]
13
28
  def self.vortex(adapter_symbol, record_class, **options)
14
29
  new(
15
30
  @adapter_registry[adapter_symbol].build(**options),
@@ -17,8 +32,16 @@ class KeyVortex
17
32
  )
18
33
  end
19
34
 
20
- attr_reader :adapter, :record_class
35
+ # @return [Adapter]
36
+ attr_reader :adapter
37
+ # @return [Class] Subclass of {Record}
38
+ attr_reader :record_class
21
39
 
40
+ # While you are able to create the object directly, it's easier to
41
+ # do so through {.vortex}.
42
+ # @param adapter [Adapter] The backend that will be used to save and retrieve records
43
+ # @param record_class [Class] The subclass of {Record} which will be saved and retreived
44
+ # @raise [Error] If the constraints of the record_class do not fit within the constraints of the adapter
22
45
  def initialize(adapter, record_class)
23
46
  @adapter = adapter
24
47
  @record_class = record_class
@@ -33,15 +56,25 @@ class KeyVortex
33
56
  end
34
57
  end
35
58
 
59
+ # Add the record to the {#adapter}. Once this is done, {#find} will
60
+ # return this record when passed the {Record#key}.
61
+ # @param record [Record] Must match {#record_class}
36
62
  def save(record)
37
63
  @adapter.save(record)
38
64
  end
39
65
 
40
- def find(id)
41
- @adapter.find(id)
66
+ # Retrieve a record that has had {#save} called on it.
67
+ # @param key [String]
68
+ # @return [Record] The {Record} with {Record#key} set to key
69
+ # @return [nil] if no record is found
70
+ def find(key)
71
+ @adapter.find(key)
42
72
  end
43
73
 
44
- def remove(id)
45
- @adapter.remove(id)
74
+ # Remove the {Record} with {Record#key} set to key from the
75
+ # {#adapter}.
76
+ # @param key [String]
77
+ def remove(key)
78
+ @adapter.remove(key)
46
79
  end
47
80
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: key-vortex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lambda Null
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-14 00:00:00.000000000 Z
11
+ date: 2023-07-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Defines abstractions that can be built on top of for key/value storage
14
14
  on different technologies (file, s3, sql, redis, etc.)
@@ -21,6 +21,7 @@ files:
21
21
  - ".reek.yml"
22
22
  - ".rspec"
23
23
  - ".rubocop.yml"
24
+ - ".yardopts"
24
25
  - CODE_OF_CONDUCT.md
25
26
  - Gemfile
26
27
  - Gemfile.lock