rep 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +11 -0
- data/README.md +8 -14
- data/Rakefile +18 -1
- data/docs/index.html +478 -0
- data/docs/lib/rep.html +478 -0
- data/docs/lib/rep/version.html +44 -0
- data/docs/rep.html +478 -0
- data/docs/rep/version.html +44 -0
- data/lib/rep.rb +180 -16
- data/lib/rep/version.rb +1 -1
- data/rep.gemspec +4 -0
- data/test/lib/rep_test.rb +47 -13
- data/test/test_helper.rb +0 -6
- metadata +72 -2
data/.travis.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- "1.8.7"
|
4
|
+
- "1.9.2"
|
5
|
+
- "1.9.3"
|
6
|
+
- jruby-18mode # JRuby in 1.8 mode
|
7
|
+
- jruby-19mode # JRuby in 1.9 mode
|
8
|
+
- rbx-18mode
|
9
|
+
- rbx-19mode
|
10
|
+
# uncomment this line if your project needs to run something other than `rake`:
|
11
|
+
# script: bundle exec rspec spec
|
data/README.md
CHANGED
@@ -52,13 +52,7 @@ Or install it yourself as:
|
|
52
52
|
|
53
53
|
## Usage
|
54
54
|
|
55
|
-
`include Rep` into any class
|
56
|
-
among other things. You describe the top level keys you want for your
|
57
|
-
json with the `::fields` method. The values for the fields are expected
|
58
|
-
to be returned from methods on the object of the same name.
|
59
|
-
|
60
|
-
If a class has `fields :one => :default`, then `def one; 1; end` is
|
61
|
-
expected.
|
55
|
+
`include Rep` into any class. See [nathanherald.com/rep](http://nathanherald.com/rep) for complete docs on every method.
|
62
56
|
|
63
57
|
## Examples
|
64
58
|
|
@@ -70,7 +64,7 @@ class PhotoRep
|
|
70
64
|
|
71
65
|
initialize_with :photo
|
72
66
|
|
73
|
-
|
67
|
+
fields [:url, :title, :exif, :location, :user] => :default
|
74
68
|
|
75
69
|
forward [:title, :exif, :location] => :photo
|
76
70
|
forward :user => :user_rep
|
@@ -91,20 +85,19 @@ class UserRep
|
|
91
85
|
|
92
86
|
initialize_with :user
|
93
87
|
|
94
|
-
|
95
|
-
|
88
|
+
fields [:name, :email, :location] => :default
|
89
|
+
fields [:id, :admin].concat(fields(:default)) => :admin
|
96
90
|
|
97
|
-
forward
|
91
|
+
forward fields(:admin) => :user
|
98
92
|
end
|
99
93
|
|
100
94
|
# You can now do crazy stuff like
|
101
|
-
|
102
95
|
UserRep.new(user: User.first).to_hash.keys # => [:name, :email, :location]
|
103
96
|
|
104
97
|
# To save from creating lots of objects, you can use a shared class that is reset fresh
|
105
98
|
UserRep.shared(user: User.first).to_hash # => { name: "Nathan Herald:, ...
|
106
99
|
|
107
|
-
# You can use class to proc (that makes a hash using the shared
|
100
|
+
# You can use class to proc (that makes a hash using the shared instance)
|
108
101
|
User.all.map(&UserRep) # => [{ name: "Nathan Herald" ...
|
109
102
|
|
110
103
|
# or maybe find all photos which will embed all users (and only ever make one instance each of PhotoRep and UserRep)
|
@@ -117,8 +110,9 @@ You don't have to have a Rep per model and Rep's can represent multiple objects
|
|
117
110
|
|
118
111
|
```ruby
|
119
112
|
class ProjectReport
|
113
|
+
include Rep
|
120
114
|
initialize_with :project, :active_users, :orders
|
121
|
-
fields [:name, :date, :count, :total_gross_cost, :cost_per_active_user]
|
115
|
+
fields [:name, :date, :count, :total_gross_cost, :cost_per_active_user] => :default
|
122
116
|
forward :date => :project
|
123
117
|
forward :count => :orders
|
124
118
|
|
data/Rakefile
CHANGED
@@ -1,8 +1,25 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
+
|
2
3
|
require 'rake/testtask'
|
3
4
|
Rake::TestTask.new do |t|
|
4
5
|
t.libs << 'test'
|
5
6
|
t.test_files = FileList['test/**/*_test.rb']
|
6
7
|
t.verbose = true
|
7
8
|
end
|
8
|
-
|
9
|
+
|
10
|
+
require 'rdiscount'
|
11
|
+
require 'rocco/tasks'
|
12
|
+
|
13
|
+
desc "Build Rocco Docs"
|
14
|
+
Rocco::make 'docs'
|
15
|
+
|
16
|
+
require 'fileutils'
|
17
|
+
desc "copy the rocco files from lib into the base docs folder"
|
18
|
+
task :copy_to_index do
|
19
|
+
`cp -R docs/lib/* docs/`
|
20
|
+
`cp docs/rep.html docs/index.html`
|
21
|
+
end
|
22
|
+
|
23
|
+
task :docs => [:rocco, :copy_to_index]
|
24
|
+
|
25
|
+
task :default => 'test'
|
data/docs/index.html
ADDED
@@ -0,0 +1,478 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="content-type" content="text/html;charset=utf-8">
|
5
|
+
<title>rep.rb</title>
|
6
|
+
<link rel="stylesheet" href="http://jashkenas.github.com/docco/resources/docco.css">
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<div id='container'>
|
10
|
+
<div id="background"></div>
|
11
|
+
<div id="jump_to">
|
12
|
+
Jump To …
|
13
|
+
<div id="jump_wrapper">
|
14
|
+
<div id="jump_page">
|
15
|
+
<a class="source" href="rep.html">rep.rb</a>
|
16
|
+
<a class="source" href="rep/version.html">version.rb</a>
|
17
|
+
</div>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
<table cellspacing=0 cellpadding=0>
|
21
|
+
<thead>
|
22
|
+
<tr>
|
23
|
+
<th class=docs><h1>rep.rb</h1></th>
|
24
|
+
<th class=code></th>
|
25
|
+
</tr>
|
26
|
+
</thead>
|
27
|
+
<tbody>
|
28
|
+
<tr id='section-1'>
|
29
|
+
<td class=docs>
|
30
|
+
<div class="pilwrap">
|
31
|
+
<a class="pilcrow" href="#section-1">¶</a>
|
32
|
+
</div>
|
33
|
+
<p><strong>Rep</strong> is a small module to endow any class to make json quickly. It solves four problems:</p>
|
34
|
+
|
35
|
+
<ol>
|
36
|
+
<li>Enumerating top level keys for a json structure</li>
|
37
|
+
<li>Providing a convention for the value of those keys</li>
|
38
|
+
<li>Defining <code>attr_accessor</code>’s that are prefilled from an options hash given to <code>#initialize</code></li>
|
39
|
+
<li>Sharing instances to help GC</li>
|
40
|
+
</ol>
|
41
|
+
|
42
|
+
|
43
|
+
<p>The code is available on <a href="http://github.com/myobie/rep">github</a>.</p>
|
44
|
+
</td>
|
45
|
+
<td class=code>
|
46
|
+
<div class='highlight'><pre></pre></div>
|
47
|
+
</td>
|
48
|
+
</tr>
|
49
|
+
<tr id='section-2'>
|
50
|
+
<td class=docs>
|
51
|
+
<div class="pilwrap">
|
52
|
+
<a class="pilcrow" href="#section-2">¶</a>
|
53
|
+
</div>
|
54
|
+
<p><code>Forwardable</code> is in the stdlib and allows ruby objects to delegate methods off to other objects. An example:</p>
|
55
|
+
|
56
|
+
<pre><code>class A
|
57
|
+
extend Forwardable
|
58
|
+
delegate [:length, :first] => :@array
|
59
|
+
def initialize(array = [])
|
60
|
+
@array = array
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
A.new([1,2,3]).length # => 3
|
65
|
+
A.new([1,2,3]).first # => 1
|
66
|
+
</code></pre>
|
67
|
+
</td>
|
68
|
+
<td class=code>
|
69
|
+
<div class='highlight'><pre><span class="nb">require</span> <span class="s1">'forwardable'</span></pre></div>
|
70
|
+
</td>
|
71
|
+
</tr>
|
72
|
+
<tr id='section-3'>
|
73
|
+
<td class=docs>
|
74
|
+
<div class="pilwrap">
|
75
|
+
<a class="pilcrow" href="#section-3">¶</a>
|
76
|
+
</div>
|
77
|
+
<p><code>JSON::generate</code> and <code>JSON::decode</code> are much safer to use than <code>Object#to_json</code>.</p>
|
78
|
+
</td>
|
79
|
+
<td class=code>
|
80
|
+
<div class='highlight'><pre><span class="nb">require</span> <span class="s1">'json'</span>
|
81
|
+
|
82
|
+
<span class="nb">require</span> <span class="s1">'rep/version'</span>
|
83
|
+
<span class="k">module</span> <span class="nn">Rep</span></pre></div>
|
84
|
+
</td>
|
85
|
+
</tr>
|
86
|
+
<tr id='section-4'>
|
87
|
+
<td class=docs>
|
88
|
+
<div class="pilwrap">
|
89
|
+
<a class="pilcrow" href="#section-4">¶</a>
|
90
|
+
</div>
|
91
|
+
<p>All classes that <code>include Rep</code> are extended with <code>Forwardable</code>,
|
92
|
+
given some aliases, endowned with <code>HashieSupport</code> if Hashie is loaded,
|
93
|
+
and setup an empty <code>#parse_opts</code> because it is required for <code>::shared</code>.</p>
|
94
|
+
</td>
|
95
|
+
<td class=code>
|
96
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">included</span><span class="p">(</span><span class="n">klass</span><span class="p">)</span>
|
97
|
+
<span class="n">klass</span><span class="o">.</span><span class="n">extend</span> <span class="no">Forwardable</span>
|
98
|
+
<span class="n">klass</span><span class="o">.</span><span class="n">extend</span> <span class="no">ClassMethods</span>
|
99
|
+
<span class="n">klass</span><span class="o">.</span><span class="n">instance_eval</span> <span class="p">{</span>
|
100
|
+
<span class="k">class</span> <span class="o"><<</span> <span class="nb">self</span>
|
101
|
+
<span class="k">alias</span> <span class="n">forward</span> <span class="n">delegate</span>
|
102
|
+
|
103
|
+
<span class="k">unless</span> <span class="n">defined?</span><span class="p">(</span><span class="n">fields</span><span class="p">)</span>
|
104
|
+
<span class="k">alias</span> <span class="n">fields</span> <span class="n">json_fields</span>
|
105
|
+
<span class="k">end</span>
|
106
|
+
|
107
|
+
<span class="k">if</span> <span class="n">defined?</span><span class="p">(</span><span class="no">Hashie</span><span class="p">)</span>
|
108
|
+
<span class="kp">include</span> <span class="no">HashieSupport</span>
|
109
|
+
<span class="k">end</span>
|
110
|
+
<span class="k">end</span>
|
111
|
+
|
112
|
+
<span class="k">unless</span> <span class="n">defined?</span><span class="p">(</span><span class="n">parse_opts</span><span class="p">)</span>
|
113
|
+
<span class="k">def</span> <span class="nf">parse_opts</span><span class="p">(</span><span class="n">opts</span> <span class="o">=</span> <span class="p">{});</span> <span class="k">end</span>
|
114
|
+
<span class="k">end</span>
|
115
|
+
<span class="p">}</span>
|
116
|
+
<span class="k">end</span></pre></div>
|
117
|
+
</td>
|
118
|
+
</tr>
|
119
|
+
<tr id='section-5'>
|
120
|
+
<td class=docs>
|
121
|
+
<div class="pilwrap">
|
122
|
+
<a class="pilcrow" href="#section-5">¶</a>
|
123
|
+
</div>
|
124
|
+
<p>Since a goal is to be able to share instances, we need an easy way to reset a
|
125
|
+
shared instance back to factory defaults. If you memoize any methods that are
|
126
|
+
not declared as json fields, then overried this method and set any memoized
|
127
|
+
variables to nil, then super.</p>
|
128
|
+
</td>
|
129
|
+
<td class=code>
|
130
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">reset_for_json!</span>
|
131
|
+
<span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">all_json_methods</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">method_name</span><span class="o">|</span>
|
132
|
+
<span class="nb">instance_variable_set</span><span class="p">(</span><span class="ss">:"@</span><span class="si">#{</span><span class="n">method_name</span><span class="si">}</span><span class="ss">"</span><span class="p">,</span> <span class="kp">nil</span><span class="p">)</span>
|
133
|
+
<span class="k">end</span>
|
134
|
+
<span class="k">end</span></pre></div>
|
135
|
+
</td>
|
136
|
+
</tr>
|
137
|
+
<tr id='section-6'>
|
138
|
+
<td class=docs>
|
139
|
+
<div class="pilwrap">
|
140
|
+
<a class="pilcrow" href="#section-6">¶</a>
|
141
|
+
</div>
|
142
|
+
<p>All the work of generating a hash from an instance is packaged up in one method. Since
|
143
|
+
fields can be aliases in the format <code>{ :json_key_name => :method_name }</code>, there
|
144
|
+
is some fancy logic to determine the <code>field_name</code> and <code>method_name</code> variables.</p>
|
145
|
+
|
146
|
+
<pre><code>{ :one => :foo }.to_a # => [[:one, :foo]]
|
147
|
+
</code></pre>
|
148
|
+
|
149
|
+
<p>Right now it will raise if either a field doesn’t have a method to provide it’s value or
|
150
|
+
if there are no json fields setup for the particular set (which defaults to <code>:default</code>).</p>
|
151
|
+
</td>
|
152
|
+
<td class=code>
|
153
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">to_hash</span><span class="p">(</span><span class="nb">name</span> <span class="o">=</span> <span class="ss">:default</span><span class="p">)</span>
|
154
|
+
<span class="k">if</span> <span class="n">fields</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">json_fields</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
|
155
|
+
<span class="n">fields</span><span class="o">.</span><span class="n">reduce</span><span class="p">({})</span> <span class="k">do</span> <span class="o">|</span><span class="n">memo</span><span class="p">,</span> <span class="n">field</span><span class="o">|</span>
|
156
|
+
<span class="n">field_name</span><span class="p">,</span> <span class="n">method_name</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span> <span class="p">?</span> <span class="n">field</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">first</span> <span class="p">:</span> <span class="o">[</span><span class="n">field</span><span class="p">,</span> <span class="n">field</span><span class="o">]</span>
|
157
|
+
<span class="k">begin</span>
|
158
|
+
<span class="n">memo</span><span class="o">[</span><span class="n">field_name</span><span class="o">]</span> <span class="o">=</span> <span class="nb">send</span><span class="p">(</span><span class="n">method_name</span><span class="p">)</span>
|
159
|
+
<span class="k">rescue</span> <span class="no">NoMethodError</span> <span class="o">=></span> <span class="n">e</span>
|
160
|
+
<span class="n">message</span> <span class="o">=</span> <span class="s2">"There is no method named '</span><span class="si">#{</span><span class="n">method_name</span><span class="si">}</span><span class="s2">' for the class '</span><span class="si">#{</span><span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="si">}</span><span class="s2">' for the '</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">' list of fields : </span><span class="si">#{</span><span class="n">e</span><span class="o">.</span><span class="n">message</span><span class="si">}</span><span class="s2">"</span>
|
161
|
+
<span class="k">raise</span> <span class="no">NoMethodError</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">method_name</span><span class="p">,</span> <span class="n">e</span><span class="o">.</span><span class="n">args</span><span class="p">)</span>
|
162
|
+
<span class="k">end</span>
|
163
|
+
<span class="n">memo</span>
|
164
|
+
<span class="k">end</span>
|
165
|
+
<span class="k">else</span>
|
166
|
+
<span class="k">raise</span> <span class="s2">"There are no json fields under the name: </span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
|
167
|
+
<span class="k">end</span>
|
168
|
+
<span class="k">end</span>
|
169
|
+
|
170
|
+
<span class="k">def</span> <span class="nf">to_json</span>
|
171
|
+
<span class="no">JSON</span><span class="o">.</span><span class="n">generate</span><span class="p">(</span><span class="n">to_hash</span><span class="p">)</span>
|
172
|
+
<span class="k">end</span>
|
173
|
+
|
174
|
+
<span class="k">module</span> <span class="nn">ClassMethods</span></pre></div>
|
175
|
+
</td>
|
176
|
+
</tr>
|
177
|
+
<tr id='section-7'>
|
178
|
+
<td class=docs>
|
179
|
+
<div class="pilwrap">
|
180
|
+
<a class="pilcrow" href="#section-7">¶</a>
|
181
|
+
</div>
|
182
|
+
<p>Defines an attr_accessor with a default value. The default for default is nil. Example:</p>
|
183
|
+
|
184
|
+
<pre><code>class A
|
185
|
+
register_accessor :name => "No Name"
|
186
|
+
end
|
187
|
+
|
188
|
+
A.new.name # => "No Name"
|
189
|
+
</code></pre>
|
190
|
+
</td>
|
191
|
+
<td class=code>
|
192
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">register_accessor</span><span class="p">(</span><span class="n">acc</span><span class="p">)</span>
|
193
|
+
<span class="nb">name</span><span class="p">,</span> <span class="n">default</span> <span class="o">=</span> <span class="n">acc</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span> <span class="p">?</span> <span class="n">acc</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">first</span> <span class="p">:</span> <span class="o">[</span><span class="n">acc</span><span class="p">,</span> <span class="kp">nil</span><span class="o">]</span>
|
194
|
+
<span class="kp">attr_accessor</span> <span class="nb">name</span>
|
195
|
+
<span class="k">if</span> <span class="n">default</span>
|
196
|
+
<span class="n">define_method</span> <span class="nb">name</span> <span class="k">do</span>
|
197
|
+
<span class="n">var_name</span> <span class="o">=</span> <span class="ss">:"@</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="ss">"</span>
|
198
|
+
<span class="nb">instance_variable_get</span><span class="p">(</span><span class="n">var_name</span><span class="p">)</span> <span class="o">||</span> <span class="nb">instance_variable_set</span><span class="p">(</span><span class="n">var_name</span><span class="p">,</span> <span class="n">default</span><span class="p">)</span>
|
199
|
+
<span class="k">end</span>
|
200
|
+
<span class="k">end</span>
|
201
|
+
<span class="k">end</span></pre></div>
|
202
|
+
</td>
|
203
|
+
</tr>
|
204
|
+
<tr id='section-8'>
|
205
|
+
<td class=docs>
|
206
|
+
<div class="pilwrap">
|
207
|
+
<a class="pilcrow" href="#section-8">¶</a>
|
208
|
+
</div>
|
209
|
+
<p>Defines an <code>#initialize</code> method that accepts a Hash argument and copies some keys out into <code>attr_accessors</code>.
|
210
|
+
If your class already has an <code>#iniatialize</code> method then this will overwrite it (so don’t use it). <code>#initialize_with</code>
|
211
|
+
does not have to be used to use any other parts of Rep.</p>
|
212
|
+
</td>
|
213
|
+
<td class=code>
|
214
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">initialize_with</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
|
215
|
+
<span class="vi">@initializiation_args</span> <span class="o">=</span> <span class="n">args</span></pre></div>
|
216
|
+
</td>
|
217
|
+
</tr>
|
218
|
+
<tr id='section-9'>
|
219
|
+
<td class=docs>
|
220
|
+
<div class="pilwrap">
|
221
|
+
<a class="pilcrow" href="#section-9">¶</a>
|
222
|
+
</div>
|
223
|
+
<p>Remember what args we normally initialize with so we can refer to them when building shared instances.</p>
|
224
|
+
</td>
|
225
|
+
<td class=code>
|
226
|
+
<div class='highlight'><pre> <span class="n">define_singleton_method</span> <span class="ss">:initializiation_args</span> <span class="k">do</span>
|
227
|
+
<span class="vi">@initializiation_args</span>
|
228
|
+
<span class="k">end</span></pre></div>
|
229
|
+
</td>
|
230
|
+
</tr>
|
231
|
+
<tr id='section-10'>
|
232
|
+
<td class=docs>
|
233
|
+
<div class="pilwrap">
|
234
|
+
<a class="pilcrow" href="#section-10">¶</a>
|
235
|
+
</div>
|
236
|
+
<p>Create an <code>attr_accessor</code> for each one. Defaults can be provided using the Hash version { :arg => :default_value }</p>
|
237
|
+
</td>
|
238
|
+
<td class=code>
|
239
|
+
<div class='highlight'><pre> <span class="n">args</span><span class="o">.</span><span class="n">each</span> <span class="p">{</span> <span class="o">|</span><span class="n">a</span><span class="o">|</span> <span class="n">register_accessor</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="p">}</span>
|
240
|
+
|
241
|
+
<span class="n">define_method</span><span class="p">(</span><span class="ss">:initialize</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">opts</span> <span class="o">=</span> <span class="p">{}</span><span class="o">|</span> <span class="n">parse_opts</span><span class="p">(</span><span class="n">opts</span><span class="p">)</span> <span class="p">}</span></pre></div>
|
242
|
+
</td>
|
243
|
+
</tr>
|
244
|
+
<tr id='section-11'>
|
245
|
+
<td class=docs>
|
246
|
+
<div class="pilwrap">
|
247
|
+
<a class="pilcrow" href="#section-11">¶</a>
|
248
|
+
</div>
|
249
|
+
<p><code>#parse_opts</code> is responsable for getting the <code>attr_accessor</code> values prefilled. Since defaults can be specified, it
|
250
|
+
must negotiate Hashes and use the first key of the hash for the <code>attr_accessor</code>’s name.</p>
|
251
|
+
</td>
|
252
|
+
<td class=code>
|
253
|
+
<div class='highlight'><pre> <span class="n">define_method</span> <span class="ss">:parse_opts</span> <span class="k">do</span> <span class="o">|</span><span class="n">opts</span><span class="o">|</span>
|
254
|
+
<span class="vi">@rep_options</span> <span class="o">=</span> <span class="n">opts</span>
|
255
|
+
<span class="nb">self</span><span class="o">.</span><span class="n">class</span><span class="o">.</span><span class="n">initializiation_args</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">field</span><span class="o">|</span>
|
256
|
+
<span class="nb">name</span> <span class="o">=</span> <span class="n">field</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span> <span class="p">?</span> <span class="n">field</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">first</span><span class="o">.</span><span class="n">first</span> <span class="p">:</span> <span class="n">field</span>
|
257
|
+
<span class="nb">instance_variable_set</span><span class="p">(</span><span class="ss">:"@</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="ss">"</span><span class="p">,</span> <span class="n">opts</span><span class="o">[</span><span class="nb">name</span><span class="o">]</span><span class="p">)</span>
|
258
|
+
<span class="k">end</span>
|
259
|
+
<span class="k">end</span>
|
260
|
+
<span class="k">end</span></pre></div>
|
261
|
+
</td>
|
262
|
+
</tr>
|
263
|
+
<tr id='section-12'>
|
264
|
+
<td class=docs>
|
265
|
+
<div class="pilwrap">
|
266
|
+
<a class="pilcrow" href="#section-12">¶</a>
|
267
|
+
</div>
|
268
|
+
<p><code>#json_fields</code> setups up some class instance variables to remember sets of top level keys for json structures. Example:</p>
|
269
|
+
|
270
|
+
<pre><code>class A
|
271
|
+
json_fields [:one, :two, :three] => :default
|
272
|
+
end
|
273
|
+
|
274
|
+
A.json_fields(:default) # => [:one, :two, :three]
|
275
|
+
</code></pre>
|
276
|
+
|
277
|
+
<p>There is a general assumption that each top level key’s value is provided by a method of the same name on an instance
|
278
|
+
of the class. If this is not true, a Hash syntax can be used to alias to a different method name. Example:</p>
|
279
|
+
|
280
|
+
<pre><code>class A
|
281
|
+
json_fields [{ :one => :the_real_one_method }, :two, { :three => :some_other_three }] => :default
|
282
|
+
end
|
283
|
+
</code></pre>
|
284
|
+
|
285
|
+
<p>Once can also set multiple sets of fields. Example:</p>
|
286
|
+
|
287
|
+
<pre><code>class A
|
288
|
+
json_fields [:one, :two, :three] => :default
|
289
|
+
json_fields [:five, :two, :six] => :other
|
290
|
+
end
|
291
|
+
</code></pre>
|
292
|
+
|
293
|
+
<p>And all fields are returned by calling <code>#json_fields</code> with no args. Example:</p>
|
294
|
+
|
295
|
+
<pre><code>A.json_fields # => { :default => [:one, :two, :three], :other => [:five, :two, :six] }
|
296
|
+
</code></pre>
|
297
|
+
</td>
|
298
|
+
<td class=code>
|
299
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">json_fields</span><span class="p">(</span><span class="n">arg</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
|
300
|
+
<span class="k">if</span> <span class="n">arg</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span>
|
301
|
+
<span class="n">fields</span><span class="p">,</span> <span class="nb">name</span> <span class="o">=</span> <span class="n">arg</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">first</span>
|
302
|
+
<span class="vi">@json_fields</span> <span class="o">||=</span> <span class="p">{}</span>
|
303
|
+
<span class="vi">@json_fields</span><span class="o">[</span><span class="nb">name</span><span class="o">]</span> <span class="o">=</span> <span class="o">[</span><span class="n">fields</span><span class="o">].</span><span class="n">flatten</span>
|
304
|
+
<span class="k">elsif</span> <span class="n">arg</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Symbol</span><span class="p">)</span>
|
305
|
+
<span class="vi">@json_fields</span> <span class="o">||=</span> <span class="p">{}</span>
|
306
|
+
<span class="vi">@json_fields</span><span class="o">[</span><span class="n">arg</span><span class="o">]</span>
|
307
|
+
<span class="k">elsif</span> <span class="n">arg</span> <span class="o">===</span> <span class="kp">nil</span>
|
308
|
+
<span class="vi">@json_fields</span> <span class="o">||</span> <span class="p">{}</span>
|
309
|
+
<span class="k">else</span></pre></div>
|
310
|
+
</td>
|
311
|
+
</tr>
|
312
|
+
<tr id='section-13'>
|
313
|
+
<td class=docs>
|
314
|
+
<div class="pilwrap">
|
315
|
+
<a class="pilcrow" href="#section-13">¶</a>
|
316
|
+
</div>
|
317
|
+
<p>TODO: make an exception class</p>
|
318
|
+
</td>
|
319
|
+
<td class=code>
|
320
|
+
<div class='highlight'><pre> <span class="k">raise</span> <span class="s2">"You can only use a Hash to set fields, a Symbol to retrieve them, or no argument to retrieve all fields for all names"</span>
|
321
|
+
<span class="k">end</span>
|
322
|
+
<span class="k">end</span></pre></div>
|
323
|
+
</td>
|
324
|
+
</tr>
|
325
|
+
<tr id='section-14'>
|
326
|
+
<td class=docs>
|
327
|
+
<div class="pilwrap">
|
328
|
+
<a class="pilcrow" href="#section-14">¶</a>
|
329
|
+
</div>
|
330
|
+
<p><code>#flat_json_fields</code> is just a utility method to DRY up the next two methods, because their code is almost exactly the same,
|
331
|
+
it is not intended for use directly and might be confusing.</p>
|
332
|
+
</td>
|
333
|
+
<td class=code>
|
334
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">flat_json_fields</span><span class="p">(</span><span class="n">side</span> <span class="o">=</span> <span class="ss">:right</span><span class="p">)</span>
|
335
|
+
<span class="n">side_number</span> <span class="o">=</span> <span class="n">side</span> <span class="o">==</span> <span class="ss">:right</span> <span class="p">?</span> <span class="mi">1</span> <span class="p">:</span> <span class="mi">0</span>
|
336
|
+
|
337
|
+
<span class="n">json_fields</span><span class="o">.</span><span class="n">reduce</span><span class="p">(</span><span class="o">[]</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">memo</span><span class="p">,</span> <span class="p">(</span><span class="nb">name</span><span class="p">,</span> <span class="n">fields</span><span class="p">)</span><span class="o">|</span>
|
338
|
+
<span class="n">memo</span> <span class="o">+</span> <span class="n">fields</span><span class="o">.</span><span class="n">map</span> <span class="k">do</span> <span class="o">|</span><span class="n">field</span><span class="o">|</span>
|
339
|
+
<span class="k">if</span> <span class="n">field</span><span class="o">.</span><span class="n">is_a?</span><span class="p">(</span><span class="no">Hash</span><span class="p">)</span>
|
340
|
+
<span class="n">field</span><span class="o">.</span><span class="n">to_a</span><span class="o">.</span><span class="n">first</span><span class="o">[</span><span class="n">side_number</span><span class="o">]</span> <span class="c1"># [name, method_name]</span>
|
341
|
+
<span class="k">else</span>
|
342
|
+
<span class="n">field</span>
|
343
|
+
<span class="k">end</span>
|
344
|
+
<span class="k">end</span>
|
345
|
+
<span class="k">end</span><span class="o">.</span><span class="n">uniq</span>
|
346
|
+
<span class="k">end</span></pre></div>
|
347
|
+
</td>
|
348
|
+
</tr>
|
349
|
+
<tr id='section-15'>
|
350
|
+
<td class=docs>
|
351
|
+
<div class="pilwrap">
|
352
|
+
<a class="pilcrow" href="#section-15">¶</a>
|
353
|
+
</div>
|
354
|
+
<p>We need a way to get a flat, uniq'ed list of all the fields accross all field sets. This is that.</p>
|
355
|
+
</td>
|
356
|
+
<td class=code>
|
357
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">all_json_fields</span>
|
358
|
+
<span class="n">flat_json_fields</span><span class="p">(</span><span class="ss">:left</span><span class="p">)</span>
|
359
|
+
<span class="k">end</span></pre></div>
|
360
|
+
</td>
|
361
|
+
</tr>
|
362
|
+
<tr id='section-16'>
|
363
|
+
<td class=docs>
|
364
|
+
<div class="pilwrap">
|
365
|
+
<a class="pilcrow" href="#section-16">¶</a>
|
366
|
+
</div>
|
367
|
+
<p>We need a wya to get a flat, uniq'ed list of all the method names accross all field sets. This is that.</p>
|
368
|
+
</td>
|
369
|
+
<td class=code>
|
370
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">all_json_methods</span>
|
371
|
+
<span class="n">flat_json_fields</span><span class="p">(</span><span class="ss">:right</span><span class="p">)</span>
|
372
|
+
<span class="k">end</span></pre></div>
|
373
|
+
</td>
|
374
|
+
</tr>
|
375
|
+
<tr id='section-17'>
|
376
|
+
<td class=docs>
|
377
|
+
<div class="pilwrap">
|
378
|
+
<a class="pilcrow" href="#section-17">¶</a>
|
379
|
+
</div>
|
380
|
+
<p>An easy way to save on GC is to use the same instance to turn an array of objects into hashes instead
|
381
|
+
of instantiating a new object for every object in the array. Here is an example of it’s usage:</p>
|
382
|
+
|
383
|
+
<pre><code>class BookRep
|
384
|
+
initialize_with :book_model
|
385
|
+
fields :title => :default
|
386
|
+
forward :title => :book_model
|
387
|
+
end
|
388
|
+
|
389
|
+
BookRep.shared(:book_model => Book.first).to_hash # => { :title => "Moby Dick" }
|
390
|
+
BookRep.shared(:book_model => Book.last).to_hash # => { :title => "Lost Horizon" }
|
391
|
+
</code></pre>
|
392
|
+
|
393
|
+
<p>This should terrify you. If it doesn’t, then this example will:</p>
|
394
|
+
|
395
|
+
<pre><code>book1 = BookRep.shared(:book_model => Book.first)
|
396
|
+
book2 = BookRep.shared(:book_model => Book.last)
|
397
|
+
|
398
|
+
boo1.object_id === book2.object_id # => true
|
399
|
+
</code></pre>
|
400
|
+
|
401
|
+
<p><strong>It really is a shared object.</strong></p>
|
402
|
+
|
403
|
+
<p>You really shouldn’t use this method directly for anything.</p>
|
404
|
+
</td>
|
405
|
+
<td class=code>
|
406
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">shared</span><span class="p">(</span><span class="n">opts</span> <span class="o">=</span> <span class="p">{})</span>
|
407
|
+
<span class="vi">@pointer</span> <span class="o">=</span> <span class="p">(</span><span class="no">Thread</span><span class="o">.</span><span class="n">current</span><span class="o">[</span><span class="ss">:rep_shared_instances</span><span class="o">]</span> <span class="o">||=</span> <span class="p">{})</span>
|
408
|
+
<span class="vi">@pointer</span><span class="o">[</span><span class="nb">object_id</span><span class="o">]</span> <span class="o">||=</span> <span class="kp">new</span>
|
409
|
+
<span class="vi">@pointer</span><span class="o">[</span><span class="nb">object_id</span><span class="o">].</span><span class="n">reset_for_json!</span>
|
410
|
+
<span class="vi">@pointer</span><span class="o">[</span><span class="nb">object_id</span><span class="o">].</span><span class="n">parse_opts</span><span class="p">(</span><span class="n">opts</span><span class="p">)</span>
|
411
|
+
<span class="vi">@pointer</span><span class="o">[</span><span class="nb">object_id</span><span class="o">]</span>
|
412
|
+
<span class="k">end</span></pre></div>
|
413
|
+
</td>
|
414
|
+
</tr>
|
415
|
+
<tr id='section-18'>
|
416
|
+
<td class=docs>
|
417
|
+
<div class="pilwrap">
|
418
|
+
<a class="pilcrow" href="#section-18">¶</a>
|
419
|
+
</div>
|
420
|
+
<p>The fanciest thing in this entire library is this <code>#to_proc</code> method. Here is an example of it’s usage:</p>
|
421
|
+
|
422
|
+
<pre><code>class BookRep
|
423
|
+
initialize_with :book_model
|
424
|
+
fields :title => :default
|
425
|
+
forward :title => :book_model
|
426
|
+
end
|
427
|
+
|
428
|
+
Book.all.map(&BookRep) # => [{ :title => "Moby Dick" }, { :title => "Lost Horizon " }]
|
429
|
+
</code></pre>
|
430
|
+
|
431
|
+
<p>And now I will explain how it works. Any object can have a to_proc method and when you call <code>#map</code> on an
|
432
|
+
array and hand it a proc it will in turn hand each object as an argument to that proc. What I’ve decided
|
433
|
+
to do with this object is use it the options for a shared instance to make a hash.</p>
|
434
|
+
|
435
|
+
<p>Since I know the different initialization argumants from a call to <code>initialize_with</code>, I can infer by order
|
436
|
+
which object is which option. Then I can create a Hash to give to <code>parse_opts</code> through the <code>shared</code> method.
|
437
|
+
I hope that makes sense.</p>
|
438
|
+
|
439
|
+
<p>It allows for extremely clean Rails controllers like this:</p>
|
440
|
+
|
441
|
+
<pre><code>class PhotosController < ApplicationController
|
442
|
+
respond_to :json, :html
|
443
|
+
|
444
|
+
def index
|
445
|
+
@photos = Photo.paginate(page: params[:page], per_page: 20)
|
446
|
+
respond_with @photos.map(&PhotoRep)
|
447
|
+
end
|
448
|
+
|
449
|
+
def show
|
450
|
+
@photo = Photo.find(params[:id])
|
451
|
+
respond_with PhotoRep.new(photo: @photo)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
</code></pre>
|
455
|
+
|
456
|
+
</td>
|
457
|
+
<td class=code>
|
458
|
+
<div class='highlight'><pre> <span class="k">def</span> <span class="nf">to_proc</span>
|
459
|
+
<span class="nb">proc</span> <span class="p">{</span> <span class="o">|</span><span class="n">obj</span><span class="o">|</span>
|
460
|
+
<span class="n">arr</span> <span class="o">=</span> <span class="o">[</span><span class="n">obj</span><span class="o">].</span><span class="n">flatten</span>
|
461
|
+
<span class="n">init_args</span> <span class="o">=</span> <span class="vi">@initializiation_args</span><span class="o">[</span><span class="mi">0</span><span class="o">.</span><span class="n">.</span><span class="p">(</span><span class="n">arr</span><span class="o">.</span><span class="n">length</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">]</span>
|
462
|
+
<span class="n">opts</span> <span class="o">=</span> <span class="no">Hash</span><span class="o">[</span><span class="n">init_args</span><span class="o">.</span><span class="n">zip</span><span class="p">(</span><span class="n">arr</span><span class="p">)</span><span class="o">]</span>
|
463
|
+
<span class="n">shared</span><span class="p">(</span><span class="n">opts</span><span class="p">)</span><span class="o">.</span><span class="n">to_hash</span>
|
464
|
+
<span class="p">}</span>
|
465
|
+
<span class="k">end</span>
|
466
|
+
<span class="k">end</span>
|
467
|
+
|
468
|
+
<span class="k">module</span> <span class="nn">HashieSupport</span>
|
469
|
+
<span class="k">def</span> <span class="nf">to_hash</span><span class="p">(</span><span class="nb">name</span> <span class="o">=</span> <span class="ss">:default</span><span class="p">)</span>
|
470
|
+
<span class="no">Hashie</span><span class="o">::</span><span class="no">Mash</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="k">super</span><span class="p">)</span>
|
471
|
+
<span class="k">end</span>
|
472
|
+
<span class="k">end</span>
|
473
|
+
<span class="k">end</span></pre></div>
|
474
|
+
</td>
|
475
|
+
</tr>
|
476
|
+
</table>
|
477
|
+
</div>
|
478
|
+
</body>
|