compo 0.3.1 → 0.4.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.
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