compo 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +8 -0
  3. data/lib/compo/branches/array.rb +17 -0
  4. data/lib/compo/branches/branch.rb +35 -0
  5. data/lib/compo/branches/constant.rb +34 -0
  6. data/lib/compo/branches/hash.rb +17 -0
  7. data/lib/compo/branches/leaf.rb +15 -0
  8. data/lib/compo/branches.rb +15 -0
  9. data/lib/compo/composites/array.rb +105 -0
  10. data/lib/compo/composites/composite.rb +183 -0
  11. data/lib/compo/composites/hash.rb +72 -0
  12. data/lib/compo/composites/leaf.rb +64 -0
  13. data/lib/compo/composites/parentless.rb +133 -0
  14. data/lib/compo/composites.rb +20 -0
  15. data/lib/compo/finders/url.rb +166 -0
  16. data/lib/compo/finders.rb +10 -0
  17. data/lib/compo/mixins/movable.rb +55 -0
  18. data/lib/compo/mixins/parent_tracker.rb +74 -0
  19. data/lib/compo/mixins/url_referenceable.rb +70 -0
  20. data/lib/compo/mixins.rb +9 -0
  21. data/lib/compo/version.rb +1 -1
  22. data/lib/compo.rb +4 -21
  23. data/spec/array_branch_spec.rb +4 -2
  24. data/spec/array_composite_shared_examples.rb +2 -2
  25. data/spec/array_composite_spec.rb +1 -1
  26. data/spec/branch_shared_examples.rb +38 -5
  27. data/spec/branch_spec.rb +1 -1
  28. data/spec/composite_shared_examples.rb +1 -1
  29. data/spec/composite_spec.rb +1 -1
  30. data/spec/constant_branch_spec.rb +18 -0
  31. data/spec/hash_branch_spec.rb +4 -2
  32. data/spec/hash_composite_shared_examples.rb +3 -3
  33. data/spec/hash_composite_spec.rb +1 -1
  34. data/spec/leaf_branch_spec.rb +9 -0
  35. data/spec/{null_composite_shared_examples.rb → leaf_composite_shared_examples.rb} +1 -1
  36. data/spec/leaf_composite_spec.rb +7 -0
  37. data/spec/movable_shared_examples.rb +3 -3
  38. data/spec/movable_spec.rb +1 -1
  39. data/spec/parent_tracker_spec.rb +2 -15
  40. data/spec/parentless_spec.rb +2 -2
  41. data/spec/url_finder_shared_examples.rb +104 -0
  42. data/spec/url_finder_spec.rb +25 -114
  43. data/spec/url_referenceable_spec.rb +1 -1
  44. metadata +30 -21
  45. data/lib/compo/array_branch.rb +0 -16
  46. data/lib/compo/array_composite.rb +0 -103
  47. data/lib/compo/branch.rb +0 -18
  48. data/lib/compo/composite.rb +0 -181
  49. data/lib/compo/hash_branch.rb +0 -17
  50. data/lib/compo/hash_composite.rb +0 -70
  51. data/lib/compo/leaf.rb +0 -14
  52. data/lib/compo/movable.rb +0 -53
  53. data/lib/compo/null_composite.rb +0 -62
  54. data/lib/compo/parent_tracker.rb +0 -80
  55. data/lib/compo/parentless.rb +0 -131
  56. data/lib/compo/url_finder.rb +0 -164
  57. data/lib/compo/url_referenceable.rb +0 -68
  58. data/spec/leaf_spec.rb +0 -9
  59. data/spec/null_composite_spec.rb +0 -7
