opal-actionpack 0.0.2
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 +7 -0
- data/.gitignore +5 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +51 -0
- data/LICENSE +22 -0
- data/LICENSE.txt +22 -0
- data/README.md +46 -0
- data/Rakefile +7 -0
- data/lib/opal-actionpack.rb +1 -0
- data/lib/opal/actionpack.rb +5 -0
- data/lib/opal/actionpack/version.rb +5 -0
- data/opal-actionpack.gemspec +32 -0
- data/opal/action_pack.rb +1 -0
- data/opal/action_pack/core.rb +475 -0
- data/opal/opal-actionpack.rb +1 -0
- data/spec/actionpack_spec.rb +301 -0
- data/spec/spec_helper.rb +17 -0
- metadata +117 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b6402fa68e9baa7931c57ca82bb15c5555200ba8
|
4
|
+
data.tar.gz: 7394860f43ee4dcb9f4d8ff71f6776bd8be8c842
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b34dbefd90dda22abe217bd966b1af968c84f6f4f96e75aa5d2e5a56e704c20fdf1a1e237eceeff9398bd2aaf4a358287d980c735bc87d48923f7a05fa54f194
|
7
|
+
data.tar.gz: ec6b3b88b7e6c48aca0b48a01707103dc66617780ce3ceb35f57d9d97465d19d63262ce3ceb1f03407e94dae4db36991996b75b40e0e4950963b5a3fff3a3d5e
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git://github.com/opal/opal.git
|
3
|
+
revision: 139b534a8e14719f1406318d432b305ba76db4b1
|
4
|
+
specs:
|
5
|
+
opal (0.6.0)
|
6
|
+
source_map
|
7
|
+
sprockets
|
8
|
+
|
9
|
+
PATH
|
10
|
+
remote: .
|
11
|
+
specs:
|
12
|
+
opal-actionpack (0.0.1)
|
13
|
+
opal (>= 0.5.0, < 1.0.0)
|
14
|
+
|
15
|
+
GEM
|
16
|
+
remote: https://rubygems.org/
|
17
|
+
specs:
|
18
|
+
diff-lcs (1.2.5)
|
19
|
+
hike (1.2.3)
|
20
|
+
json (1.8.1)
|
21
|
+
multi_json (1.8.4)
|
22
|
+
opal-rspec (0.3.0.beta2)
|
23
|
+
opal (>= 0.6.0, < 1.0.0)
|
24
|
+
rack (1.5.2)
|
25
|
+
rake (10.1.0)
|
26
|
+
rspec (2.14.1)
|
27
|
+
rspec-core (~> 2.14.0)
|
28
|
+
rspec-expectations (~> 2.14.0)
|
29
|
+
rspec-mocks (~> 2.14.0)
|
30
|
+
rspec-core (2.14.7)
|
31
|
+
rspec-expectations (2.14.4)
|
32
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
33
|
+
rspec-mocks (2.14.4)
|
34
|
+
source_map (3.0.1)
|
35
|
+
json
|
36
|
+
sprockets (2.10.1)
|
37
|
+
hike (~> 1.2)
|
38
|
+
multi_json (~> 1.0)
|
39
|
+
rack (~> 1.0)
|
40
|
+
tilt (~> 1.1, != 1.3.0)
|
41
|
+
tilt (1.4.1)
|
42
|
+
|
43
|
+
PLATFORMS
|
44
|
+
ruby
|
45
|
+
|
46
|
+
DEPENDENCIES
|
47
|
+
opal!
|
48
|
+
opal-actionpack!
|
49
|
+
opal-rspec (= 0.3.0.beta2)
|
50
|
+
rake
|
51
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) <year> <copyright holders>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
22
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Elia Schito
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Opal: ActiveRecord
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
gem 'opal-actionpack'
|
8
|
+
|
9
|
+
And then execute:
|
10
|
+
|
11
|
+
$ bundle
|
12
|
+
|
13
|
+
Or install it yourself as:
|
14
|
+
|
15
|
+
$ gem install opal-actionpack
|
16
|
+
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
Inside your `application.js.rb`:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
require 'action_pack' # to require the whole action pack lib
|
24
|
+
```
|
25
|
+
|
26
|
+
## Testing
|
27
|
+
|
28
|
+
There are two ways to run tests. You can run them inside of MRI
|
29
|
+
for ease of testing and better debuggability or you can run them
|
30
|
+
using Opal (as this is how it will actually be used).
|
31
|
+
|
32
|
+
* To run in Opal do - rake
|
33
|
+
* To run in MRI do - rspec spec
|
34
|
+
|
35
|
+
|
36
|
+
## Contributing
|
37
|
+
|
38
|
+
1. Fork it
|
39
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
40
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
41
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
42
|
+
5. Create new Pull Request
|
43
|
+
|
44
|
+
## License
|
45
|
+
|
46
|
+
opal-activerecord is Copyright © 2014 Steve Tuckner. It is free software, and may be redistributed under the terms specified in the LICENSE file (an MIT License).
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'opal/actionpack'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'opal/actionpack/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'opal-actionpack'
|
8
|
+
gem.version = Opal::Actionpack::VERSION
|
9
|
+
gem.authors = ['Steve Tuckner']
|
10
|
+
gem.email = ['stevetuckner@stewdle.com']
|
11
|
+
gem.licenses = ['MIT']
|
12
|
+
gem.description = %q{A small port of the glorious ActionPack for Opal}
|
13
|
+
gem.summary = %q{
|
14
|
+
This implements a subset of the rails/actionpack gem.
|
15
|
+
Currently it does some of the routing functionality,
|
16
|
+
and some rendering functionality. It doesn't do
|
17
|
+
forms yet.
|
18
|
+
}
|
19
|
+
gem.homepage = 'https://github.com/boberetezeke/opal-actionpack'
|
20
|
+
gem.rdoc_options << '--main' << 'README' <<
|
21
|
+
'--line-numbers' <<
|
22
|
+
'--include' << 'opal'
|
23
|
+
|
24
|
+
gem.files = `git ls-files`.split($/)
|
25
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
26
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
27
|
+
gem.require_paths = ['lib']
|
28
|
+
|
29
|
+
gem.add_dependency 'opal', ['>= 0.5.0', '< 1.0.0']
|
30
|
+
gem.add_development_dependency 'rake'
|
31
|
+
gem.add_development_dependency 'opal-rspec'
|
32
|
+
end
|
data/opal/action_pack.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'action_pack/core'
|
@@ -0,0 +1,475 @@
|
|
1
|
+
class String
|
2
|
+
def capitalize
|
3
|
+
self[0..0].upcase + self[1..-1]
|
4
|
+
end
|
5
|
+
|
6
|
+
def singularize
|
7
|
+
if m = /^(.*)s$/.match(self)
|
8
|
+
return m[1]
|
9
|
+
end
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def pluralize
|
14
|
+
self + "s"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module PathHandler
|
19
|
+
def resolve_path(root, *args)
|
20
|
+
@application.resolve_path(root, *args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(sym, *args, &block)
|
24
|
+
sym_to_s = sym.to_s
|
25
|
+
m = /^(.*)_path$/.match(sym_to_s)
|
26
|
+
if m
|
27
|
+
return @application.resolve_path(m[1], *args)
|
28
|
+
end
|
29
|
+
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ActionController
|
35
|
+
class Base
|
36
|
+
include PathHandler
|
37
|
+
|
38
|
+
attr_reader :params
|
39
|
+
|
40
|
+
def initialize(params)
|
41
|
+
@application = Application.instance
|
42
|
+
@params = params
|
43
|
+
end
|
44
|
+
|
45
|
+
def render_template
|
46
|
+
ActionView.new.render(self, file: render_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def invoke_action(action)
|
50
|
+
# set up default render path
|
51
|
+
@render_path = "views" + "/" + view_path + "/" + action.name
|
52
|
+
self.send(action.name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def view_path
|
56
|
+
controller_parts = self.class.to_s.split(/::/)
|
57
|
+
controller_parts = controller_parts[0..-2].map{|part| part.downcase} + [controller_root_name(controller_parts[-1])]
|
58
|
+
controller_parts.join("/")
|
59
|
+
end
|
60
|
+
|
61
|
+
def controller_root_name(controller_name)
|
62
|
+
/^(.*)Controller$/.match(controller_name)[1].downcase
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def render_path
|
68
|
+
@render_path
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class ActionView
|
74
|
+
include PathHandler
|
75
|
+
attr_reader :absolute_path
|
76
|
+
|
77
|
+
INITIALIZE_DEFAULTS={locals: {}, path: ""}
|
78
|
+
def initialize(options={})
|
79
|
+
options = INITIALIZE_DEFAULTS.merge(options)
|
80
|
+
@path = options[:path]
|
81
|
+
@application = Application.instance
|
82
|
+
# Opal / RMI diff ("".split(/\//) == [""] vs []
|
83
|
+
if @path == ""
|
84
|
+
@path_parts = []
|
85
|
+
else
|
86
|
+
@path_parts = @path.split(/\//)
|
87
|
+
end
|
88
|
+
@locals = options[:locals]
|
89
|
+
@absolute_path = ""
|
90
|
+
end
|
91
|
+
|
92
|
+
def render(controller, options={}, locals={}, &block)
|
93
|
+
if options[:file]
|
94
|
+
render_path = options[:file]
|
95
|
+
@absolute_path = render_path
|
96
|
+
elsif options[:partial]
|
97
|
+
partial_parts = options[:partial].split(/\//)
|
98
|
+
render_path = (@path_parts + partial_parts[0..-2] + ["_" + partial_parts[-1]]).join("/")
|
99
|
+
elsif options[:text]
|
100
|
+
return options[:text]
|
101
|
+
end
|
102
|
+
@locals = locals
|
103
|
+
copy_instance_variables_from(controller)
|
104
|
+
Template[render_path].render(self)
|
105
|
+
end
|
106
|
+
|
107
|
+
def copy_instance_variables_from(object)
|
108
|
+
object.instance_variables.each do |ivar|
|
109
|
+
self.instance_variable_set(ivar, object.instance_variable_get(ivar))
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def link_to(text, path, options={})
|
114
|
+
"<a href=\"#{path}\"" + options.map{|k,v| "#{k}=\"#{v}\""}.join(' ') + ">#{text}</a>"
|
115
|
+
end
|
116
|
+
|
117
|
+
def method_missing(sym, *args, &block)
|
118
|
+
sym_to_s = sym.to_s
|
119
|
+
if @locals.has_key?(sym_to_s)
|
120
|
+
return @locals[sym_to_s]
|
121
|
+
elsif @locals.has_key?(sym)
|
122
|
+
return @locals[sym]
|
123
|
+
end
|
124
|
+
|
125
|
+
super
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Application
|
130
|
+
class Router
|
131
|
+
def initialize
|
132
|
+
@routes = []
|
133
|
+
end
|
134
|
+
|
135
|
+
def draw
|
136
|
+
yield self
|
137
|
+
end
|
138
|
+
|
139
|
+
def resources(route, options={})
|
140
|
+
@routes.push(Route.new(route, options))
|
141
|
+
end
|
142
|
+
|
143
|
+
def match_url(url)
|
144
|
+
parts, params = to_parts(url)
|
145
|
+
|
146
|
+
@routes.each do |route|
|
147
|
+
#puts "matching parts=#{parts.inspect}, route=#{route.inspect}"
|
148
|
+
if action = route.match(parts, params)
|
149
|
+
return [action, params]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
raise "no route matches #{url}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def match_path(resource_name, action_name, *args)
|
157
|
+
@routes.each do |route|
|
158
|
+
action = route.match_path(resource_name, action_name, *args)
|
159
|
+
return action if action
|
160
|
+
end
|
161
|
+
|
162
|
+
raise "no route matches path #{resource_name}::#{action_name}"
|
163
|
+
end
|
164
|
+
|
165
|
+
def to_parts(url)
|
166
|
+
# remove leading '/'
|
167
|
+
if m = /^\/(.*)$/.match(url)
|
168
|
+
url = m[1]
|
169
|
+
end
|
170
|
+
|
171
|
+
# separate url on ?
|
172
|
+
if m = /^([^?]*)\?(.*)$/.match(url)
|
173
|
+
url = m[1]
|
174
|
+
keys_and_values = m[2].split(/&/)
|
175
|
+
params = {}
|
176
|
+
keys_and_values.each do |key_and_value|
|
177
|
+
key, value = key_and_value.split(/=/)
|
178
|
+
params[key] = value
|
179
|
+
end
|
180
|
+
else
|
181
|
+
params = {}
|
182
|
+
end
|
183
|
+
|
184
|
+
[url.split(/\//), params]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Action
|
189
|
+
attr_reader :name
|
190
|
+
def initialize(route, action_type, name, parts)
|
191
|
+
@route = route
|
192
|
+
@action_type = action_type
|
193
|
+
@name = name
|
194
|
+
@parts = parts
|
195
|
+
end
|
196
|
+
|
197
|
+
def match(parts, params)
|
198
|
+
#puts "Action:match: parts: #{parts.inspect}, name: #{@name}, action_parts: #{@parts.inspect}"
|
199
|
+
return false if parts.size != @parts.size
|
200
|
+
return true if parts.size == 0
|
201
|
+
|
202
|
+
@parts.each_with_index do |part, index|
|
203
|
+
if part.class == String
|
204
|
+
return true if /#{part}/.match(parts[index])
|
205
|
+
else
|
206
|
+
param_key = part.keys.first
|
207
|
+
matcher = /(#{part.values.first})/
|
208
|
+
if m = matcher.match(parts[index])
|
209
|
+
params[param_key] = m[1]
|
210
|
+
return true
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
return false
|
215
|
+
end
|
216
|
+
|
217
|
+
def match_path(action_name, *args)
|
218
|
+
#puts "action_name = #{action_name}, name = #{@name}, args = #{args.inspect}"
|
219
|
+
params = {}
|
220
|
+
return [false, params] unless @name.to_s == action_name.to_s
|
221
|
+
|
222
|
+
if args.size > 0
|
223
|
+
if args.last.is_a?(Hash)
|
224
|
+
params = args.pop
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
if @action_type == :member
|
229
|
+
if args.size == 1
|
230
|
+
object = args.first
|
231
|
+
if object.class == String
|
232
|
+
object_id = object
|
233
|
+
else
|
234
|
+
object_id = object.id
|
235
|
+
end
|
236
|
+
#puts "object_id = #{object_id}, name = #{@name}"
|
237
|
+
#
|
238
|
+
if @name.to_s == 'show'
|
239
|
+
action_root = object_id.to_s
|
240
|
+
else
|
241
|
+
action_root = "#{object_id}/#{@name}"
|
242
|
+
end
|
243
|
+
return [action_root, params]
|
244
|
+
else
|
245
|
+
raise "requires one argument passed to member path"
|
246
|
+
end
|
247
|
+
else
|
248
|
+
if args.size == 0
|
249
|
+
#puts "Action#match_path: @name = #{@name}"
|
250
|
+
if @name.to_s == 'index'
|
251
|
+
# FIXME: need to url encode parameters
|
252
|
+
action_root = ""
|
253
|
+
else
|
254
|
+
action_root = @name
|
255
|
+
end
|
256
|
+
return [action_root, params]
|
257
|
+
else
|
258
|
+
raise "argument passed to collection path"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def invoke_controller(action, params, options)
|
264
|
+
if options[:render_view]
|
265
|
+
controller_class_name = "#{@route.name.capitalize}Controller"
|
266
|
+
controller_class = Object.const_get(controller_class_name)
|
267
|
+
controller = controller_class.new(params)
|
268
|
+
controller.invoke_action(action)
|
269
|
+
html = controller.render_template
|
270
|
+
Document.find(options[:selector]).html = html
|
271
|
+
end
|
272
|
+
|
273
|
+
controller_client_class_name = "#{@route.name.capitalize}ClientController"
|
274
|
+
begin
|
275
|
+
controller_client_class = Object.const_get(controller_client_class_name)
|
276
|
+
begin
|
277
|
+
controller_action_class = controller_client_class.const_get(action.name.capitalize)
|
278
|
+
controller_action = controller_action_class.new(params)
|
279
|
+
controller_action.add_bindings
|
280
|
+
rescue
|
281
|
+
puts "client class: #{controller_client_class_name}::#{action.name.capitalize} doesn't exist"
|
282
|
+
end
|
283
|
+
rescue
|
284
|
+
puts "client class: #{controller_client_class_name} doesn't exist"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
class Route
|
290
|
+
ALL_COLLECTION_ACTIONS = [:index]
|
291
|
+
ALL_RESOURCE_ACTIONS = [:show, :new, :create, :edit, :update, :destroy]
|
292
|
+
|
293
|
+
attr_reader :name
|
294
|
+
|
295
|
+
def initialize(name, options)
|
296
|
+
@name = name.to_s
|
297
|
+
@actions = [
|
298
|
+
Action.new(self, :member, :show, [{id: '.*'}]),
|
299
|
+
Action.new(self, :collection, :new, ['new']),
|
300
|
+
Action.new(self, :member, :edit, [{id: '.*'}, 'edit']),
|
301
|
+
Action.new(self, :collection, :index, [])
|
302
|
+
]
|
303
|
+
end
|
304
|
+
|
305
|
+
def match(parts, params)
|
306
|
+
#puts "Route:match: parts: #{parts}, name: #{@name}"
|
307
|
+
return nil unless @name == parts[0]
|
308
|
+
@actions.each do |action|
|
309
|
+
return action if action.match(parts[1..-1], params)
|
310
|
+
end
|
311
|
+
return nil
|
312
|
+
end
|
313
|
+
|
314
|
+
def match_path(resource_name, action_name, *args)
|
315
|
+
#puts "Route#match_path, name = #{@name}, resource_name = #{resource_name}"
|
316
|
+
return nil unless @name.to_s == resource_name.to_s
|
317
|
+
@actions.each do |action|
|
318
|
+
action_path, params = action.match_path(action_name, *args)
|
319
|
+
# FIXME: need to url encode parameters
|
320
|
+
params_string = params.map{|key, value| "#{key}=#{value}"}.join("&")
|
321
|
+
|
322
|
+
#puts "action_path = #{action_path}"
|
323
|
+
if action_path
|
324
|
+
if action_path == ""
|
325
|
+
url = "/#{resource_name}"
|
326
|
+
else
|
327
|
+
url = "/#{resource_name}/#{action_path}"
|
328
|
+
end
|
329
|
+
|
330
|
+
return params_string.empty? ? url : "#{url}?#{params_string}"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
return nil
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def self.routes
|
338
|
+
@routes ||= Router.new
|
339
|
+
end
|
340
|
+
|
341
|
+
def self.instance
|
342
|
+
return @@application if defined?(@@application)
|
343
|
+
@@application = new
|
344
|
+
end
|
345
|
+
|
346
|
+
def initialize
|
347
|
+
@memory_store = ActiveRecord::MemoryStore.new
|
348
|
+
ActiveRecord::Base.connection = @memory_store
|
349
|
+
end
|
350
|
+
|
351
|
+
def launch(initial_url, initial_objects)
|
352
|
+
begin
|
353
|
+
@objects = [initial_objects]
|
354
|
+
@objects.each { |object| object.save }
|
355
|
+
puts "memory_store = #{@memory_store.inspect}"
|
356
|
+
go_to_route(initial_url, render_view: false)
|
357
|
+
rescue Exception => e
|
358
|
+
puts "Exception: #{e}"
|
359
|
+
e.backtrace[0..10].each do |trace|
|
360
|
+
puts trace
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def resolve_path(path, *args)
|
366
|
+
# FIXME: can't detect plural by checking for trailing 's'
|
367
|
+
m = /^((\w+)_)?((\w+?)(s)?)$/.match(path)
|
368
|
+
if m
|
369
|
+
action_with_underscore = m[1]
|
370
|
+
action = m[2]
|
371
|
+
resource = m[3]
|
372
|
+
resource_root = m[4]
|
373
|
+
is_plural = m[5]
|
374
|
+
|
375
|
+
#puts "matched pattern: #{m}, action = #{action.inspect}, resource = #{resource}, is_plural=#{is_plural.inspect}"
|
376
|
+
if action_with_underscore
|
377
|
+
#puts "multi part path, action = #{action}"
|
378
|
+
if !is_plural
|
379
|
+
resource = resource.pluralize
|
380
|
+
end
|
381
|
+
else
|
382
|
+
if is_plural
|
383
|
+
#puts "single part path, plural"
|
384
|
+
action = 'index'
|
385
|
+
else
|
386
|
+
#puts "single part path, singular"
|
387
|
+
resource = resource.pluralize
|
388
|
+
action = 'show'
|
389
|
+
end
|
390
|
+
end
|
391
|
+
else
|
392
|
+
raise "unable to match path: #{path}_path"
|
393
|
+
end
|
394
|
+
|
395
|
+
self.class.routes.match_path(resource, action, *args)
|
396
|
+
end
|
397
|
+
|
398
|
+
def go_to_route(url, options={})
|
399
|
+
@current_route_action, @params = self.class.routes.match_url(url)
|
400
|
+
@current_route_action.invoke_controller(@current_route_action, @params, options)
|
401
|
+
end
|
402
|
+
|
403
|
+
ROUTE_MAP = {
|
404
|
+
"Calculator" => {route: 'calculators', key: 'calculator'},
|
405
|
+
"Results" => {route: 'results', key: 'result'}
|
406
|
+
}
|
407
|
+
|
408
|
+
def connect
|
409
|
+
@memory_store.on_change do |change_type, object|
|
410
|
+
route = "/" + ROUTE_MAP[object.class.to_s][:route]
|
411
|
+
key = ROUTE_MAP[object.class.to_s][:key]
|
412
|
+
case change_type
|
413
|
+
when :insert
|
414
|
+
puts "INSERT: #{object}"
|
415
|
+
HTTP.post route, {:payload => {:key => object.to_json}}
|
416
|
+
when :delete
|
417
|
+
puts "DELETE: #{object}"
|
418
|
+
when :update
|
419
|
+
puts "UPDATE: #{object}"
|
420
|
+
HTTP.put "#{route}/#{object.id}", object.attributes do |response|
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
=begin
|
428
|
+
class Node
|
429
|
+
attr_reader :children
|
430
|
+
def initialize(name=nil)
|
431
|
+
@name = name
|
432
|
+
@children = []
|
433
|
+
end
|
434
|
+
|
435
|
+
def node(name, &block)
|
436
|
+
child = Node.new(name)
|
437
|
+
@children.push(child)
|
438
|
+
child.instance_exec(&block) if block
|
439
|
+
end
|
440
|
+
|
441
|
+
def instance_exec(*args, &block)
|
442
|
+
method_name = nil
|
443
|
+
n = 0
|
444
|
+
n += 1 while respond_to?(method_name = "__instance_exec#{n}")
|
445
|
+
self.class.instance_eval { define_method(method_name, &block) }
|
446
|
+
|
447
|
+
begin
|
448
|
+
send(method_name, *args)
|
449
|
+
ensure
|
450
|
+
self.class.instance_eval { remove_method(method_name) } rescue nil
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def to_s
|
455
|
+
|
456
|
+
@name + "[" + @children.map{|child| child.to_s}.join(", ") + "]"
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
def tree(name, &block)
|
461
|
+
@tree = Node.new(name)
|
462
|
+
@tree.instance_exec(&block)
|
463
|
+
@tree
|
464
|
+
end
|
465
|
+
|
466
|
+
tree("Simpsons family tree") do
|
467
|
+
node("gramps") do
|
468
|
+
node("homer+marge") do
|
469
|
+
node("bart")
|
470
|
+
node("lisa")
|
471
|
+
node("maggie")
|
472
|
+
end
|
473
|
+
end
|
474
|
+
end
|
475
|
+
=end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'action_pack'
|
@@ -0,0 +1,301 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# mock up ActiveRecord
|
4
|
+
class ActiveRecord
|
5
|
+
class Base
|
6
|
+
def self.connection=(connection)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class MemoryStore
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class CalculatorsController < ActionController::Base
|
15
|
+
def initialize(params={})
|
16
|
+
super
|
17
|
+
@ivar = 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Nested
|
22
|
+
class ResultsController < ActionController::Base
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class CalculatorsClientController
|
27
|
+
class Show < ActionController::Base
|
28
|
+
def initialize(params)
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_bindings
|
33
|
+
end
|
34
|
+
|
35
|
+
def invoke_callback
|
36
|
+
calculators_path(id:1)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
if RUBY_ENGINE != "opal"
|
43
|
+
class Template
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe Application do
|
48
|
+
let(:object1) { double('object') }
|
49
|
+
|
50
|
+
describe "#launch" do
|
51
|
+
before do
|
52
|
+
Application.routes.draw do |router|
|
53
|
+
router.resources :calculators
|
54
|
+
router.resources :results
|
55
|
+
end
|
56
|
+
allow(object1).to receive(:save).and_return(nil)
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when launching the application with a show action" do
|
60
|
+
it "should be true" do
|
61
|
+
=begin
|
62
|
+
show_controller = double('calculator_show_controller')
|
63
|
+
expect(CalculatorsController).to receive(:new).with({id: '1'}).and_return(show_controller)
|
64
|
+
expect(show_controller).to receive(:add_bindings)
|
65
|
+
application = Application.instance.launch("/calculators/1", object1)
|
66
|
+
=end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe Application::Action do
|
72
|
+
describe "#match_path" do
|
73
|
+
let(:route) { double('route') }
|
74
|
+
|
75
|
+
it "matches if a show path with 1 arg" do
|
76
|
+
action = Application::Action.new(route, :member, 'show', {id: '.*'})
|
77
|
+
expect(action.match_path(:show, '1')).to eq(["1", {}])
|
78
|
+
end
|
79
|
+
|
80
|
+
it "matches if a show path with 1 arg and params" do
|
81
|
+
action = Application::Action.new(route, :member, 'show', {id: '.*'})
|
82
|
+
expect(action.match_path(:show, '1', extra: 1)).to eq(["1", {extra: 1}])
|
83
|
+
end
|
84
|
+
|
85
|
+
it "matches if a non-show member path with 1 arg" do
|
86
|
+
action = Application::Action.new(route, :member, 'edit', {id: '.*'})
|
87
|
+
expect(action.match_path(:edit, '1')).to eq(["1/edit", {}])
|
88
|
+
end
|
89
|
+
|
90
|
+
it "matches if a index path with 0 args" do
|
91
|
+
action = Application::Action.new(route, :collection, 'index', {id: '.*'})
|
92
|
+
expect(action.match_path(:index)).to eq(["", {}])
|
93
|
+
end
|
94
|
+
|
95
|
+
it "matches if a index path with params as args" do
|
96
|
+
action = Application::Action.new(route, :collection, 'index', {id: '.*'})
|
97
|
+
expect(action.match_path(:index, extra: 1)).to eq(["", {extra: 1}])
|
98
|
+
end
|
99
|
+
|
100
|
+
it "matches if a non-index collection path with 0 args" do
|
101
|
+
action = Application::Action.new(route, :collection, 'new', {id: '.*'})
|
102
|
+
expect(action.match_path(:new)).to eq(["new", {}])
|
103
|
+
end
|
104
|
+
|
105
|
+
#it "raises an exception with a member path without 1 arg"
|
106
|
+
#it "raises an exception with a collection path without 0 args"
|
107
|
+
end
|
108
|
+
|
109
|
+
#describe "#match"
|
110
|
+
end
|
111
|
+
|
112
|
+
describe Application::Route do
|
113
|
+
describe "#match_path" do
|
114
|
+
it "matches a show path" do
|
115
|
+
route = Application::Route.new("calculators", {})
|
116
|
+
expect(route.match_path("calculators", "show", "1")).to eq("/calculators/1")
|
117
|
+
end
|
118
|
+
|
119
|
+
it "matches a non-show member path" do
|
120
|
+
route = Application::Route.new("calculators", {})
|
121
|
+
expect(route.match_path("calculators", "edit", "1")).to eq("/calculators/1/edit")
|
122
|
+
end
|
123
|
+
|
124
|
+
it "matches an index path" do
|
125
|
+
route = Application::Route.new("calculators", {})
|
126
|
+
expect(route.match_path("calculators", "index")).to eq("/calculators")
|
127
|
+
end
|
128
|
+
|
129
|
+
it "matches an non-index collection path" do
|
130
|
+
route = Application::Route.new("calculators", {})
|
131
|
+
expect(route.match_path("calculators", "new")).to eq("/calculators/new")
|
132
|
+
end
|
133
|
+
|
134
|
+
#describe "#match"
|
135
|
+
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe Application::Router do
|
140
|
+
before do
|
141
|
+
Application.routes.draw do |router|
|
142
|
+
router.resources :calculators
|
143
|
+
router.resources :results
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "#match_path" do
|
148
|
+
it "matches a show path" do
|
149
|
+
expect(Application.routes.match_path("calculators", "show", "1")).to eq("/calculators/1")
|
150
|
+
end
|
151
|
+
|
152
|
+
it "matches a non-show member path" do
|
153
|
+
expect(Application.routes.match_path("results", "edit", "1")).to eq("/results/1/edit")
|
154
|
+
end
|
155
|
+
|
156
|
+
it "matches an index path" do
|
157
|
+
expect(Application.routes.match_path("results", "index")).to eq("/results")
|
158
|
+
end
|
159
|
+
|
160
|
+
it "matches an non-index collection path" do
|
161
|
+
expect(Application.routes.match_path("calculators", "new")).to eq("/calculators/new")
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#describe "#draw"
|
166
|
+
#describe "#resources"
|
167
|
+
#describe "#match_url"
|
168
|
+
#describe "#to_parts"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
describe ActionView do
|
173
|
+
before do
|
174
|
+
Application.routes.draw do |router|
|
175
|
+
router.resources :calculators
|
176
|
+
router.resources :results
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe "#render" do
|
181
|
+
let(:template) { double('template') }
|
182
|
+
|
183
|
+
before do
|
184
|
+
@controller = CalculatorsController.new
|
185
|
+
end
|
186
|
+
|
187
|
+
it "renders from files" do
|
188
|
+
action_view = ActionView.new
|
189
|
+
expect(template).to receive(:render).with(action_view)
|
190
|
+
expect(Template).to receive(:[]).with("a/b/c").and_return(template)
|
191
|
+
action_view.render(@controller, file: "a/b/c")
|
192
|
+
|
193
|
+
expect(action_view.instance_variable_get(:@ivar)).to eq(1)
|
194
|
+
end
|
195
|
+
|
196
|
+
context "when doing a partial" do
|
197
|
+
it "renders from single-directory path partials" do
|
198
|
+
action_view = ActionView.new(path: "a/b")
|
199
|
+
expect(template).to receive(:render).with(action_view)
|
200
|
+
expect(Template).to receive(:[]).with("a/b/_c").and_return(template)
|
201
|
+
action_view.render(@controller, partial: "c")
|
202
|
+
end
|
203
|
+
|
204
|
+
it "renders from multi-directory path partials" do
|
205
|
+
action_view = ActionView.new
|
206
|
+
expect(template).to receive(:render).with(action_view)
|
207
|
+
expect(Template).to receive(:[]).with("a/b/_c").and_return(template)
|
208
|
+
action_view.render(@controller, partial: "a/b/c")
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it "renders from text" do
|
213
|
+
action_view = ActionView.new
|
214
|
+
expect(action_view.render(@controller, text: "hello")).to eq("hello")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe "#link_to" do
|
219
|
+
# it should generate an anchor link with and without options
|
220
|
+
end
|
221
|
+
|
222
|
+
describe "#resolve_path" do
|
223
|
+
it "should match a show path" do
|
224
|
+
action_view = ActionView.new
|
225
|
+
expect(action_view.resolve_path('calculator', "1")).to eq("/calculators/1")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "#method_missing" do
|
230
|
+
it "should handle show _path method" do
|
231
|
+
action_view = ActionView.new
|
232
|
+
expect(action_view.calculator_path("1")).to eq("/calculators/1")
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should handle show _path method with params" do
|
236
|
+
action_view = ActionView.new
|
237
|
+
expect(action_view.calculator_path("1", extra: 1)).to eq("/calculators/1?extra=1")
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should handle a non-show member _path method" do
|
241
|
+
action_view = ActionView.new
|
242
|
+
expect(action_view.edit_calculator_path("1")).to eq("/calculators/1/edit")
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should handle a index _path method" do
|
246
|
+
action_view = ActionView.new
|
247
|
+
expect(action_view.calculators_path).to eq("/calculators")
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should handle a index _path method with params" do
|
251
|
+
action_view = ActionView.new
|
252
|
+
expect(action_view.calculators_path(extra: 1)).to eq("/calculators?extra=1")
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should handle a non-index collection _path method" do
|
256
|
+
action_view = ActionView.new
|
257
|
+
expect(action_view.new_calculators_path).to eq("/calculators/new")
|
258
|
+
end
|
259
|
+
|
260
|
+
it "should handle references to locals" do
|
261
|
+
action_view = ActionView.new(locals: { test_var: 1})
|
262
|
+
expect(action_view.test_var).to eq(1)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
describe ActionController::Base do
|
268
|
+
before do
|
269
|
+
Application.routes.draw do |router|
|
270
|
+
router.resources :calculators
|
271
|
+
router.resources :results
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe "#action_method" do
|
276
|
+
it "should resolve_paths in the application" do
|
277
|
+
expect(Application.instance).to receive(:resolve_path)
|
278
|
+
calculators_controller = CalculatorsClientController::Show.new({})
|
279
|
+
calculators_controller.invoke_callback
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "#view_path" do
|
284
|
+
it "should return the path to the view on a sinple controller" do
|
285
|
+
controller = CalculatorsController.new({})
|
286
|
+
expect(controller.view_path).to eq("calculators")
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should return the path to the view on a sinple controller" do
|
290
|
+
controller = Nested::ResultsController.new({})
|
291
|
+
expect(controller.view_path).to eq("nested/results")
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
describe "#controller_root_name" do
|
296
|
+
it "should return a downcased name fo the controller class's root name" do
|
297
|
+
controller = CalculatorsController.new({})
|
298
|
+
expect(controller.controller_root_name("CalculatorsController")).to eq("calculators")
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
if RUBY_ENGINE == "opal"
|
2
|
+
require 'opal-rspec'
|
3
|
+
require 'opal-actionpack'
|
4
|
+
else
|
5
|
+
require_relative '../opal/action_pack/core'
|
6
|
+
end
|
7
|
+
|
8
|
+
module TestUnitHelpers
|
9
|
+
def assert_equal actual, expected
|
10
|
+
actual.should == expected
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
RSpec.configure do |config|
|
15
|
+
config.include TestUnitHelpers
|
16
|
+
end
|
17
|
+
|
metadata
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: opal-actionpack
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Tuckner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: opal
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.5.0
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.5.0
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: opal-rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
description: A small port of the glorious ActionPack for Opal
|
62
|
+
email:
|
63
|
+
- stevetuckner@stewdle.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- .gitignore
|
69
|
+
- Gemfile
|
70
|
+
- Gemfile.lock
|
71
|
+
- LICENSE
|
72
|
+
- LICENSE.txt
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- lib/opal-actionpack.rb
|
76
|
+
- lib/opal/actionpack.rb
|
77
|
+
- lib/opal/actionpack/version.rb
|
78
|
+
- opal-actionpack.gemspec
|
79
|
+
- opal/action_pack.rb
|
80
|
+
- opal/action_pack/core.rb
|
81
|
+
- opal/opal-actionpack.rb
|
82
|
+
- spec/actionpack_spec.rb
|
83
|
+
- spec/spec_helper.rb
|
84
|
+
homepage: https://github.com/boberetezeke/opal-actionpack
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options:
|
90
|
+
- --main
|
91
|
+
- README
|
92
|
+
- --line-numbers
|
93
|
+
- --include
|
94
|
+
- opal
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.0.3
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: This implements a subset of the rails/actionpack gem. Currently it does some
|
113
|
+
of the routing functionality, and some rendering functionality. It doesn't do forms
|
114
|
+
yet.
|
115
|
+
test_files:
|
116
|
+
- spec/actionpack_spec.rb
|
117
|
+
- spec/spec_helper.rb
|