dirt-core 2.2.3

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.
Files changed (102) hide show
  1. data/Gemfile +8 -0
  2. data/Gemfile.lock +46 -0
  3. data/MIT-LICENSE +23 -0
  4. data/README +14 -0
  5. data/doc/BlockValidator.html +272 -0
  6. data/doc/ClosureValidator.html +276 -0
  7. data/doc/Dirt.html +158 -0
  8. data/doc/Dirt/FooBar.html +167 -0
  9. data/doc/Dirt/HerpZerp.html +161 -0
  10. data/doc/Dirt/HerpZerp/DerpGerp.html +161 -0
  11. data/doc/Dirt/HerpZerp/DerpGerp/FooBar.html +167 -0
  12. data/doc/Dirt/MemoryPersister.html +453 -0
  13. data/doc/Dirt/MemoryRecord.html +157 -0
  14. data/doc/Dirt/MissingRecordError.html +157 -0
  15. data/doc/Dirt/Model.html +320 -0
  16. data/doc/Dirt/NoPersisterError.html +157 -0
  17. data/doc/Dirt/Relation.html +338 -0
  18. data/doc/Dirt/Role.html +345 -0
  19. data/doc/Dirt/TooManyRecordsError.html +157 -0
  20. data/doc/Dirt/TransactionError.html +157 -0
  21. data/doc/ExistenceValidator.html +316 -0
  22. data/doc/FooBar.html +153 -0
  23. data/doc/Gemfile.html +131 -0
  24. data/doc/HerpZerp.html +147 -0
  25. data/doc/HerpZerp/DerpGerp.html +147 -0
  26. data/doc/HerpZerp/DerpGerp/FooBar.html +153 -0
  27. data/doc/Persister.html +311 -0
  28. data/doc/Persisting.html +413 -0
  29. data/doc/PresenceValidator.html +281 -0
  30. data/doc/README.html +132 -0
  31. data/doc/Role.html +309 -0
  32. data/doc/Scheduler.html +147 -0
  33. data/doc/SelfExistenceValidator.html +278 -0
  34. data/doc/TestPersister.html +411 -0
  35. data/doc/Validating.html +293 -0
  36. data/doc/created.rid +18 -0
  37. data/doc/images/add.png +0 -0
  38. data/doc/images/brick.png +0 -0
  39. data/doc/images/brick_link.png +0 -0
  40. data/doc/images/bug.png +0 -0
  41. data/doc/images/bullet_black.png +0 -0
  42. data/doc/images/bullet_toggle_minus.png +0 -0
  43. data/doc/images/bullet_toggle_plus.png +0 -0
  44. data/doc/images/date.png +0 -0
  45. data/doc/images/delete.png +0 -0
  46. data/doc/images/find.png +0 -0
  47. data/doc/images/loadingAnimation.gif +0 -0
  48. data/doc/images/macFFBgHack.png +0 -0
  49. data/doc/images/package.png +0 -0
  50. data/doc/images/page_green.png +0 -0
  51. data/doc/images/page_white_text.png +0 -0
  52. data/doc/images/page_white_width.png +0 -0
  53. data/doc/images/plugin.png +0 -0
  54. data/doc/images/ruby.png +0 -0
  55. data/doc/images/tag_blue.png +0 -0
  56. data/doc/images/tag_green.png +0 -0
  57. data/doc/images/transparent.png +0 -0
  58. data/doc/images/wrench.png +0 -0
  59. data/doc/images/wrench_orange.png +0 -0
  60. data/doc/images/zoom.png +0 -0
  61. data/doc/index.html +110 -0
  62. data/doc/js/darkfish.js +155 -0
  63. data/doc/js/jquery.js +18 -0
  64. data/doc/js/navigation.js +142 -0
  65. data/doc/js/search.js +94 -0
  66. data/doc/js/search_index.js +1 -0
  67. data/doc/js/searcher.js +228 -0
  68. data/doc/rdoc.css +543 -0
  69. data/doc/table_of_contents.html +203 -0
  70. data/lib/dirt/bdd/matchers.rb +10 -0
  71. data/lib/dirt/bdd/persister_spec_helper.rb +213 -0
  72. data/lib/dirt/bdd/shared_examples.rb +29 -0
  73. data/lib/dirt/contexts/context.rb +15 -0
  74. data/lib/dirt/core.rb +36 -0
  75. data/lib/dirt/errors/missing_record_error.rb +4 -0
  76. data/lib/dirt/errors/no_persister_error.rb +4 -0
  77. data/lib/dirt/errors/too_many_records_error.rb +4 -0
  78. data/lib/dirt/errors/transaction_error.rb +4 -0
  79. data/lib/dirt/models/model_behaviour.rb +47 -0
  80. data/lib/dirt/persisters/memory_persister.rb +86 -0
  81. data/lib/dirt/persisters/persister.rb +54 -0
  82. data/lib/dirt/persisters/relation.rb +38 -0
  83. data/lib/dirt/roles/persisting.rb +73 -0
  84. data/lib/dirt/roles/role.rb +32 -0
  85. data/lib/dirt/roles/validating.rb +27 -0
  86. data/lib/dirt/roles/validation/closure_validator.rb +15 -0
  87. data/lib/dirt/roles/validation/existence_validator.rb +53 -0
  88. data/lib/dirt/roles/validation/presence_validator.rb +21 -0
  89. data/lib/dirt/roles/validation/self_existence_validator.rb +18 -0
  90. data/spec/isolations/closure_validator_spec.rb +57 -0
  91. data/spec/isolations/context_spec.rb +54 -0
  92. data/spec/isolations/existence_validator_spec.rb +151 -0
  93. data/spec/isolations/memory_persister_spec.rb +175 -0
  94. data/spec/isolations/model_spec.rb +91 -0
  95. data/spec/isolations/persister_spec.rb +113 -0
  96. data/spec/isolations/persisting_spec.rb +266 -0
  97. data/spec/isolations/presence_validator_spec.rb +63 -0
  98. data/spec/isolations/role_spec.rb +116 -0
  99. data/spec/isolations/self_existence_validator_spec.rb +64 -0
  100. data/spec/isolations/spec_helper.rb +26 -0
  101. data/spec/isolations/validating_spec.rb +88 -0
  102. metadata +161 -0
