clearwater 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/clearwater/version.rb +1 -1
- data/opal/clearwater/application.rb +31 -35
- data/opal/clearwater/cached_render.rb +16 -7
- data/opal/clearwater/component.rb +5 -22
- data/opal/clearwater/virtual_dom.rb +12 -4
- data/spec/clearwater/application_spec.rb +45 -0
- data/spec/clearwater/cached_render_spec.rb +46 -0
- data/spec/clearwater/router_spec.rb +51 -0
- data/spec/component_spec.rb +67 -0
- metadata +14 -9
- data/opal/clearwater/api_client.rb +0 -90
- data/opal/clearwater/cgi.rb +0 -14
- data/opal/clearwater/store.rb +0 -99
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d75fcebb1932de023f32ad43613c30907bfed76
|
4
|
+
data.tar.gz: 1b0b6fba9eef8bf9539f1c7cc454af550fb40ff2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 70fac50714fb26e2600bf9e23d3ebaee2350b1500cfc21ec36265e0f8d30376e0abb7cabad394f92e5346f51211ec960060fc19d6257993a784c12365d5abf4c
|
7
|
+
data.tar.gz: 03a1ea608623bb899094124e71cae1708fcd40f4940d52d06de73fe570f26af8279d0e33bac731f995ac0153f031ac52c0362c0ed6ffd2e23761abddd4137197
|
data/lib/clearwater/version.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
require 'clearwater/router'
|
2
2
|
require 'clearwater/application_registry'
|
3
|
+
require 'browser'
|
3
4
|
require 'browser/delay'
|
4
|
-
require '
|
5
|
+
require 'native'
|
6
|
+
require 'browser/event'
|
7
|
+
require 'browser/animation_frame'
|
5
8
|
|
6
9
|
module Clearwater
|
7
10
|
class Application
|
8
|
-
RENDER_FPS = 60
|
9
11
|
AppRegistry = ApplicationRegistry.new
|
10
12
|
|
11
13
|
attr_reader :store, :router, :component, :api_client, :on_render
|
@@ -15,17 +17,17 @@ module Clearwater
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def initialize options={}
|
18
|
-
@store = options.fetch(:store) { nil }
|
19
20
|
@router = options.fetch(:router) { Router.new }
|
20
21
|
@component = options.fetch(:component) { nil }
|
21
|
-
@api_client = options.fetch(:api_client) { nil }
|
22
22
|
@element = options.fetch(:element) { nil }
|
23
|
+
@document = options.fetch(:document) { $document }
|
24
|
+
@window = options.fetch(:window) { $window }
|
23
25
|
@on_render = []
|
24
26
|
|
25
27
|
router.application = self
|
26
|
-
component.router = router
|
28
|
+
component.router = router if component
|
27
29
|
|
28
|
-
|
30
|
+
@document.on 'visibilitychange' do
|
29
31
|
if @render_on_visibility_change
|
30
32
|
@render_on_visibility_change = false
|
31
33
|
render
|
@@ -33,16 +35,16 @@ module Clearwater
|
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
def call
|
38
|
+
def call &block
|
37
39
|
AppRegistry << self
|
38
|
-
render_current_url
|
40
|
+
render_current_url &block
|
39
41
|
watch_url
|
40
42
|
end
|
41
43
|
|
42
44
|
def watch_url
|
43
45
|
unless @watching_url
|
44
46
|
@watching_url = true
|
45
|
-
|
47
|
+
@window.on('popstate') { render_current_url }
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
@@ -53,6 +55,8 @@ module Clearwater
|
|
53
55
|
|
54
56
|
def render &block
|
55
57
|
on_render << block if block
|
58
|
+
return if @will_render
|
59
|
+
@will_render = true
|
56
60
|
|
57
61
|
# If the app isn't being shown, wait to render until it is.
|
58
62
|
if `document.hidden`
|
@@ -60,15 +64,15 @@ module Clearwater
|
|
60
64
|
return
|
61
65
|
end
|
62
66
|
|
63
|
-
|
67
|
+
animation_frame { perform_render }
|
64
68
|
|
65
69
|
nil
|
66
70
|
end
|
67
71
|
|
68
72
|
def element
|
69
73
|
@element ||= begin
|
70
|
-
if
|
71
|
-
|
74
|
+
if @document.body
|
75
|
+
@document.body
|
72
76
|
else
|
73
77
|
nil
|
74
78
|
end
|
@@ -77,10 +81,10 @@ module Clearwater
|
|
77
81
|
|
78
82
|
def benchmark message
|
79
83
|
if debug?
|
80
|
-
start =
|
84
|
+
start = `performance.now()`
|
81
85
|
result = yield
|
82
|
-
finish =
|
83
|
-
puts "#{message} in #{(finish - start)
|
86
|
+
finish = `performance.now()`
|
87
|
+
puts "#{message} in #{(finish - start).round(3)}ms"
|
84
88
|
result
|
85
89
|
else
|
86
90
|
yield
|
@@ -88,19 +92,11 @@ module Clearwater
|
|
88
92
|
end
|
89
93
|
|
90
94
|
def debug?
|
91
|
-
|
95
|
+
!!@debug
|
92
96
|
end
|
93
97
|
|
94
|
-
def
|
95
|
-
|
96
|
-
unless @next_render
|
97
|
-
@next_render = last_render + time_between_renders
|
98
|
-
now = Time.now
|
99
|
-
after [@next_render - now, time_between_renders].max do
|
100
|
-
perform_render
|
101
|
-
@next_render = nil
|
102
|
-
end
|
103
|
-
end
|
98
|
+
def debug!
|
99
|
+
@debug = true
|
104
100
|
end
|
105
101
|
|
106
102
|
def perform_render
|
@@ -108,21 +104,21 @@ module Clearwater
|
|
108
104
|
raise TypeError, "Cannot render to a non-existent element. Make sure the document ready event has been triggered before invoking the application."
|
109
105
|
end
|
110
106
|
|
111
|
-
@_virtual_dom ||= VirtualDOM::Document.new(element)
|
112
|
-
|
113
107
|
rendered = benchmark('Generated virtual DOM') { component.render }
|
114
|
-
benchmark('Rendered to actual DOM') {
|
108
|
+
benchmark('Rendered to actual DOM') { virtual_dom.render rendered }
|
115
109
|
@last_render = Time.now
|
116
|
-
|
117
|
-
|
110
|
+
@will_render = false
|
111
|
+
run_callbacks
|
112
|
+
nil
|
118
113
|
end
|
119
114
|
|
120
|
-
def
|
121
|
-
@
|
115
|
+
def virtual_dom
|
116
|
+
@virtual_dom ||= VirtualDOM::Document.new(element)
|
122
117
|
end
|
123
118
|
|
124
|
-
def
|
125
|
-
|
119
|
+
def run_callbacks
|
120
|
+
on_render.each(&:call)
|
121
|
+
on_render.clear
|
126
122
|
end
|
127
123
|
end
|
128
124
|
end
|
@@ -1,14 +1,23 @@
|
|
1
1
|
module Clearwater
|
2
2
|
module CachedRender
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
|
4
|
+
def self.included base
|
5
|
+
%x{
|
6
|
+
Opal.defn(base, 'type', 'Thunk');
|
7
|
+
Opal.defn(base, 'render', function(prev) {
|
8
|
+
var self = this;
|
9
|
+
var should_render;
|
10
|
+
|
11
|
+
if(prev && prev.vnode && #{!should_render?(`prev`)}) {
|
12
|
+
return prev.vnode;
|
13
|
+
} else {
|
14
|
+
return #{sanitize_content(render)};
|
15
|
+
}
|
16
|
+
});
|
17
|
+
}
|
9
18
|
end
|
10
19
|
|
11
|
-
def should_render?
|
20
|
+
def should_render? _
|
12
21
|
false
|
13
22
|
end
|
14
23
|
end
|
@@ -1,23 +1,10 @@
|
|
1
1
|
require 'clearwater/virtual_dom'
|
2
|
-
require '
|
2
|
+
require 'browser'
|
3
3
|
|
4
4
|
module Clearwater
|
5
5
|
module Component
|
6
6
|
attr_accessor :router, :outlet
|
7
7
|
|
8
|
-
def self.included(klass)
|
9
|
-
def klass.attributes(*attrs)
|
10
|
-
attrs.each do |attr|
|
11
|
-
ivar = "@#{attr}"
|
12
|
-
define_method(attr) { instance_variable_get(ivar) }
|
13
|
-
define_method("#{attr}=") do |value|
|
14
|
-
instance_variable_set ivar, value
|
15
|
-
call
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
8
|
def render
|
22
9
|
end
|
23
10
|
|
@@ -142,7 +129,7 @@ module Clearwater
|
|
142
129
|
end
|
143
130
|
|
144
131
|
def tag tag_name, attributes=nil, content=nil
|
145
|
-
if !(`attributes
|
132
|
+
if !(`attributes.$$is_hash || attributes === #{nil}`)
|
146
133
|
content = attributes
|
147
134
|
attributes = nil
|
148
135
|
end
|
@@ -158,10 +145,6 @@ module Clearwater
|
|
158
145
|
router.params_for_path(router.current_path)
|
159
146
|
end
|
160
147
|
|
161
|
-
def param(key)
|
162
|
-
params[key]
|
163
|
-
end
|
164
|
-
|
165
148
|
def sanitize_attributes attributes
|
166
149
|
return attributes unless attributes.is_a? Hash
|
167
150
|
|
@@ -189,7 +172,7 @@ module Clearwater
|
|
189
172
|
|
190
173
|
def sanitize_content content
|
191
174
|
%x{
|
192
|
-
if(content.$$class) {
|
175
|
+
if(content && content.$$class) {
|
193
176
|
if(content.$$class === Opal.Array) {
|
194
177
|
return #{content.map { |c| `self.$sanitize_content(c)` }};
|
195
178
|
} else if(content === Opal.nil) {
|
@@ -198,8 +181,8 @@ module Clearwater
|
|
198
181
|
var cached_render = content.$cached_render;
|
199
182
|
var render = content.$render;
|
200
183
|
|
201
|
-
if(
|
202
|
-
return content
|
184
|
+
if(content.type === 'Thunk' && typeof(content.render) === 'function') {
|
185
|
+
return content;
|
203
186
|
} else if(render && !render.$$stub) {
|
204
187
|
return self.$sanitize_content(content.$render());
|
205
188
|
} else {
|
@@ -2,12 +2,16 @@ require 'browser'
|
|
2
2
|
require 'clearwater/virtual_dom/js/virtual_dom.js'
|
3
3
|
|
4
4
|
module VirtualDOM
|
5
|
-
def self.node(tag_name, attributes, content)
|
5
|
+
def self.node(tag_name, attributes=nil, content=nil)
|
6
6
|
content = sanitize_content(content)
|
7
7
|
attributes = HashUtils.camelize_keys(attributes).to_n
|
8
8
|
`virtualDom.h(tag_name, attributes, content)`
|
9
9
|
end
|
10
10
|
|
11
|
+
def self.create_element(node)
|
12
|
+
`virtualDom.create(node)`
|
13
|
+
end
|
14
|
+
|
11
15
|
def self.diff first, second
|
12
16
|
`virtualDom.diff(first, second)`
|
13
17
|
end
|
@@ -18,8 +22,8 @@ module VirtualDOM
|
|
18
22
|
|
19
23
|
def self.sanitize_content content
|
20
24
|
%x{
|
21
|
-
if(content === Opal.nil) return
|
22
|
-
if(content.$$
|
25
|
+
if(content === Opal.nil || content === undefined) return null;
|
26
|
+
if(content.$$is_array)
|
23
27
|
return #{content.map!{ |c| sanitize_content c }};
|
24
28
|
return content;
|
25
29
|
}
|
@@ -54,7 +58,11 @@ module VirtualDOM
|
|
54
58
|
|
55
59
|
module StringUtils
|
56
60
|
def self.camelize string
|
57
|
-
|
61
|
+
%x{
|
62
|
+
return string.replace(/_(\w)/g, function(full_match, character_match) {
|
63
|
+
return character_match.toUpperCase();
|
64
|
+
})
|
65
|
+
}
|
58
66
|
end
|
59
67
|
end
|
60
68
|
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'clearwater'
|
2
|
+
|
3
|
+
module Clearwater
|
4
|
+
RSpec.describe Application do
|
5
|
+
let(:app) {
|
6
|
+
Application.new(
|
7
|
+
component: component,
|
8
|
+
element: element,
|
9
|
+
)
|
10
|
+
}
|
11
|
+
let(:component) {
|
12
|
+
Class.new do
|
13
|
+
include Clearwater::Component
|
14
|
+
|
15
|
+
def render
|
16
|
+
h1('Hello world')
|
17
|
+
end
|
18
|
+
end.new
|
19
|
+
}
|
20
|
+
let(:element) { $document.create_element('div') }
|
21
|
+
|
22
|
+
it 'renders to the specified element' do
|
23
|
+
app.perform_render
|
24
|
+
|
25
|
+
expect(element.inner_html).to eq '<h1>Hello world</h1>'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'calls queued blocks after rendering' do
|
29
|
+
i = 1
|
30
|
+
app.on_render << proc { i += 1 }
|
31
|
+
app.on_render << proc { i += 1 }
|
32
|
+
|
33
|
+
app.perform_render
|
34
|
+
expect(i).to eq 3
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'empties the block queue after rendering' do
|
38
|
+
app.on_render << proc { }
|
39
|
+
|
40
|
+
app.perform_render
|
41
|
+
|
42
|
+
expect(app.on_render).to be_empty
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'clearwater/component'
|
2
|
+
require 'clearwater/cached_render'
|
3
|
+
|
4
|
+
module Clearwater
|
5
|
+
describe CachedRender do
|
6
|
+
let(:component_class) {
|
7
|
+
Class.new do
|
8
|
+
include Clearwater::Component
|
9
|
+
include Clearwater::CachedRender
|
10
|
+
|
11
|
+
def initialize value
|
12
|
+
@value = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def render
|
16
|
+
@value.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
}
|
20
|
+
let(:value) { double }
|
21
|
+
let(:component) { component_class.new(value) }
|
22
|
+
|
23
|
+
it 'memoizes the return value of render' do
|
24
|
+
component = component()
|
25
|
+
|
26
|
+
expect(value).to receive(:to_s)
|
27
|
+
%x{ component.render(component) }
|
28
|
+
|
29
|
+
component.instance_exec { @vnode = VirtualDOM.node('div', 'howdy') }
|
30
|
+
expect(value).not_to receive(:to_s)
|
31
|
+
|
32
|
+
2.times { `component.render(component)` }
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'uses should_render? to determine whether to call render again' do
|
36
|
+
component = component()
|
37
|
+
def component.should_render?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
expect(value).to receive(:to_s).twice
|
42
|
+
|
43
|
+
2.times { `component.render(component)` }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'clearwater/router'
|
2
|
+
|
3
|
+
module Clearwater
|
4
|
+
RSpec.describe Router do
|
5
|
+
let(:component_class) {
|
6
|
+
Class.new do
|
7
|
+
include Clearwater::Component
|
8
|
+
end
|
9
|
+
}
|
10
|
+
let(:routed_component) { component_class.new }
|
11
|
+
let!(:router) {
|
12
|
+
component = routed_component
|
13
|
+
Router.new do
|
14
|
+
route 'articles' => component do
|
15
|
+
route ':article_id' => component
|
16
|
+
end
|
17
|
+
end
|
18
|
+
}
|
19
|
+
|
20
|
+
it 'sets the router for a routed component' do
|
21
|
+
expect(routed_component.router).to be router
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'gets the params for a given path' do
|
25
|
+
expect(router.params_for_path('/articles/foo')).to eq article_id: 'foo'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'gets the components for a given path' do
|
29
|
+
expect(router.targets_for_path('/articles/1')).to eq [
|
30
|
+
routed_component, routed_component
|
31
|
+
]
|
32
|
+
|
33
|
+
expect(router.targets_for_path('/articles')).to eq [routed_component]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'gets the current path' do
|
37
|
+
location = { pathname: '/foo' }
|
38
|
+
router = Router.new(location: location)
|
39
|
+
|
40
|
+
expect(router.current_path).to eq '/foo'
|
41
|
+
|
42
|
+
location[:pathname] = '/bar'
|
43
|
+
|
44
|
+
expect(router.current_path).to eq '/bar'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'gets the params from the path' do
|
48
|
+
expect(router.params_for_path('/articles/123')).to eq({ article_id: '123' })
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'clearwater/component'
|
2
|
+
|
3
|
+
# When running specs from the CLI, it doesn't define this class
|
4
|
+
unless defined? Browser::Event
|
5
|
+
module Browser
|
6
|
+
class Event
|
7
|
+
def initialize event
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module Clearwater
|
14
|
+
RSpec.describe Component do
|
15
|
+
let(:component_class) {
|
16
|
+
Class.new do
|
17
|
+
include Clearwater::Component
|
18
|
+
end
|
19
|
+
}
|
20
|
+
let(:component) { component_class.new }
|
21
|
+
|
22
|
+
it 'provides a default render method' do
|
23
|
+
expect(component.render).to be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
Component::HTML_TAGS.each do |tag|
|
27
|
+
it "provides helpers for `#{tag}` elements" do
|
28
|
+
expect(`#{component.send(tag)}.tagName`).to eq tag.upcase
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'sanitizes element attributes' do
|
33
|
+
attributes = component.sanitize_attributes({
|
34
|
+
class: 'foo',
|
35
|
+
onclick: proc { |event| expect(event).to be_a Browser::Event },
|
36
|
+
})
|
37
|
+
|
38
|
+
# Renames :class to :class_name
|
39
|
+
expect(attributes[:class_name]).to eq 'foo'
|
40
|
+
|
41
|
+
# Wraps yielded events in a Browser::Event
|
42
|
+
attributes[:onclick].call(`document.createEvent('MouseEvent')`)
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'sanitizing content' do
|
46
|
+
it 'sanitizes components by calling `render`' do
|
47
|
+
allow(component).to receive(:render) { 'foo' }
|
48
|
+
expect(component.sanitize_content(component)).to eq 'foo'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'sanitizes arrays by sanitizing each element' do
|
52
|
+
allow(component).to receive(:render) { 'foo' }
|
53
|
+
expect(component.sanitize_content([component, nil, 1])).to eq ['foo', '', 1]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'retrieves params from the router' do
|
58
|
+
router = double('Router')
|
59
|
+
params = { article_id: 123 }
|
60
|
+
allow(router).to receive(:params_for_path) { params }
|
61
|
+
allow(router).to receive(:current_path) { '/articles/123' }
|
62
|
+
component.router = router
|
63
|
+
|
64
|
+
expect(component.params).to eq params
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: clearwater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jamie Gaskins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-10-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: opal
|
@@ -53,19 +53,19 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.3'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name: rspec
|
56
|
+
name: opal-rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.5.0.beta2
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 0.5.0.beta2
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,19 +132,20 @@ files:
|
|
132
132
|
- lib/clearwater.rb
|
133
133
|
- lib/clearwater/version.rb
|
134
134
|
- opal/clearwater.rb
|
135
|
-
- opal/clearwater/api_client.rb
|
136
135
|
- opal/clearwater/application.rb
|
137
136
|
- opal/clearwater/application_registry.rb
|
138
137
|
- opal/clearwater/cached_render.rb
|
139
|
-
- opal/clearwater/cgi.rb
|
140
138
|
- opal/clearwater/component.rb
|
141
139
|
- opal/clearwater/link.rb
|
142
140
|
- opal/clearwater/router.rb
|
143
141
|
- opal/clearwater/router/route.rb
|
144
142
|
- opal/clearwater/router/route_collection.rb
|
145
|
-
- opal/clearwater/store.rb
|
146
143
|
- opal/clearwater/virtual_dom.rb
|
147
144
|
- opal/clearwater/virtual_dom/js/virtual_dom.js
|
145
|
+
- spec/clearwater/application_spec.rb
|
146
|
+
- spec/clearwater/cached_render_spec.rb
|
147
|
+
- spec/clearwater/router_spec.rb
|
148
|
+
- spec/component_spec.rb
|
148
149
|
homepage: https://clearwater-rb.github.io/
|
149
150
|
licenses:
|
150
151
|
- MIT
|
@@ -169,5 +170,9 @@ rubygems_version: 2.4.8
|
|
169
170
|
signing_key:
|
170
171
|
specification_version: 4
|
171
172
|
summary: Front-end web framework built on Opal
|
172
|
-
test_files:
|
173
|
+
test_files:
|
174
|
+
- spec/clearwater/application_spec.rb
|
175
|
+
- spec/clearwater/cached_render_spec.rb
|
176
|
+
- spec/clearwater/router_spec.rb
|
177
|
+
- spec/component_spec.rb
|
173
178
|
has_rdoc:
|
@@ -1,90 +0,0 @@
|
|
1
|
-
require "clearwater/cgi"
|
2
|
-
require "browser/http"
|
3
|
-
|
4
|
-
module Clearwater
|
5
|
-
class APIClient
|
6
|
-
ResponseNotFinished = Class.new(RuntimeError)
|
7
|
-
|
8
|
-
attr_reader :base_url
|
9
|
-
|
10
|
-
def initialize attributes={}
|
11
|
-
@base_url = attributes.fetch(:base_url) { nil }
|
12
|
-
end
|
13
|
-
|
14
|
-
def fetch resource, id, params
|
15
|
-
response = Browser::HTTP.get(path_for_resource(resource, id, params))
|
16
|
-
Response.new(response)
|
17
|
-
end
|
18
|
-
|
19
|
-
def store resource, id, data={}
|
20
|
-
path = case id
|
21
|
-
when String, Numeric
|
22
|
-
path_for_resource(resource, id)
|
23
|
-
when Hash
|
24
|
-
data.merge! id
|
25
|
-
path_for_resource(resource)
|
26
|
-
end
|
27
|
-
|
28
|
-
response = Browser::HTTP.post(path, data)
|
29
|
-
Response.new(response)
|
30
|
-
end
|
31
|
-
|
32
|
-
def update resource, id, data
|
33
|
-
response = Browser::HTTP.patch path_for_resource(resource, id), data: data
|
34
|
-
Response.new(response)
|
35
|
-
end
|
36
|
-
|
37
|
-
def delete resource, id
|
38
|
-
response = Browser::HTTP.delete path_for_resource(resource, id)
|
39
|
-
Response.new(response)
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def path_for_resource resource, id, params={}
|
45
|
-
path = "#{base_url}/#{resource}"
|
46
|
-
case id
|
47
|
-
when String, Numeric
|
48
|
-
path += "/#{id}"
|
49
|
-
when Hash
|
50
|
-
params.merge! id
|
51
|
-
end
|
52
|
-
|
53
|
-
if params.any?
|
54
|
-
path += "?#{query_string(params)}"
|
55
|
-
end
|
56
|
-
|
57
|
-
path
|
58
|
-
end
|
59
|
-
|
60
|
-
def query_string params
|
61
|
-
query_params = params.map { |key, value|
|
62
|
-
"#{CGI.escape(key)}=#{CGI.escape(value)}"
|
63
|
-
}.join("&")
|
64
|
-
end
|
65
|
-
|
66
|
-
class Response
|
67
|
-
attr_reader :response
|
68
|
-
|
69
|
-
def initialize response
|
70
|
-
@response = response
|
71
|
-
end
|
72
|
-
|
73
|
-
def then
|
74
|
-
response.then do |r|
|
75
|
-
yield self.class.new(r)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def fail
|
80
|
-
response.fail do |r|
|
81
|
-
yield self.class.new(r)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def json
|
86
|
-
response.json
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
data/opal/clearwater/cgi.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
module Clearwater
|
2
|
-
class CGI
|
3
|
-
def self.escape string
|
4
|
-
# string.gsub(/([^ a-zA-Z0-9_.-]+)/) do |ch|
|
5
|
-
string.chars.map do |ch|
|
6
|
-
if ch =~ /([^ a-zA-Z0-9_.-]+)/
|
7
|
-
"%#{ch.ord.to_s(16).upcase}"
|
8
|
-
else
|
9
|
-
ch
|
10
|
-
end
|
11
|
-
end.join.tr(" ", "+")
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
data/opal/clearwater/store.rb
DELETED
@@ -1,99 +0,0 @@
|
|
1
|
-
require "json"
|
2
|
-
|
3
|
-
module Clearwater
|
4
|
-
class Store
|
5
|
-
attr_reader :mapping, :identity_map
|
6
|
-
|
7
|
-
def initialize options={}
|
8
|
-
@url = options.fetch(:url) { "/api/:model/:id" }
|
9
|
-
@mapping = Mapping.new(options.fetch(:mapping) { {} })
|
10
|
-
@protocol = options.fetch(:protocol) { HTTP }
|
11
|
-
@identity_map = IdentityMap.new
|
12
|
-
end
|
13
|
-
|
14
|
-
def all klass
|
15
|
-
deserialized = api(:get, url_for(klass)).body
|
16
|
-
models = JSON.parse(deserialized).map do |attributes|
|
17
|
-
deserialize(klass, attributes)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def find klass, id
|
22
|
-
identity_map.fetch(klass, id) do
|
23
|
-
serialized = api(:get, url_for(klass, id)).body
|
24
|
-
identity_map[klass][id] = deserialize(klass, JSON.parse(serialized))
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def save model
|
29
|
-
method = persisted?(model) ? :patch : :post
|
30
|
-
response = api method, url_for_model(model)
|
31
|
-
if method == :post && response.ok
|
32
|
-
attributes = JSON.parse(response.body)
|
33
|
-
model.instance_variable_set :@id, attributes[:id]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def delete model
|
38
|
-
api :delete, url_for_model(model)
|
39
|
-
end
|
40
|
-
|
41
|
-
def persisted? model
|
42
|
-
!!model.id
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
|
47
|
-
def api method, url
|
48
|
-
@protocol.public_send method, url
|
49
|
-
end
|
50
|
-
|
51
|
-
def url_for klass, id=nil
|
52
|
-
@url.gsub(":model", mapping[klass])
|
53
|
-
.gsub(":id", id.to_s)
|
54
|
-
end
|
55
|
-
|
56
|
-
def url_for_model model
|
57
|
-
url_for(model.class, model.id)
|
58
|
-
end
|
59
|
-
|
60
|
-
def url_for_class klass, id
|
61
|
-
url_for(klass, nil)
|
62
|
-
end
|
63
|
-
|
64
|
-
def deserialize klass, attributes
|
65
|
-
model = klass.allocate
|
66
|
-
attributes.each do |attr, value|
|
67
|
-
model.instance_variable_set "@#{attr}", value
|
68
|
-
end
|
69
|
-
|
70
|
-
model
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
class Mapping
|
75
|
-
def initialize mappings={}
|
76
|
-
@custom_mappings = mappings
|
77
|
-
end
|
78
|
-
|
79
|
-
def [] klass
|
80
|
-
@custom_mappings.fetch(klass) { |*args|
|
81
|
-
klass.name.downcase.gsub("::", "/") + "s"
|
82
|
-
}
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
class IdentityMap
|
87
|
-
def initialize
|
88
|
-
@map = Hash.new { |h, k| h[k] = {} }
|
89
|
-
end
|
90
|
-
|
91
|
-
def [] key
|
92
|
-
@map[key]
|
93
|
-
end
|
94
|
-
|
95
|
-
def fetch(klass, id, &block)
|
96
|
-
self[klass].fetch(id, &block)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|