dirt-core 2.2.3

Sign up to get free protection for your applications and to get access to all the features.
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