@@ -1,103 +0,0 @@
1
- require 'forwardable'
2
- require 'compo/composite'
3
-
4
- module Compo
5
- # Implementation of Composite that stores its children in an Array.
6
- #
7
- # IDs for items entering a ListComposite must be numeric, and will change if
8
- # an item with an ID less than the item in question is deleted or inserted.
9
- # This means the ID function for objects in a ListComposite may report
10
- # different values at different times.
11
- #
12
- # Adding an object at an occupied ID moves the occupant and those at
13
- # successive IDs up by one.
14
- class ArrayComposite
15
- include Composite
16
- extend Forwardable
17
-
18
- # Initialises an ArrayComposite
19
- #
20
- # @api public
21
- # @example Initializes an ArrayComposite.
22
- # comp = ArrayComposite.new
23
- def initialize
24
- @children = []
25
- end
26
-
27
- # Returns the ArrayComposite's children, as a Hash
28
- #
29
- # @api public
30
- # @example Gets the children of an empty ArrayComposite.
31
- # comp.children
32
- # #=> {}
33
- # @example Gets the children of a populated ArrayComposite.
34
- # comp.children
35
- # #=> {0: :first, 1: :second}
36
- #
37
- # @return [Hash] The Hash mapping the IDs of children to their values.
38
- def children
39
- Hash[(0...@children.size).zip(@children)]
40
- end
41
-
42
- private
43
-
44
- def_delegator :@children, :delete, :remove!
45
- def_delegator :@children, :delete_at, :remove_id!
46
-
47
- # Inserts a child into the ArrayComposite with the given ID
48
- #
49
- # You probably want to use #add instead.
50
- #
51
- # @api private
52
- #
53
- # @param id [Object] The ID under which the child is to be added.
54
- # @param child [Object] The child to add to the ArrayComposite.
55
- #
56
- # @return [Object] The newly added child, or nil if the ID was invalid.
57
- def add!(id, child)
58
- valid_id?(id) ? do_insert(id, child) : nil
59
- end
60
-
61
- # Checks to see if the given ID is valid
62
- #
63
- # A valid ID for ArrayComposites is a number between 0 and the current
64
- # size of the children list.
65
- #
66
- # @api private
67
- #
68
- # @param id [Object] The candidate ID.
69
- #
70
- # @return [Boolean] True if the ID is valid; false if not.
71
- def valid_id?(id)
72
- id.is_a?(Numeric) && (0..@children.size).cover?(id)
73
- end
74
-
75
- # Actually performs the insertion of an item into the array
76
- #
77
- # @api private
78
- #
79
- # @param id [Numeric] The index into the array at which the child is to be
80
- # inserted.
81
- # @param child [Object] The object to insert into the children array.
82
- #
83
- # @return [Object] The inserted child.
84
- def do_insert(id, child)
85
- @children.insert(id, child)
86
- child
87
- end
88
-
89
- # Creates an ID function for the given child
90
- #
91
- # The returned proc is O(n), as it checks the child array at each call to
92
- # find the current ID of the child.
93
- #
94
- # @api private
95
- #
96
- # @param child [Object] The child whose ID is to be returned by the proc.
97
- #
98
- # @return [Proc] A proc returning the child's ID.
99
- def id_function(object)
100
- proc { @children.index(object) }
101
- end
102
- end
103
- end
data/lib/compo/branch.rb DELETED
@@ -1,18 +0,0 @@
1
- require 'compo/movable'
2
- require 'compo/parent_tracker'
3
- require 'compo/url_referenceable'
4
-
5
- module Compo
6
- # A movable, URL referenceable parent tracker
7
- #
8
- # A Branch represents a fully-featured part of a composite object. This
9
- # abstract pattern is implemented concretely by Leaf (a Branch with no
10
- # children), ArrayBranch (a Branch with a list of numerically identified
11
- # children), and HashBranch (a Branch with a hash of key-identified children).
12
- # reports no children.
13
- module Branch
14
- include Movable
15
- include ParentTracker
16
- include UrlReferenceable
17
- end
18
- end
@@ -1,181 +0,0 @@
1
- require 'forwardable'
2
-
3
- module Compo
4
- # Mixin for objects that can contain other objects
5
- #
6
- # Objects implementing this interface should implement add!, remove! or
7
- # remove_id!, and id_function:
8
- #
9
- # add! - Given a desired ID and child, adds the child to the children
10
- # of this object; returns the child if successful, nil
11
- # otherwise.
12
- # remove! - Given a child, removes and returns it from the children; if
13
- # not provided, this is implemented in terms of remove_id!.
14
- # remove_id! - Given an ID, removes and returns the child with this ID from
15
- # the children; if not provided, this is implemented in terms
16
- # of remove!.
17
- # children - Returns the children, as a Hash mapping from current IDs to
18
- # their child values.
19
- # id_function - Given a newly inserted child, returns a proc that will
20
- # always return the child's current ID so long as it is part
21
- # of the Composite.
22
- module Composite
23
- extend Forwardable
24
- include Enumerable
25
-
26
- # Adds a child to this Composite
27
- #
28
- # @api public
29
- # @example Adds a child with intended id 3.
30
- # composite.add_child(3, leaf)
31
- #
32
- # @param id [Object] The intended ID of the child in this Composite.
33
- # The actual ID may not be the same as this; consult the proc supplied
34
- # to the child via #update_parent.
35
- # @param child [Object] The child to add to this Composite.
36
- #
37
- # @return [Object] The added child if successful; nil otherwise.
38
- def add(id, child)
39
- add!(id, child).tap(&method(:assign_parent_to))
40
- end
41
-
42
- # Removes a child from this Composite directly
43
- #
44
- # This method can fail (for example, if the child does not exist in the
45
- # Composite).
46
- #
47
- # @api public
48
- # @example Removes a child.
49
- # composite.remove(child)
50
- #
51
- # @param child [Object] The child to remove from this object.
52
- #
53
- # @return [Object] The removed child if successful; nil otherwise.
54
- def remove(child)
55
- remove!(child).tap(&method(:remove_parent_of))
56
- end
57
-
58
- # Removes a child from this Composite, given its ID
59
- #
60
- # This method can fail (for example, if the ID does not exist in the
61
- # Composite).
62
- #
63
- # @api public
64
- # @example Removes the child with ID :foo.
65
- # composite.remove_id(:foo)
66
- #
67
- # @param id The ID of the child to remove from this object.
68
- #
69
- # @return [Object] The removed child if successful; nil otherwise.
70
- def remove_id(id)
71
- remove_id!(id).tap(&method(:remove_parent_of))
72
- end
73
-
74
- # Gets the child in this Composite with the given ID
75
- #
76
- # The ID is compared directly against the IDs of the children of this
77
- # composite. To use a predicate to find an ID, use #get_child_such_that.
78
- #
79
- # @api public
80
- # @example Gets the child with ID :in, if children is {in: 3}.
81
- # composite.get_child(:in)
82
- # #=> 3
83
- # @example Fails to get the child with ID :out, if children is {in: 3}.
84
- # composite.get_child(:out)
85
- # #=> nil
86
- # @example Fails to get the child with ID '1', if children is {1 => 3}.
87
- # composite.get_child('1')
88
- # #=> nil
89
- #
90
- # @param id [Object] The ID of the child to get from this Composite.
91
- #
92
- # @return [Object] The child if successful; nil otherwise.
93
- def get_child(id)
94
- children[id]
95
- end
96
-
97
- # Gets the child in this Composite whose ID matches a given predicate
98
- #
99
- # If multiple children match this predicate, the result is the first child
100
- # in the hash.
101
- #
102
- # @api public
103
- # @example Gets the child with ID :in, if children is {in: 3}.
104
- # composite.get_child_such_that { |x| x == :in }
105
- # #=> 3
106
- # @example Fails to get the child with ID :out, if children is {in: 3}.
107
- # composite.get_child_such_that { |x| x == :out }
108
- # #=> nil
109
- # @example Get the child with an ID whose string form is '1', if children
110
- # is {1 => 3}.
111
- # composite.get_child_such_that { |x| x.to_s == '3' }
112
- # #=> 3
113
- #
114
- # @yieldparam id [Object] An ID to check against the predicate.
115
- #
116
- # @return [Object] The child if successful; nil otherwise.
117
- def get_child_such_that(&block)
118
- child = children.each.find { |k, _| block.call(k) }
119
- (_, value) = child unless child.nil?
120
- value
121
- end
122
-
123
- def_delegator :children, :each
124
-
125
- protected
126
-
127
- # Assigns this object to a child as its parent
128
- #
129
- # This also updates its ID function to point to the child's ID under this
130
- # parent.
131
- #
132
- # @api private
133
- #
134
- # @param child [Object] The child whose parent assignment is being set.
135
- #
136
- # @return [void]
137
- def assign_parent_to(child)
138
- child.update_parent(self, id_function(child)) unless child.nil?
139
- end
140
-
141
- # Removes a child's parent assignment
142
- #
143
- # This also clears its ID function.
144
- #
145
- # @api private
146
- #
147
- # @param child [Object] The child whose parent assignment is being set.
148
- #
149
- # @return [void]
150
- def remove_parent_of(child)
151
- Parentless.for(child)
152
- end
153
-
154
- # Default implementation of #remove! in terms of #remove_id!
155
- #
156
- # Either this or #remove_id! must be overridden by the implementing class.
157
- #
158
- # @api private
159
- #
160
- # @param child [Object] The child to remove from this object.
161
- #
162
- # @return [void]
163
- def remove!(child)
164
- remove_id!(children.key(child))
165
- end
166
-
167
- # Default implementation of #remove_id! in terms of #remove!
168
- #
169
- # Either this or #remove! must be overridden by the implementing class.
170
- #
171
- # @api private
172
- #
173
- # @param id [Object] The current ID of the child to remove from this
174
- # object.
175
- #
176
- # @return [void]
177
- def remove_id!(id)
178
- remove!(get_child(id))
179
- end
180
- end
181
- end
@@ -1,17 +0,0 @@
1
- require 'compo/branch'
2
-
3
- module Compo
4
- # A simple implementation of a branch, whose children are stored in an Hash
5
- #
6
- # An HashBranch is a composite object that may be moved into other composite
7
- # objects. It stores its children as an Hash, and the ID of each child is
8
- # its hash key. Inserting and removing items into the HashBranch will not
9
- # change the IDs of other items, but inserting with an existing key will
10
- # remove the previous occupant.
11
- #
12
- # This is an extension of HashComposite to include the Movable and
13
- # ParentTracker mixins.
14
- class HashBranch < HashComposite
15
- include Branch
16
- end
17
- end
@@ -1,70 +0,0 @@
1
- require 'forwardable'
2
- require 'compo/composite'
3
-
4
- module Compo
5
- # Implementation of Composite that stores its children in a Hash.
6
- #
7
- # IDs for items entering a ListComposite may be any permissible hash.
8
- #
9
- # Adding an item at an occupied ID removes the occupant.
10
- class HashComposite
11
- include Composite
12
- extend Forwardable
13
-
14
- # Initialises a HashComposite
15
- #
16
- # @api public
17
- # @example Initializes a HashComposite.
18
- # comp = HashComposite.new
19
- def initialize
20
- @children = {}
21
- end
22
-
23
- # Returns the HashComposite's children, as a Hash
24
- #
25
- # @api public
26
- # @example Gets the children of an empty HashComposite.
27
- # comp.children
28
- # #=> {}
29
- # @example Gets the children of a populated HashComposite.
30
- # comp.children
31
- # #=> {foo: 3, bar: 5}
32
- #
33
- # @return [Hash] The Hash mapping the IDs of children to their values.
34
- attr_reader :children
35
-
36
- private
37
-
38
- # Inserts a child into the HashComposite with the given ID
39
- #
40
- # You probably want to use #add instead.
41
- #
42
- # @api private
43
- #
44
- # @param id [Object] The ID under which the child is to be added.
45
- # @param child [Object] The child to add to the HashComposite.
46
- #
47
- # @return [Object] The newly added child.
48
- def add!(id, child)
49
- remove_id(id)
50
- @children[id] = child
51
- end
52
-
53
- def_delegator :@children, :delete, :remove_id!
54
-
55
- # Creates an ID function for the given child
56
- #
57
- # The returned proc is O(1), as it stores the ID assigned to the child at
58
- # calling time under the assumption that it will not change until removal.
59
- #
60
- # @api private
61
- #
62
- # @param child [Object] The child whose ID is to be returned by the proc.
63
- #
64
- # @return [Proc] A proc returning the child's ID.
65
- def id_function(child)
66
- id = @children.key(child)
67
- proc { id }
68
- end
69
- end
70
- end
data/lib/compo/leaf.rb DELETED
@@ -1,14 +0,0 @@
1
- require 'compo/branch'
2
- require 'compo/null_composite'
3
- require 'compo/parent_tracker'
4
-
5
- module Compo
6
- # A simple implementation of a leaf node
7
- #
8
- # Leaves have no children, but can be moved to one. They implement the
9
- # Composite API, but all additions and removals fail, and the Leaf always
10
- # reports no children.
11
- class Leaf < NullComposite
12
- include Branch
13
- end
14
- end
data/lib/compo/movable.rb DELETED
@@ -1,53 +0,0 @@
1
- module Compo
2
- # Helper mixin for objects that can be moved into other objects
3
- #
4
- # This mixin defines a method, #move_to, which handles removing a child
5
- # from its current parent and adding it to a new one.
6
- #
7
- # It expects the current parent to be reachable from #parent.
8
- module Movable
9
- # Moves this model object to a new parent with a new ID
10
- #
11
- # @api public
12
- # @example Moves the object to a new parent.
13
- # movable.move_to(parent, :id)
14
- # @example Moves the object out of its parent (deleting it, if there are
15
- # no other live references).
16
- # movable.move_to(nil, nil)
17
- #
18
- # @param new_parent [ModelObject] The new parent for this object (can be
19
- # nil).
20
- # @param new_id [Object] The new ID under which the object will exist in
21
- # the parent.
22
- #
23
- # @return [self]
24
- def move_to(new_parent, new_id)
25
- move_from_old_parent
26
- move_to_new_parent(new_parent, new_id)
27
- self
28
- end
29
-
30
- private
31
-
32
- # Performs the move from an old parent, if necessary
33
- #
34
- # @api private
35
- #
36
- # @return [void]
37
- def move_from_old_parent
38
- parent.remove(self)
39
- end
40
-
41
- # Performs the move to a new parent, if necessary
42
- #
43
- # @api private
44
- #
45
- # @param new_parent [Composite] The target parent of the move.
46
- # @param new_id [Object] The intended new ID of this child.
47
- #
48
- # @return [void]
49
- def move_to_new_parent(new_parent, new_id)
50
- new_parent.add(new_id, self) unless new_parent.nil?
51
- end
52
- end
53
- end
@@ -1,62 +0,0 @@
1
- require 'compo/composite'
2
-
3
- module Compo
4
- # Null implementation of Composite
5
- #
6
- # Add/remove operations on NullComposite always fail, and #children always
7
- # returns the empty hash.
8
- #
9
- # This is useful for leaf classes that still need to expose the composite
10
- # API.
11
- class NullComposite
12
- include Composite
13
-
14
- # Returns the empty hash
15
- #
16
- # @api public
17
- # @example Gets the children
18
- # comp.children
19
- # #=> {}
20
- #
21
- # @return [Hash] The empty hash.
22
- def children
23
- {}
24
- end
25
-
26
- private
27
-
28
- # Fails to add a child into the NullComposite
29
- #
30
- # @api private
31
- #
32
- # @param id [Object] Ignored.
33
- # @param child [Object] Ignored.
34
- #
35
- # @return [nil]
36
- def add!(_, _)
37
- nil
38
- end
39
-
40
- # Fails to remove the given child
41
- #
42
- # @api private
43
- #
44
- # @param child [Object] Ignored.
45
- #
46
- # @return [nil]
47
- def remove!(_)
48
- nil
49
- end
50
-
51
- # Fails to remove the child with the given ID
52
- #
53
- # @api private
54
- #
55
- # @param id [Object] Ignored.
56
- #
57
- # @return [nil]
58
- def remove_id!(_)
59
- nil
60
- end
61
- end
62
- end
@@ -1,80 +0,0 @@
1
- require 'forwardable'
2
- require 'compo/parentless'
3
-
4
- module Compo
5
- # Basic implementation of parent tracking as a mixin
6
- #
7
- # This implements #parent, #update_parent and #remove_parent to track the
8
- # current parent and ID function as instance variables. It also implements
9
- # #parent, and #id in terms of the ID function.
10
- module ParentTracker
11
- extend Forwardable
12
-
13
- # Initialises the ParentTracker
14
- #
15
- # This constructor sets the tracker up so it initially has an instance of
16
- # Parentless as its 'parent'.
17
- #
18
- # @api semipublic
19
- # @example Creates a new ParentTracker.
20
- # ParentTracker.new
21
- #
22
- # @return [Void]
23
- def initialize
24
- super()
25
- remove_parent
26
- end
27
-
28
- # Gets this object's current ID
29
- #
30
- # @api public
31
- # @example Gets the object's parent while it has none.
32
- # parent_tracker.parent
33
- # #=> nil
34
- # @example Gets the object's parent while it has one.
35
- # parent_tracker.parent
36
- # #=> :the_current_parent
37
- #
38
- # @return [Composite] The current parent.
39
- attr_reader :parent
40
-
41
- # Gets this object's current ID
42
- #
43
- # @api public
44
- # @example Gets the object's ID while it has no parent.
45
- # parent_tracker.id
46
- # #=> nil
47
- # @example Gets the object's ID while it has a parent.
48
- # parent_tracker.id
49
- # #=> :the_current_id
50
- #
51
- # @return [Object] The current ID.
52
- def_delegator :@id_function, :call, :id
53
-
54
- # Updates this object's parent and ID function
55
- #
56
- # @api public
57
- # @example Update this Leaf's parent and ID function.
58
- # parent_tracker.update_parent(new_parent, new_id_function)
59
- #
60
- # @return [void]
61
- def update_parent(new_parent, new_id_function)
62
- fail 'Parent cannot be nil: use #remove_parent.' if new_parent.nil?
63
- fail 'ID function cannot be nil: use -> { nil }.' if new_id_function.nil?
64
-
65
- @parent = new_parent
66
- @id_function = new_id_function
67
- end
68
-
69
- # Blanks out this object's parent and ID function
70
- #
71
- # @api public
72
- # @example Update this Leaf's parent and ID function.
73
- # movable.update_parent(new_parent, new_id_function)
74
- #
75
- # @return [void]
76
- def remove_parent
77
- update_parent(Parentless.new, -> { nil })
78
- end
79
- end
80
- end