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