clearwater 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4d5baa8070083b2dd17e375a1cb25f73aa7373b9
4
- data.tar.gz: e6e5f345cc9216fcba43b56065e63e9429d9eba3
3
+ metadata.gz: 5d75fcebb1932de023f32ad43613c30907bfed76
4
+ data.tar.gz: 1b0b6fba9eef8bf9539f1c7cc454af550fb40ff2
5
5
  SHA512:
6
- metadata.gz: a5abfc45b771018eb5d07ee9398a0411e7a0ae305c614d8460148305b806f738bfa4e6209f2a12ea630984e49c876a2db01147027c78b2fe8ef8a05f6dd35acb
7
- data.tar.gz: aa0f21f5bca2409e2818be2ce6d36df19ed7a7adac7890267adf5b894e27c501f5cfa6fd5f80c3e484c574897343f88701f82bee37711bbd5e06eef03f72bf32
6
+ metadata.gz: 70fac50714fb26e2600bf9e23d3ebaee2350b1500cfc21ec36265e0f8d30376e0abb7cabad394f92e5346f51211ec960060fc19d6257993a784c12365d5abf4c
7
+ data.tar.gz: 03a1ea608623bb899094124e71cae1708fcd40f4940d52d06de73fe570f26af8279d0e33bac731f995ac0153f031ac52c0362c0ed6ffd2e23761abddd4137197
@@ -1,3 +1,3 @@
1
1
  module Clearwater
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -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 'browser/event/pop_state'
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
- $document.on 'visibilitychange' do
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
- $window.on('popstate') { render_current_url }
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
- delay_render
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 `document.body != null` || `document.body != undefined`
71
- $document.body
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 = Time.now
84
+ start = `performance.now()`
81
85
  result = yield
82
- finish = Time.now
83
- puts "#{message} in #{(finish - start) * 1000}ms"
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
- false
95
+ !!@debug
92
96
  end
93
97
 
94
- def delay_render
95
- # Throttle rendering
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') { @_virtual_dom.render rendered }
108
+ benchmark('Rendered to actual DOM') { virtual_dom.render rendered }
115
109
  @last_render = Time.now
116
- on_render.each(&:call)
117
- on_render.clear
110
+ @will_render = false
111
+ run_callbacks
112
+ nil
118
113
  end
119
114
 
120
- def last_render
121
- @last_render ||= Time.now - 10
115
+ def virtual_dom
116
+ @virtual_dom ||= VirtualDOM::Document.new(element)
122
117
  end
123
118
 
124
- def time_between_renders
125
- 1 / RENDER_FPS
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
- def cached_render
4
- if !defined?(@cached_render) || should_render?
5
- @cached_render = sanitize_content(render)
6
- else
7
- @cached_render
8
- end
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 'set'
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 === undefined || attributes.$$class === #{Hash} || attributes === #{nil}`)
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(cached_render && !cached_render.$$stub) {
202
- return content.$cached_render();
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 Opal.nil;
22
- if(content.$$class === Opal.Array)
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
- string.gsub(/_(\w)/) { |match| match.gsub(/_/, '').upcase }
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.1.2
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-08-31 00:00:00.000000000 Z
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: '3.0'
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: '3.0'
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
@@ -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
@@ -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