@@ -0,0 +1,15 @@
1
+ module Dirt
2
+ class Context
3
+ def self.run(*args)
4
+ new(*args).call()
5
+ end
6
+
7
+ # Override and save state here.
8
+ def initialize(*args)
9
+ end
10
+
11
+ # Override to perform the actual command
12
+ def call
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ #--
2
+ # Copyright (c) 2014 Tenjin Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'dirt/persisters/persister'
25
+ require 'dirt/persisters/memory_persister'
26
+
27
+ require 'dirt/contexts/context'
28
+
29
+ require 'dirt/models/model_behaviour'
30
+
31
+ require 'dirt/roles/persisting'
32
+ require 'dirt/roles/validating'
33
+ require 'dirt/roles/validation/presence_validator'
34
+ require 'dirt/roles/validation/existence_validator'
35
+ require 'dirt/roles/validation/self_existence_validator'
36
+ require 'dirt/roles/validation/closure_validator'
@@ -0,0 +1,4 @@
1
+ module Dirt
2
+ class MissingRecordError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Dirt
2
+ class NoPersisterError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Dirt
2
+ class TooManyRecordsError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Dirt
2
+ class TransactionError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,47 @@
1
+ module Dirt
2
+ # Common class for all model objects within the system.
3
+ module ModelBehaviour
4
+ def self.included(klass)
5
+ klass.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ # Initializes a new instance of the class. The parameter hash takes whatever properties of the
10
+ # specific model type as keys.
11
+ def new(*args)
12
+ obj = nil
13
+ begin
14
+ obj = super
15
+ rescue ArgumentError
16
+ obj = super()
17
+ end
18
+ obj.update(args[0]) if args[0].is_a? Hash
19
+ obj
20
+ end
21
+ end
22
+
23
+ # Updates the properties of this instance. The parameter hash takes whatever properties of the
24
+ # specific model type as keys.
25
+ def update(params)
26
+ params.keys.each do |key|
27
+ self.send("#{key}=", params[key])
28
+ end
29
+ end
30
+
31
+ # The hash representation of any model object maps attribute names as symbols to their values, like the following:
32
+ # {attr1: val1, attr2: val2, ...}
33
+ def to_hash
34
+ instance_variables.each_with_object({}) do |var, hash|
35
+ hash[var[1..-1].to_sym] = instance_variable_get(var)
36
+ end
37
+ end
38
+
39
+ def ==(other)
40
+ return instance_variables.all? do |var|
41
+ instance_variable_get(var) == other.instance_variable_get(var)
42
+ end
43
+
44
+ false
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,86 @@
1
+ module Dirt
2
+ require 'dirt/errors/missing_record_error'
3
+ require 'ostruct'
4
+
5
+ # An in-memory implementation of persistence.
6
+ class MemoryPersister
7
+ def initialize(type, &block)
8
+ @type = type
9
+ @next_id = 0
10
+ @records = {}
11
+ @new_maker = block
12
+ end
13
+
14
+ def new(*args)
15
+ raise RuntimeError.new('Cannot create a new instance without a block given to init.') unless @new_maker
16
+ @new_maker.yield(*args)
17
+ end
18
+
19
+ # Saves the record to the array either under the given id, or a new one if none is provided,
20
+ def save(data, id=nil)
21
+ assert_exists(id)
22
+
23
+ id ||= @next_id += 1
24
+
25
+ @records[id] = data
26
+
27
+ MemoryRecord.new(data.to_hash.merge(id: id))
28
+ end
29
+
30
+ # Returns the record with the given id.
31
+ def load(id)
32
+ assert_exists(id)
33
+
34
+ record = @records[id]
35
+
36
+ record ? MemoryRecord.new(record.to_hash.merge(id: id)) : nil
37
+ end
38
+
39
+ # Returns the list of all records.
40
+ def all
41
+ @records.collect do |id, r|
42
+ MemoryRecord.new(r.to_hash.merge(id: id))
43
+ end
44
+ end
45
+
46
+ # Removes the record with the given id.
47
+ def delete(id)
48
+ if @records.has_key?(id)
49
+ MemoryRecord.new(@records.delete(id).to_hash.merge(id: id))
50
+ end
51
+ end
52
+
53
+ # determines whether a record exists with the given id
54
+ def exists?(id)
55
+ @records[id] != nil
56
+ end
57
+
58
+ def find(params)
59
+ where(params).first
60
+ end
61
+
62
+ def where(params)
63
+ matches = @records.select do |id, record|
64
+ same_id = (params.delete(:id) || id) == id
65
+
66
+ params.all? { |attr, val| record.send(attr) == val } && same_id
67
+ end.collect do |id, record|
68
+ MemoryRecord.new(record.to_hash.merge(id: id))
69
+ end
70
+
71
+ Relation.new(matches)
72
+ end
73
+
74
+ private
75
+ def assert_exists(id)
76
+ raise MissingRecordError.new("That #{@type} (id: #{id}) does not exist.") if id && !@records[id]
77
+ end
78
+ end
79
+
80
+ class MemoryRecord < OpenStruct
81
+ alias_method :to_hash, :marshal_dump
82
+ end
83
+
84
+ # This must be at the bottom to work around the circular dependency with Relation.
85
+ require 'dirt/persisters/relation'
86
+ end
@@ -0,0 +1,54 @@
1
+ require 'active_support/all'
2
+ require 'dirt/errors/transaction_error'
3
+ require 'dirt/errors/no_persister_error'
4
+
5
+ # This Persister registry is the source for all persisters in the system. Register persisters here with
6
+ # #for.
7
+ #
8
+ # == Persisters
9
+ # All persisters must respond to the following:
10
+ #
11
+ # * +all+ Returns all records.
12
+ # * +find(id)+ Returns the record with the given id.
13
+ # * +exists?(id)+ Determines whether a record exists with the given id
14
+ # * +save(data)+ Saves a new record.
15
+ # * +save(data, id)+ Saves a record at the given id.
16
+ # * +transaction(&block)+ Rolls back changes brought about during :yeild, and re-raises a TransactionError iff that error is raised in the yeild.
17
+ # * delete(id) Deletes the record with the given id.
18
+ class Persister
19
+ # If persister is supplied, then this sets the persister for the given class,
20
+ # otherwise it returns the previously set persister for that class.
21
+ # If the first argument is a class, it is converted to a symbol.
22
+ def self.for(klass, persister=nil)
23
+ if klass.is_a? Class
24
+ klass = klass.to_s.demodulize.underscore.to_sym
25
+ end
26
+
27
+ @@persisters ||= {}
28
+ @@persisters[klass] ||= persister if persister
29
+
30
+ @@persisters[klass] || raise(Dirt::NoPersisterError.new("There is no persister for \"#{klass.to_s.pluralize}\"."))
31
+ end
32
+
33
+ # Forgets about all previously saved persisters.
34
+ def self.clear
35
+ @@persisters = nil
36
+ end
37
+
38
+ #
39
+ def self.transaction(persister_list=[], &block)
40
+ begin
41
+ if persister_list.size < 1
42
+ yeild
43
+ elsif persister_list.size == 1
44
+ persister_list.first.transaction &block
45
+ else
46
+ self.transaction(persister_list[1..(persister_list.size-1)]) do
47
+ persister_list.first.transaction &block
48
+ end
49
+ end
50
+ rescue Dirt::TransactionError => e
51
+ {errors: e.message.split("\n")}
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,38 @@
1
+ module Dirt
2
+ require 'dirt/persisters/memory_persister'
3
+
4
+ class Relation < MemoryPersister
5
+ @records = []
6
+
7
+ def initialize(records)
8
+ @records = records
9
+ end
10
+
11
+ def first
12
+ @records.first
13
+
14
+ #match = @records.first
15
+ #match ? match : nil
16
+ end
17
+
18
+ def collect(&block)
19
+ @records.collect(&block)
20
+ end
21
+
22
+ def each(&block)
23
+ @records.each(&block)
24
+ end
25
+
26
+ def empty?
27
+ @records.empty?
28
+ end
29
+
30
+ def all?(&block)
31
+ every = true
32
+
33
+ @records.each do |record|
34
+ every &= yield record
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,73 @@
1
+ require 'dirt/roles/role'
2
+ require 'dirt/persisters/persister'
3
+
4
+ # Role for adding persistability to model objects.
5
+ class Persisting < Dirt::Role
6
+ attr_reader :id
7
+
8
+ def initialize(decorated, id=nil)
9
+ super(decorated)
10
+ @id = id
11
+ end
12
+
13
+ # Saves the decorated object with the appropriate persister.
14
+ def save()
15
+ saved = persister.save(@decorated, @id)
16
+
17
+ if saved
18
+ @id = saved.id
19
+ self
20
+ end
21
+ end
22
+
23
+ # Loads persisted data into the decorated object
24
+ def load(id)
25
+ assert_exists(id)
26
+
27
+ loaded = persister.load(id)
28
+
29
+ chameleonize(loaded)
30
+
31
+ self
32
+ end
33
+
34
+ # Loads from the first record that matches the given attributes.
35
+ def load_by(attrs)
36
+ record = Persister.for(@decorated.class).find(attrs)
37
+
38
+ unless record
39
+ raise Dirt::MissingRecordError.new("No record matches #{attrs.collect { |pair| pair.join(' == ') }.join(', ')}.")
40
+ end
41
+
42
+ chameleonize(record)
43
+
44
+ self
45
+ end
46
+
47
+ # Removes the decorated object from the appropriate persister.
48
+ def delete(id)
49
+ assert_exists(id)
50
+
51
+ persister.delete(id)
52
+ end
53
+
54
+ def ==(other)
55
+ super && other.id == @id
56
+ end
57
+
58
+ private
59
+ def chameleonize(record)
60
+ @id = record.id
61
+ @decorated.update(record.to_hash.except(:id))
62
+ end
63
+
64
+ def persister
65
+ Persister.for(@decorated.class)
66
+ end
67
+
68
+ def assert_exists(id)
69
+ unless persister.exists?(id)
70
+ raise Dirt::MissingRecordError.new("That #{@decorated.class.to_s.demodulize.downcase} (id: #{id || 'nil'}) does not exist.")
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,32 @@
1
+ module Dirt
2
+ # Roles are part of the DCI system design paradigm. They decorate a model object to extend its
3
+ # behaviour for the context of a single interaction.
4
+ class Role
5
+ # Takes the decorated object.
6
+ def initialize(decorated)
7
+ @decorated = decorated
8
+ end
9
+
10
+ # Attempts to run the missing method on the decorated object before exploding as normal,
11
+ # with a light tingling sensation.
12
+ def method_missing(method, *args, &block)
13
+ # if @decorated.respond_to?(method)
14
+ @decorated.send(method, *args, &block)
15
+ # else
16
+ # super
17
+ # end
18
+ end
19
+
20
+ def class
21
+ @decorated.class
22
+ end
23
+
24
+ def respond_to?(method, privates = false)
25
+ super || @decorated.respond_to?(method, privates)
26
+ end
27
+
28
+ def ==(other)
29
+ other && (other.instance_variable_get(:@decorated) == @decorated || other == @decorated)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # A Role for testing whether the wrapped object is valid, using the list of validators provided in the constructor,
2
+ #
3
+ # == Validators
4
+ # Each validator must respond to
5
+ # * +valid?+ to return whether that validator passes, and
6
+ # * +error_message+ to return any errors found when +valid?+ is +false+.
7
+ class Validating < Dirt::Role
8
+ # Takes the decorated object and then the list of validators. See Role for more on decoration.
9
+ def initialize(decorated, validators)
10
+ super(decorated)
11
+ @validators = validators
12
+ end
13
+
14
+ # Returns +true+ _iff_ every validator passes.
15
+ def valid?
16
+ @validators.all? do |v|
17
+ v.valid?(@decorated)
18
+ end
19
+ end
20
+
21
+ # Returns a list of the string messages returned from the validators that have failed.
22
+ def errors
23
+ @validators.collect do |v|
24
+ v.error_message(@decorated) unless v.valid?(@decorated)
25
+ end.reject { |m| m.nil? }
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ # Takes a block, and uses it to determine validity,
2
+ class ClosureValidator
3
+ def initialize(block, message_block)
4
+ @block = block
5
+ @message_block = message_block
6
+ end
7
+
8
+ def valid?(validated)
9
+ @block.call(validated)
10
+ end
11
+
12
+ def error_message(validated)
13
+ @message_block.call(validated)
14
+ end
15
+ end