action_tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,6 @@
1
+ README.md
2
+ MANUAL.md
3
+ lib/**/*.rb
4
+ bin/*
5
+ features/**/*.feature
6
+ LICENSE
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format progress
3
+ spec
data/.yardopts ADDED
@@ -0,0 +1,14 @@
1
+
2
+
3
+
4
+ --title "ActionTree"
5
+
6
+ --protected
7
+ --no-private
8
+ --markup markdown
9
+ --markup-provider maruku
10
+
11
+ lib/**/*.rb -
12
+ README.md
13
+ MANUAL.md
14
+ LICENSE
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010-2011 Jostein Berre Eliassen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/MANUAL.md ADDED
@@ -0,0 +1,277 @@
1
+
2
+ ActionTree
3
+ ==========
4
+
5
+ **Manual pages**
6
+
7
+ <pre>
8
+ \/ | |/
9
+ \/ / \||/ /_/___/_
10
+ \/ |/ \/
11
+ _\__\_\ | /_____/_
12
+ \ | / /
13
+ __ _-----` |{,-----------~
14
+ \ }{
15
+ }{{ ACTION TREE:
16
+ }}{
17
+ {{} a dry request router/controller
18
+ , -=-~{ .-^- _
19
+ ejm `}
20
+ {
21
+ </pre>
22
+
23
+ This document covers:
24
+
25
+ 1. Architecture
26
+ 2. Usage
27
+ 1. Defining routes
28
+ 2. Looking up requests
29
+ 3. Running match results
30
+ 3. Dialects
31
+ 4. Advanced usage
32
+ 1. Modularity
33
+ 2. Recursive routes
34
+
35
+
36
+ &nbsp;
37
+
38
+ Architecture
39
+ ------------
40
+
41
+ ActionTree is a [DRY](http://en.wikipedia.org/wiki/Don't_repeat_yourself) request router/controller framweork suitable for web apps. Given a request, such as `/houses/21/parties`, it runs the relevant pieces of code in the right order, with the right variables. It is a bit similar to [Sinatra](http://www.sinatrarb.com/)'s routing, in that it maps URLs to procs, but is much more powerful.
42
+
43
+ The routes are kept as a tree of nodes. Request paths are looked up through the tree in the same way that file paths match a file tree. Unlike file trees, one node can match several, and not just one path fragment. In other words, looking up '/house/32/info' first looks for a child node that matches 'house', then from there a child node matching '32', and from there child node matching 'info'.
44
+
45
+ Each node has:
46
+
47
+ * A match token
48
+ * N child nodes
49
+
50
+ Each node also has
51
+
52
+ * One optional (namespaced) action
53
+ * One optional not_found handler
54
+ * N ordered before filters
55
+ * N ordered after filters
56
+ * N ordered postprocessors
57
+
58
+ All of these are procs. Everything except the action is inherited by all descending nodes.
59
+
60
+ Finally, each node has
61
+
62
+ * A helper scope with n methods, stored as a module.
63
+
64
+ These helpers are also inherited by all descending nodes.
65
+
66
+ ---
67
+
68
+ Since all routes are trees, all routes can easily be reused or mounted in several locations. Since inheritance happens per request, the same route mounted several places can work differently in each context.
69
+
70
+ This enables DRY and concise controllers and routes.
71
+
72
+ Actually, the routes are not even really trees, but graphs, which means you can build infinite, recursive routes, such as `/me/dad/mom/mom/dad/dad/dad/...` -- although that would be a strange thing to do.
73
+
74
+
75
+ ### Paths and locations
76
+
77
+ I will differentiate between request *paths* and route *locations*.
78
+
79
+ **Paths**, like `/houses/21`...
80
+
81
+ * Express route lookups.
82
+ * Are divided into fragments, like `"21"`, `"bobsleigh"` or `"Tirol"`.
83
+
84
+ **Locations**, like `houses/:number`...
85
+
86
+ * Express route definitions.
87
+ * Are divided into tokens, like `:number`, `'about'` or `/[a-z]+/`.
88
+ * Can be
89
+ * plain: `'about'`
90
+ * captures:
91
+ * `:number`,
92
+ * `':embedded-:symbol'`
93
+ * `/regex[p]?/`.
94
+
95
+
96
+ &nbsp;
97
+
98
+ Defining routes
99
+ ---
100
+
101
+ ### DSL Methods
102
+
103
+ Each of the following DSL methods take an optional location argument and a block parameter. The location format is specified later in this document. If omitted, the location will be the same as the current context.
104
+
105
+ **route**(location=nil, &blk)
106
+ : Also known as `with`, `r`, `w`, `_`
107
+ : Evaluates the code in `blk` in context of the specified `location`.
108
+
109
+ **action**(location=nil, layer=nil, &blk)
110
+ : Also known as `a`, `o`
111
+ : Attaches an action. Any number of actions can be attached to every node, and they will run in the same order later. `layer` specifies a layer name, to allow storing different actions for different contexts in the same node.
112
+
113
+ **get**, **put**, **post** and **delete**
114
+ : Shortcuts to layering actions.
115
+ : These methods are shortcuts to the action method above, to simplify layering of http verbs.
116
+
117
+
118
+ **before**(location=nil, &blk)
119
+ : Also known as `b`
120
+ : Attaches another before hook to be run for this action and all descendants.
121
+
122
+ **after**(location=nil, &blk)
123
+ : Attaches another after hook to be run for this action and all descendants.
124
+
125
+ **helpers**(location=nil, &blk)
126
+ : Use def inside the block to write helper methods that will be available to all descendants.
127
+
128
+ **not_found**(location=nil, &blk)
129
+ : Also known as `x`
130
+ : todo
131
+
132
+ **mount**(node)
133
+ : todo
134
+
135
+ **mount**(location, node)
136
+ : todo
137
+
138
+
139
+ ### Location format specification
140
+
141
+ Locations can be expressed in four ways:
142
+
143
+ * A single token, such as `"bobsleigh"`, `:variety` or `/[0-9]+/`
144
+ * A path string, such as `"/book/chapter/:page/:from-:to"`
145
+ * An array of tokens, like `["ideas", :id, "search", /[a-z]+/]`
146
+ * Omitting the path parameter, resulting in a reference to the current scope.
147
+
148
+ There are four different types of tokens:
149
+
150
+ * `"bobsleigh"` matches the exact path segment "bobsleigh".
151
+ * `:variety` matches any path segment and keeps it as @variety.
152
+ * `/[0-9]+/` matches the regexp, in this case numbers.
153
+ * `":year-:month-:date"` matches "anything-like-this", keeping it as `@year`, `@month` and `@date`
154
+
155
+ All these can be used with DSL methods, like this:
156
+
157
+ before('bobsleigh') { polish_ice }
158
+ action(:variety) { "Thank you for choosing #{@variety}" }
159
+ after(/[0-9]+/) { log_number(@match.to_i) }
160
+ helper(':id-:name') { Cow.get(@id) }
161
+ route [/[0-9]+/, /[0-9]+/, /[0-9]+/] do
162
+ "You visited #{match.join('/')}"
163
+ end
164
+
165
+
166
+ ### Utility methods
167
+
168
+ #### descend
169
+
170
+ descend(location) #=> #<ActionTree::Node>
171
+
172
+ house_routes = routes.descend('houses')
173
+
174
+ Returns the node at `location` relative to the current context.
175
+
176
+
177
+
178
+ &nbsp;
179
+
180
+ Looking up requests
181
+ -------------------
182
+
183
+ - matching
184
+ - was it found?
185
+ - on to run
186
+ - match chain
187
+
188
+
189
+
190
+ &nbsp;
191
+
192
+ Running match results
193
+ ---------------------
194
+
195
+ - running
196
+ - parameters: scope and action layer.
197
+ - scope is created
198
+ - precedence: captures overwrite source scope.
199
+ - variable copy
200
+ - captures
201
+ - accumulation
202
+ - regexps accumulate in @match
203
+ - esoteric variables, such as @__match
204
+ - dialect variables, such as @get and @post
205
+ - actions are run
206
+ - order
207
+ each node with before hooks -> run in order of attachment
208
+ actions of current node run in order of attachment
209
+ each node with after filters -> run in order of attachment
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+ &nbsp;
219
+
220
+ Appendix
221
+ --------
222
+
223
+ ### Re-using routes
224
+
225
+ Let's say we want to make a reusable authentication API:
226
+
227
+ simple_auth = ActionTree.new do
228
+ before { authenticate }
229
+ action('login') { ... }
230
+ action('logout') { ... }
231
+ end
232
+
233
+ Now this can be mounted anywhere
234
+
235
+ some_app = ActionTree.new do
236
+ # tada:
237
+ mount simple_auth
238
+
239
+ with('cars') do
240
+ # or within a scope
241
+ mount instant_storefront
242
+ end
243
+
244
+ # or with a path:
245
+ mount 'docs/api', magic_api_doc_generator
246
+ end
247
+
248
+ If you want to pick out a subsection from routes you have already built, you can use the `descend` method:
249
+
250
+ car_routes = some_app.descend('cars')
251
+
252
+ Now we can mount just the car routes in another ActionTree, or even somewhere else within the same one. We can even make endless circular route constructs...
253
+
254
+ family = ActionTree.new do
255
+ before('father') { @person = @person.father }
256
+ before('mother') { @person = @person.mother }
257
+ action('show') { @person.render }
258
+ end
259
+
260
+ family.mount('father', family)
261
+ family.mount('mother', family)
262
+
263
+ me = ActionTree.new do
264
+ before { @person = ME }
265
+ mount family
266
+ end
267
+
268
+ me.match('mother/father/mother/father/father/father').run # => great-great-grand-whatnot
269
+
270
+
271
+
272
+
273
+ ### DSL variants ###
274
+
275
+ ### Mounting and advanced routes ###
276
+
277
+
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ [**CLICK HERE TO READ THE MANUAL**](http://rdoc.info/github/jbe/action_tree/master/file/MANUAL.md)
2
+ *ALPHA VERSION. Documentation may be out of date.*
3
+ <pre>
4
+ \/ | |/
5
+ \/ / \||/ /_/___/_
6
+ \/ |/ \/
7
+ _\__\_\ | /_____/_
8
+ \ | / /
9
+ __ _-----` |{,-----------~
10
+ \ }{
11
+ }{{ ACTION TREE:
12
+ }}{
13
+ {{} a dry request router/controller
14
+ , -=-~{ .-^- _
15
+ ejm `}
16
+ {
17
+ </pre>
18
+
19
+ $ gem install action_tree
20
+
21
+ Quickstart
22
+ ----------
23
+
24
+
25
+ The Local Office of Alligators, division of information, subdivision of web2.0 and social media, have drawn a (naive) map for their new alligator web API:
26
+
27
+
28
+ welcome
29
+ |-- alligators
30
+ | |-- list
31
+ | |-- create
32
+ | `-- (with an id)
33
+ | |-- edit
34
+ | `-- hug
35
+
36
+ A map like this not only shows paths, but also relationships between actions. For instance, `edit` and `hug` retrieve a specific alligator (with an id) before doing anything else. Similarly, every request beneath `/alligators/` should be counted, to see if we're on Digg yet.
37
+
38
+ So let's put those two in `before` hooks in an ActionTree:
39
+
40
+ routes = ActionTree.new do
41
+ action { "Welcome to the alligator manager #{@name}!" }
42
+
43
+ with 'alligators' do
44
+ before { @count = (@count || 0) + 1 }
45
+
46
+ action { Alligator.all }
47
+ action('create') { Alligator.create }
48
+
49
+ with :id do
50
+ before { @alligator = Alligator.get(@id) }
51
+ action { 'this is ' + @alligator }
52
+ action('edit') { "editing #{@alligator}" }
53
+ action('hug') { "#{@alligator} is pleased." }
54
+ end
55
+ end
56
+ end
57
+
58
+ We can then match and run actions:
59
+
60
+ routes.match('/') #=> #<ActionTree::Match >
61
+ routes.match('/').found? #=> true
62
+
63
+ @name = 'Zap'
64
+ routes.match('/').run(self) # => "Welcome to the alligator manager, Zap."
65
+
66
+ routes.match('/alligators/4').run # => "this is #<Alligator 4>"
67
+ router.match('/alligators/8/hug').run # => "<#Alligator 8> is pleased."
68
+
69
+ routes.match('nonexistant/route') # => #<ActionTree::NotFound>
70
+ routes.match('nonexistant/route').found? #=> false
71
+
72
+
73
+ &nbsp;
74
+
75
+
76
+ Available dialects
77
+ ------------------
78
+
79
+ [**at-rack**](https://github.com/jbe/at-rack) turns ActionTree into a Sinatra-like framework, and allows you to mount trees as rack apps.
80
+
81
+ **at-websocket** is planned to make writing websocket servers easier.
82
+
83
+ &nbsp;
84
+
85
+ ---
86
+
87
+ Copyright (c) 2010-2011 Jostein Berre Eliassen. See [LICENSE](http://rdoc.info/github/jbe/action_tree/master/file/LICENSE) for details.
88
+
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "action_tree"
8
+ gem.summary = %Q{Versatile routing dry enough to kill cacti.}
9
+ gem.description = %Q{ActionTree is a router. It provides a compact DSL for defining routes that map paths to actions. It was designed as a router for modern ruby mini-frameworks.}
10
+ gem.email = "post@jostein.be"
11
+ gem.homepage = "http://github.com/jbe/action_tree"
12
+ gem.authors = ["jbe"]
13
+
14
+ #gem.required_ruby_version = '>= 1.8.7'
15
+ gem.add_dependency 'backports'
16
+
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'rake/testtask'
25
+ Rake::TestTask.new(:test) do |test|
26
+ test.libs << 'lib' << 'test'
27
+ test.pattern = 'test/**/test_*.rb'
28
+ test.verbose = true
29
+ end
30
+
31
+ require 'rspec/core/rake_task'
32
+ RSpec::Core::RakeTask.new(:spec)
33
+
34
+
35
+ task :spec => :check_dependencies
36
+ task :default => :spec
37
+
38
+
39
+
40
+ #namespace :test do
41
+ # desc 'open testing shell'
42
+ # task :shell => [:build, :install] do
43
+ # sh 'irb -r ./test/shell.rb'
44
+ # end
45
+ #end