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 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