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.
- checksums.yaml +4 -4
- data/CHANGELOG +8 -0
- data/lib/compo/branches/array.rb +17 -0
- data/lib/compo/branches/branch.rb +35 -0
- data/lib/compo/branches/constant.rb +34 -0
- data/lib/compo/branches/hash.rb +17 -0
- data/lib/compo/branches/leaf.rb +15 -0
- data/lib/compo/branches.rb +15 -0
- data/lib/compo/composites/array.rb +105 -0
- data/lib/compo/composites/composite.rb +183 -0
- data/lib/compo/composites/hash.rb +72 -0
- data/lib/compo/composites/leaf.rb +64 -0
- data/lib/compo/composites/parentless.rb +133 -0
- data/lib/compo/composites.rb +20 -0
- data/lib/compo/finders/url.rb +166 -0
- data/lib/compo/finders.rb +10 -0
- data/lib/compo/mixins/movable.rb +55 -0
- data/lib/compo/mixins/parent_tracker.rb +74 -0
- data/lib/compo/mixins/url_referenceable.rb +70 -0
- data/lib/compo/mixins.rb +9 -0
- data/lib/compo/version.rb +1 -1
- data/lib/compo.rb +4 -21
- data/spec/array_branch_spec.rb +4 -2
- data/spec/array_composite_shared_examples.rb +2 -2
- data/spec/array_composite_spec.rb +1 -1
- data/spec/branch_shared_examples.rb +38 -5
- data/spec/branch_spec.rb +1 -1
- data/spec/composite_shared_examples.rb +1 -1
- data/spec/composite_spec.rb +1 -1
- data/spec/constant_branch_spec.rb +18 -0
- data/spec/hash_branch_spec.rb +4 -2
- data/spec/hash_composite_shared_examples.rb +3 -3
- data/spec/hash_composite_spec.rb +1 -1
- data/spec/leaf_branch_spec.rb +9 -0
- data/spec/{null_composite_shared_examples.rb → leaf_composite_shared_examples.rb} +1 -1
- data/spec/leaf_composite_spec.rb +7 -0
- data/spec/movable_shared_examples.rb +3 -3
- data/spec/movable_spec.rb +1 -1
- data/spec/parent_tracker_spec.rb +2 -15
- data/spec/parentless_spec.rb +2 -2
- data/spec/url_finder_shared_examples.rb +104 -0
- data/spec/url_finder_spec.rb +25 -114
- data/spec/url_referenceable_spec.rb +1 -1
- metadata +30 -21
- data/lib/compo/array_branch.rb +0 -16
- data/lib/compo/array_composite.rb +0 -103
- data/lib/compo/branch.rb +0 -18
- data/lib/compo/composite.rb +0 -181
- data/lib/compo/hash_branch.rb +0 -17
- data/lib/compo/hash_composite.rb +0 -70
- data/lib/compo/leaf.rb +0 -14
- data/lib/compo/movable.rb +0 -53
- data/lib/compo/null_composite.rb +0 -62
- data/lib/compo/parent_tracker.rb +0 -80
- data/lib/compo/parentless.rb +0 -131
- data/lib/compo/url_finder.rb +0 -164
- data/lib/compo/url_referenceable.rb +0 -68
- data/spec/leaf_spec.rb +0 -9
- data/spec/null_composite_spec.rb +0 -7
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'compo/composites/composite'
|
2
|
+
|
3
|
+
module Compo
|
4
|
+
module Composites
|
5
|
+
# A Composite that represents the non-existent parent of an orphan
|
6
|
+
#
|
7
|
+
# Parentless is the parent assigned when an object is removed from a
|
8
|
+
# Composite, and should be the default parent of an object that can be
|
9
|
+
# added to one. It exists to make some operations easier, such as URL
|
10
|
+
# creation.
|
11
|
+
class Parentless
|
12
|
+
include Composite
|
13
|
+
|
14
|
+
# Creates a new instance of Parentless and adds an item to it
|
15
|
+
#
|
16
|
+
# This effectively removes the item's parent.
|
17
|
+
#
|
18
|
+
# If this method is passed nil, then nothing happens.
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
# @example Makes a new Parentless for an item.
|
22
|
+
# Parentless.for(item)
|
23
|
+
# @example Does nothing.
|
24
|
+
# Parentless.for(nil)
|
25
|
+
#
|
26
|
+
# @param item [Object] The item to be reparented to a Parentless.
|
27
|
+
#
|
28
|
+
# @return [void]
|
29
|
+
def self.for(item)
|
30
|
+
new.add(nil, item) unless item.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
# 'Removes' a child from this Parentless
|
34
|
+
#
|
35
|
+
# This always succeeds, and never triggers any other action.
|
36
|
+
#
|
37
|
+
# @api public
|
38
|
+
# @example 'Removes' a child.
|
39
|
+
# parentless.remove(child)
|
40
|
+
#
|
41
|
+
# @param child [Object] The child to 'remove' from this Parentless.
|
42
|
+
#
|
43
|
+
# @return [Object] The child.
|
44
|
+
def remove(child)
|
45
|
+
child
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the empty hash
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
# @example Gets the children
|
52
|
+
# parentless.children
|
53
|
+
# #=> {}
|
54
|
+
#
|
55
|
+
# @return [Hash] The empty hash.
|
56
|
+
def children
|
57
|
+
{}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the URL of this Parentless
|
61
|
+
#
|
62
|
+
# This is always the empty string.
|
63
|
+
#
|
64
|
+
# @api public
|
65
|
+
# @example Gets the URL of a Parentless
|
66
|
+
# parentless.url
|
67
|
+
# #=> ''
|
68
|
+
#
|
69
|
+
# @return [Hash] The empty string.
|
70
|
+
def url
|
71
|
+
''
|
72
|
+
end
|
73
|
+
|
74
|
+
# Given the ID of a child in this Parentless, returns that child's URL
|
75
|
+
#
|
76
|
+
# This is always the empty string. This is so that children of orphan
|
77
|
+
# objects have URLs starting with /their_id.
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
# @example Gets the URL of the child of a Parentless.
|
81
|
+
# parentless.child_url(:child_id)
|
82
|
+
# #=> ''
|
83
|
+
#
|
84
|
+
# @return [Hash] The empty string.
|
85
|
+
def child_url(_)
|
86
|
+
''
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns the parent of this Parentless
|
90
|
+
#
|
91
|
+
# This is always the same Parentless, for convenience's sake.
|
92
|
+
# Technically, as a null object, Parentless has no parent.
|
93
|
+
#
|
94
|
+
# @api public
|
95
|
+
# @example Gets the 'parent' of a Parentless.
|
96
|
+
# parentless.parent
|
97
|
+
#
|
98
|
+
# @return [self]
|
99
|
+
def parent
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
# 'Adds' a child to this Parentless
|
106
|
+
#
|
107
|
+
# This always succeeds.
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
#
|
111
|
+
# @param id [Object] Ignored.
|
112
|
+
# @param child [Object] The object to 'add' to this Parentless.
|
113
|
+
#
|
114
|
+
# @return [Object] The child.
|
115
|
+
def add!(_, child)
|
116
|
+
child
|
117
|
+
end
|
118
|
+
|
119
|
+
# Creates an ID function for the given child
|
120
|
+
#
|
121
|
+
# The returned proc is O(1), and always returns nil.
|
122
|
+
#
|
123
|
+
# @api private
|
124
|
+
#
|
125
|
+
# @param child [Object] The child whose ID is to be returned by the proc.
|
126
|
+
#
|
127
|
+
# @return [Proc] A proc returning nil.
|
128
|
+
def id_function(_)
|
129
|
+
-> { nil }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'compo/composites/composite'
|
2
|
+
require 'compo/composites/array'
|
3
|
+
require 'compo/composites/hash'
|
4
|
+
require 'compo/composites/leaf'
|
5
|
+
require 'compo/composites/parentless'
|
6
|
+
|
7
|
+
module Compo
|
8
|
+
# Submodule containing implementations of composite objects
|
9
|
+
#
|
10
|
+
# These classes only implement the concept of an object containing children,
|
11
|
+
# and those children being stored in such a way that they can be retrieved by
|
12
|
+
# ID. For the full Compo experience, including parent tracking, URL
|
13
|
+
# referencing, and moving of objects between parents, see the Branches
|
14
|
+
# submodule.
|
15
|
+
#
|
16
|
+
# The Composites are the lowest level of Compo; everything else is an
|
17
|
+
# extension to them.
|
18
|
+
module Composites
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
module Compo
|
2
|
+
module Finders
|
3
|
+
# A method object for finding an item in a composite tree via its URL
|
4
|
+
#
|
5
|
+
# These are *not* thread-safe.
|
6
|
+
class Url
|
7
|
+
# Initialises an URL finder
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
# @example Initialises an UrlFinder with default missing resource
|
11
|
+
# handling.
|
12
|
+
# UrlFinder.new(composite, 'a/b/c')
|
13
|
+
# @example Initialises an UrlFinder returning a default value.
|
14
|
+
# UrlFinder.new(composite, 'a/b/c', missing_proc=->(_) { :default })
|
15
|
+
#
|
16
|
+
# @param root [Composite] A composite object serving as the root of the
|
17
|
+
# search tree, and the URL.
|
18
|
+
#
|
19
|
+
# @param url [String] A partial URL that follows this model object's URL
|
20
|
+
# to form the URL of the resource to locate. Can be nil, in which case
|
21
|
+
# this object is returned.
|
22
|
+
#
|
23
|
+
# @param missing_proc [Proc] A proc to call, with the requested URL, if
|
24
|
+
# the resource could not be found. If nil (the default), this raises a
|
25
|
+
# string exception.
|
26
|
+
def initialize(root, url, missing_proc: nil)
|
27
|
+
@root = root
|
28
|
+
@url = url
|
29
|
+
@missing_proc = missing_proc || method(:default_missing_proc)
|
30
|
+
|
31
|
+
reset
|
32
|
+
end
|
33
|
+
|
34
|
+
# Finds the model object at a URL, given a model root
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
# @example Finds a URL with default missing resource handling.
|
38
|
+
# UrlFinder.find(composite, 'a/b/c') { |item| p item }
|
39
|
+
#
|
40
|
+
# @param (see #initialize)
|
41
|
+
#
|
42
|
+
# @yieldparam (see #run)
|
43
|
+
#
|
44
|
+
# @return [Object] The return value of the block.
|
45
|
+
def self.find(*args, &block)
|
46
|
+
new(*args).run(&block)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Attempts to find a child resource with the given partial URL
|
50
|
+
#
|
51
|
+
# If the resource is found, it will be yielded to the attached block;
|
52
|
+
# otherwise, an exception will be raised.
|
53
|
+
#
|
54
|
+
# @api public
|
55
|
+
# @example Runs an UrlFinder, returning the item unchanged.
|
56
|
+
# finder.run { |item| item }
|
57
|
+
# #=> item
|
58
|
+
#
|
59
|
+
# @yieldparam resource [ModelObject] The resource found.
|
60
|
+
# @yieldparam args [Array] The splat from above.
|
61
|
+
#
|
62
|
+
# @return [Object] The return value of the block.
|
63
|
+
def run
|
64
|
+
# We're traversing down the URL by repeatedly splitting it into its
|
65
|
+
# head (part before the next /) and tail (part after). While we still
|
66
|
+
# have a tail, then the URL still needs walking down.
|
67
|
+
reset
|
68
|
+
descend until hit_end_of_url?
|
69
|
+
yield @resource
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Performs a descending step in the URL finder
|
75
|
+
#
|
76
|
+
# This tries to move down a level of the URL hierarchy, fetches the
|
77
|
+
# resource at that level, and fails according to @missing_proc if there is
|
78
|
+
# no such resource.
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
#
|
82
|
+
# @return [Void]
|
83
|
+
def descend
|
84
|
+
descend_url
|
85
|
+
next_resource
|
86
|
+
fail_with_no_resource if @resource.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
# Seeks to the next resource pointed at by @next_id
|
90
|
+
#
|
91
|
+
# @api private
|
92
|
+
#
|
93
|
+
# @return [Void]
|
94
|
+
def next_resource
|
95
|
+
@resource = @resource.get_child_such_that { |id| id.to_s == @next_id }
|
96
|
+
end
|
97
|
+
|
98
|
+
# Fails, using @missing_proc, due to a missing resource
|
99
|
+
#
|
100
|
+
# @api private
|
101
|
+
#
|
102
|
+
# @return [Void]
|
103
|
+
def fail_with_no_resource
|
104
|
+
# If the proc returns a value instead of raising an error, then set
|
105
|
+
# things up so that value is yielded in place of the missing resource.
|
106
|
+
@tail = nil
|
107
|
+
@resource = @missing_proc.call(@url)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Default value for @missing_proc
|
111
|
+
#
|
112
|
+
# @api private
|
113
|
+
#
|
114
|
+
# @param url [String] The URL whose finding failed.
|
115
|
+
#
|
116
|
+
# @return [Void]
|
117
|
+
def default_missing_proc(url)
|
118
|
+
fail("Could not find resource: #{url}")
|
119
|
+
end
|
120
|
+
|
121
|
+
# Decides whether we have reached the end of the URL
|
122
|
+
#
|
123
|
+
# @api private
|
124
|
+
#
|
125
|
+
# @return [Boolean] Whether we have hit the end of the URL.
|
126
|
+
def hit_end_of_url?
|
127
|
+
@tail.nil? || @tail.empty?
|
128
|
+
end
|
129
|
+
|
130
|
+
# Splits the tail on the next URL level
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
#
|
134
|
+
# @return [Void]
|
135
|
+
def descend_url
|
136
|
+
@next_id, @tail = @tail.split('/', 2)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Resets this UrlFinder so it can be used again
|
140
|
+
#
|
141
|
+
# @api private
|
142
|
+
#
|
143
|
+
# @return [Void]
|
144
|
+
def reset
|
145
|
+
@next_id, @tail = nil, trimmed_url
|
146
|
+
@resource = @root
|
147
|
+
end
|
148
|
+
|
149
|
+
# Removes any leading or trailing slash from the URL, returning the result
|
150
|
+
#
|
151
|
+
# This only removes one leading or trailing slash. Thus, '///' will be
|
152
|
+
# returned as '/'.
|
153
|
+
#
|
154
|
+
# @api private
|
155
|
+
#
|
156
|
+
# @return [String] The URL with no trailing or leading slash.
|
157
|
+
def trimmed_url
|
158
|
+
first, last = 0, @url.length
|
159
|
+
first += 1 if @url.start_with?('/')
|
160
|
+
last -= 1 if @url.end_with?('/')
|
161
|
+
|
162
|
+
@url[first...last]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Compo
|
2
|
+
module Mixins
|
3
|
+
# Helper mixin for objects that can be moved into other objects
|
4
|
+
#
|
5
|
+
# This mixin defines a method, #move_to, which handles removing a child
|
6
|
+
# from its current parent and adding it to a new one.
|
7
|
+
#
|
8
|
+
# It expects the current parent to be reachable from #parent.
|
9
|
+
module Movable
|
10
|
+
# Moves this model object to a new parent with a new ID
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
# @example Moves the object to a new parent.
|
14
|
+
# movable.move_to(parent, :id)
|
15
|
+
# @example Moves the object out of its parent (deleting it, if there are
|
16
|
+
# no other live references).
|
17
|
+
# movable.move_to(nil, nil)
|
18
|
+
#
|
19
|
+
# @param new_parent [ModelObject] The new parent for this object (can be
|
20
|
+
# nil).
|
21
|
+
# @param new_id [Object] The new ID under which the object will exist in
|
22
|
+
# the parent.
|
23
|
+
#
|
24
|
+
# @return [self]
|
25
|
+
def move_to(new_parent, new_id)
|
26
|
+
move_from_old_parent
|
27
|
+
move_to_new_parent(new_parent, new_id)
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Performs the move from an old parent, if necessary
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
#
|
37
|
+
# @return [void]
|
38
|
+
def move_from_old_parent
|
39
|
+
parent.remove(self)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Performs the move to a new parent, if necessary
|
43
|
+
#
|
44
|
+
# @api private
|
45
|
+
#
|
46
|
+
# @param new_parent [Composite] The target parent of the move.
|
47
|
+
# @param new_id [Object] The intended new ID of this child.
|
48
|
+
#
|
49
|
+
# @return [void]
|
50
|
+
def move_to_new_parent(new_parent, new_id)
|
51
|
+
new_parent.add(new_id, self) unless new_parent.nil?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'compo/composites/parentless'
|
3
|
+
|
4
|
+
module Compo
|
5
|
+
module Mixins
|
6
|
+
# Basic implementation of parent tracking as a mixin
|
7
|
+
#
|
8
|
+
# Adding this to a Composite allows the composite to be aware of its current
|
9
|
+
# parent.
|
10
|
+
#
|
11
|
+
# This implements #parent, #update_parent and #remove_parent to track the
|
12
|
+
# current parent and ID function as instance variables. It also implements
|
13
|
+
# #parent, and #id in terms of the ID function.
|
14
|
+
module ParentTracker
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
# Initialises the ParentTracker
|
18
|
+
#
|
19
|
+
# This constructor sets the tracker up so it initially has an instance of
|
20
|
+
# Parentless as its 'parent'.
|
21
|
+
#
|
22
|
+
# @api semipublic
|
23
|
+
# @example Creates a new ParentTracker.
|
24
|
+
# ParentTracker.new
|
25
|
+
#
|
26
|
+
# @return [Void]
|
27
|
+
def initialize
|
28
|
+
super()
|
29
|
+
Compo::Composites::Parentless.for(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Gets this object's current ID
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
# @example Gets the object's parent while it has none.
|
36
|
+
# parent_tracker.parent
|
37
|
+
# #=> nil
|
38
|
+
# @example Gets the object's parent while it has one.
|
39
|
+
# parent_tracker.parent
|
40
|
+
# #=> :the_current_parent
|
41
|
+
#
|
42
|
+
# @return [Composite] The current parent.
|
43
|
+
attr_reader :parent
|
44
|
+
|
45
|
+
# Gets this object's current ID
|
46
|
+
#
|
47
|
+
# @api public
|
48
|
+
# @example Gets the object's ID while it has no parent.
|
49
|
+
# parent_tracker.id
|
50
|
+
# #=> nil
|
51
|
+
# @example Gets the object's ID while it has a parent.
|
52
|
+
# parent_tracker.id
|
53
|
+
# #=> :the_current_id
|
54
|
+
#
|
55
|
+
# @return [Object] The current ID.
|
56
|
+
def_delegator :@id_proc, :call, :id
|
57
|
+
|
58
|
+
# Updates this object's parent and ID function
|
59
|
+
#
|
60
|
+
# @api public
|
61
|
+
# @example Update this Leaf's parent and ID function.
|
62
|
+
# parent_tracker.update_parent(new_parent, new_id_function)
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
def update_parent(new_parent, new_id_proc)
|
66
|
+
fail 'Parent cannot be nil: use #remove_parent.' if new_parent.nil?
|
67
|
+
fail 'ID function cannot be nil: use -> { nil }.' if new_id_proc.nil?
|
68
|
+
|
69
|
+
@parent = new_parent
|
70
|
+
@id_proc = new_id_proc
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Compo
|
2
|
+
module Mixins
|
3
|
+
# Adds ID-based 'URL's to Compo classes
|
4
|
+
#
|
5
|
+
# For the purposes of this module, a URL is a string of slash-delimited IDs
|
6
|
+
# representing the location of a Composite in the tree structure formed by
|
7
|
+
# its ancestors. Depending on the types of IDs used in the structure, the
|
8
|
+
# URLs may not actually be literal Uniform Resource Locators.
|
9
|
+
#
|
10
|
+
# This module expects its includer to define #parent and #id. These are
|
11
|
+
# defined, for example, by the Compo::ParentTracker mixin.
|
12
|
+
module UrlReferenceable
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
# Returns the URL of this object
|
16
|
+
#
|
17
|
+
# The #url of a Composite is defined inductively as '' for composites that
|
18
|
+
# have no parent, and the joining of the parent URL and the current ID
|
19
|
+
# otherwise.
|
20
|
+
#
|
21
|
+
# The result of #url can be used to give a URL hierarchy to Composites.
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
# @example Gets the URL of an object with no parent.
|
25
|
+
# orphan.url
|
26
|
+
# #=> ''
|
27
|
+
# @example Gets the URL of an object with a parent.
|
28
|
+
# leaf.url
|
29
|
+
# #=> 'grandparent_id/parent_id/id'
|
30
|
+
#
|
31
|
+
# @return [String] The URL of this object.
|
32
|
+
def url
|
33
|
+
parent.child_url(id)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the URL of a child of this object, with the given ID
|
37
|
+
#
|
38
|
+
# This defaults to joining the ID to this object's URL with a slash.
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
# @example Gets the URL of the child of an object without a parent.
|
42
|
+
# orphan.child_url(:id)
|
43
|
+
# #=> '/id'
|
44
|
+
# @example Gets the URL of the child of an object with a parent.
|
45
|
+
# leaf.child_url(:id)
|
46
|
+
# #=> 'grandparent_id/parent_id/id'
|
47
|
+
#
|
48
|
+
# @param child_id [Object] The ID of the child whose URL is sought.
|
49
|
+
#
|
50
|
+
# @return [String] The URL of the child with the given ID.
|
51
|
+
def child_url(child_id)
|
52
|
+
[url, child_id].join('/')
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the URL of this object's parent
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
# @example Gets the parent URL of an object with no parent.
|
59
|
+
# orphan.parent_url
|
60
|
+
# #=> nil
|
61
|
+
# @example Gets the URL of an object with a parent.
|
62
|
+
# leaf.parent_url
|
63
|
+
# #=> 'grandparent_id/parent_id'
|
64
|
+
#
|
65
|
+
# @return [String] The URL of this object's parent, or nil if there is no
|
66
|
+
# parent.
|
67
|
+
def_delegator :parent, :url, :parent_url
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/compo/mixins.rb
ADDED
data/lib/compo/version.rb
CHANGED
data/lib/compo.rb
CHANGED
@@ -1,24 +1,7 @@
|
|
1
|
-
|
2
|
-
require 'compo/
|
3
|
-
require 'compo/
|
4
|
-
require 'compo/
|
5
|
-
require 'compo/url_referenceable'
|
6
|
-
|
7
|
-
# Composite implementations
|
8
|
-
require 'compo/array_composite'
|
9
|
-
require 'compo/hash_composite'
|
10
|
-
require 'compo/null_composite'
|
11
|
-
require 'compo/parentless'
|
12
|
-
|
13
|
-
# Leaf and branch classes
|
14
|
-
require 'compo/array_branch'
|
15
|
-
require 'compo/hash_branch'
|
16
|
-
require 'compo/leaf'
|
17
|
-
|
18
|
-
# Utilities
|
19
|
-
require 'compo/url_finder'
|
20
|
-
|
21
|
-
# Misc
|
1
|
+
require 'compo/composites'
|
2
|
+
require 'compo/mixins'
|
3
|
+
require 'compo/branches'
|
4
|
+
require 'compo/finders'
|
22
5
|
require 'compo/version'
|
23
6
|
|
24
7
|
# The main module for Compo
|
data/spec/array_branch_spec.rb
CHANGED
@@ -3,7 +3,9 @@ require 'compo'
|
|
3
3
|
require 'array_composite_shared_examples'
|
4
4
|
require 'branch_shared_examples'
|
5
5
|
|
6
|
-
describe Compo::
|
7
|
-
it_behaves_like 'a branch'
|
6
|
+
describe Compo::Branches::Array do
|
7
|
+
it_behaves_like 'a branch with children' do
|
8
|
+
let(:initial_ids) { [0, 1] }
|
9
|
+
end
|
8
10
|
it_behaves_like 'an array composite'
|
9
11
|
end
|
@@ -123,7 +123,7 @@ shared_examples 'an array composite' do
|
|
123
123
|
|
124
124
|
it 'calls #update_parent on the child with a Parentless' do
|
125
125
|
expect(child1).to receive(:update_parent).once do |parent, _|
|
126
|
-
expect(parent).to be_a(Compo::Parentless)
|
126
|
+
expect(parent).to be_a(Compo::Composites::Parentless)
|
127
127
|
end
|
128
128
|
subject.remove(child1)
|
129
129
|
end
|
@@ -171,7 +171,7 @@ shared_examples 'an array composite' do
|
|
171
171
|
|
172
172
|
it 'calls #update_parent on the child with a Parentless' do
|
173
173
|
expect(child1).to receive(:update_parent).once do |parent, _|
|
174
|
-
expect(parent).to be_a(Compo::Parentless)
|
174
|
+
expect(parent).to be_a(Compo::Composites::Parentless)
|
175
175
|
end
|
176
176
|
subject.remove_id(0)
|
177
177
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'url_finder_shared_examples'
|
1
2
|
require 'url_referenceable_shared_examples'
|
2
3
|
require 'movable_shared_examples'
|
3
4
|
|
@@ -7,7 +8,7 @@ shared_examples 'a branch' do
|
|
7
8
|
|
8
9
|
describe '#initialize' do
|
9
10
|
it 'initialises with a Parentless as parent' do
|
10
|
-
expect(subject.parent).to be_a(Compo::Parentless)
|
11
|
+
expect(subject.parent).to be_a(Compo::Composites::Parentless)
|
11
12
|
end
|
12
13
|
|
13
14
|
it 'initialises with an ID function returning nil' do
|
@@ -22,10 +23,10 @@ shared_examples 'a branch' do
|
|
22
23
|
end
|
23
24
|
end
|
24
25
|
context 'when the Branch is the child of a root' do
|
25
|
-
let(:parent) { Compo::
|
26
|
+
let(:parent) { Compo::Branches::Hash.new }
|
26
27
|
before(:each) { subject.move_to(parent, :id) }
|
27
28
|
|
28
|
-
it 'returns /ID, where ID is the ID of the
|
29
|
+
it 'returns /ID, where ID is the ID of the Branch' do
|
29
30
|
expect(subject.url).to eq('/id')
|
30
31
|
end
|
31
32
|
end
|
@@ -34,11 +35,13 @@ shared_examples 'a branch' do
|
|
34
35
|
describe '#move_to' do
|
35
36
|
context 'when the Branch has a parent' do
|
36
37
|
context 'when the new parent is nil' do
|
37
|
-
let(:parent) { Compo::
|
38
|
+
let(:parent) { Compo::Branches::Hash.new }
|
38
39
|
before(:each) { subject.move_to(parent, :id) }
|
39
40
|
|
40
41
|
it 'loses its previous parent' do
|
41
|
-
expect(subject.move_to(nil, :id).parent).to be_a(
|
42
|
+
expect(subject.move_to(nil, :id).parent).to be_a(
|
43
|
+
Compo::Composites::Parentless
|
44
|
+
)
|
42
45
|
end
|
43
46
|
|
44
47
|
it 'is no longer a child of its parent' do
|
@@ -49,3 +52,33 @@ shared_examples 'a branch' do
|
|
49
52
|
end
|
50
53
|
end
|
51
54
|
end
|
55
|
+
|
56
|
+
shared_examples 'a branch with children' do
|
57
|
+
it_behaves_like 'a branch'
|
58
|
+
|
59
|
+
describe '#find_url' do
|
60
|
+
it_behaves_like 'a URL finding' do
|
61
|
+
let(:target) { Compo::Branches::Leaf.new }
|
62
|
+
|
63
|
+
before(:each) do
|
64
|
+
a = Compo::Branches::Hash.new
|
65
|
+
b = Compo::Branches::Array.new
|
66
|
+
d = Compo::Branches::Leaf.new
|
67
|
+
e = Compo::Branches::Leaf.new
|
68
|
+
zero = Compo::Branches::Leaf.new
|
69
|
+
|
70
|
+
a.move_to(subject, initial_ids[0])
|
71
|
+
b.move_to(a, 'b')
|
72
|
+
zero.move_to(b, 0)
|
73
|
+
target.move_to(b, 1)
|
74
|
+
d.move_to(subject, initial_ids[1])
|
75
|
+
e.move_to(a, 'e')
|
76
|
+
end
|
77
|
+
|
78
|
+
let(:correct_url) { "#{initial_ids[0]}/b/1" }
|
79
|
+
let(:incorrect_url) { "#{initial_ids[0]}/z/1" }
|
80
|
+
|
81
|
+
let(:proc) { ->(*args, &b) { subject.find_url(*args, &b) } }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|