rep 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 and it is endowed with a `#to_json` method,
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
- json_fields [:url, :title, :exif, :location, :user] => :default
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
- json_fields [:name, :email, :location] => :default
95
- json_fields [:id, :admin].concat(json_fields(:default)) => :admin
88
+ fields [:name, :email, :location] => :default
89
+ fields [:id, :admin].concat(fields(:default)) => :admin
96
90
 
97
- forward json_fields(:admin) => :user
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 class)
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
- task default: 'test'
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'
@@ -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 &hellip;
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">&#182;</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>&rsquo;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">&#182;</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] =&gt; :@array
59
+ def initialize(array = [])
60
+ @array = array
61
+ end
62
+ end
63
+
64
+ A.new([1,2,3]).length # =&gt; 3
65
+ A.new([1,2,3]).first # =&gt; 1
66
+ </code></pre>
67
+ </td>
68
+ <td class=code>
69
+ <div class='highlight'><pre><span class="nb">require</span> <span class="s1">&#39;forwardable&#39;</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">&#182;</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">&#39;json&#39;</span>
81
+
82
+ <span class="nb">require</span> <span class="s1">&#39;rep/version&#39;</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">&#182;</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">&lt;&lt;</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">&#182;</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">:&quot;@</span><span class="si">#{</span><span class="n">method_name</span><span class="si">}</span><span class="ss">&quot;</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">&#182;</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 =&gt; :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 =&gt; :foo }.to_a # =&gt; [[:one, :foo]]
147
+ </code></pre>
148
+
149
+ <p>Right now it will raise if either a field doesn&rsquo;t have a method to provide it&rsquo;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">=&gt;</span> <span class="n">e</span>
160
+ <span class="n">message</span> <span class="o">=</span> <span class="s2">&quot;There is no method named &#39;</span><span class="si">#{</span><span class="n">method_name</span><span class="si">}</span><span class="s2">&#39; for the class &#39;</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">&#39; for the &#39;</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">&#39; 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">&quot;</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">&quot;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">&quot;</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">&#182;</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 =&gt; "No Name"
186
+ end
187
+
188
+ A.new.name # =&gt; "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">:&quot;@</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="ss">&quot;</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">&#182;</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&rsquo;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">&#182;</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">&#182;</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">&#182;</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>&rsquo;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">:&quot;@</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="ss">&quot;</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">&#182;</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] =&gt; :default
272
+ end
273
+
274
+ A.json_fields(:default) # =&gt; [:one, :two, :three]
275
+ </code></pre>
276
+
277
+ <p>There is a general assumption that each top level key&rsquo;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 =&gt; :the_real_one_method }, :two, { :three =&gt; :some_other_three }] =&gt; :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] =&gt; :default
289
+ json_fields [:five, :two, :six] =&gt; :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 # =&gt; { :default =&gt; [:one, :two, :three], :other =&gt; [: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">&#182;</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">&quot;You can only use a Hash to set fields, a Symbol to retrieve them, or no argument to retrieve all fields for all names&quot;</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">&#182;</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">&#182;</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">&#182;</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">&#182;</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&rsquo;s usage:</p>
382
+
383
+ <pre><code>class BookRep
384
+ initialize_with :book_model
385
+ fields :title =&gt; :default
386
+ forward :title =&gt; :book_model
387
+ end
388
+
389
+ BookRep.shared(:book_model =&gt; Book.first).to_hash # =&gt; { :title =&gt; "Moby Dick" }
390
+ BookRep.shared(:book_model =&gt; Book.last).to_hash # =&gt; { :title =&gt; "Lost Horizon" }
391
+ </code></pre>
392
+
393
+ <p>This should terrify you. If it doesn&rsquo;t, then this example will:</p>
394
+
395
+ <pre><code>book1 = BookRep.shared(:book_model =&gt; Book.first)
396
+ book2 = BookRep.shared(:book_model =&gt; Book.last)
397
+
398
+ boo1.object_id === book2.object_id # =&gt; true
399
+ </code></pre>
400
+
401
+ <p><strong>It really is a shared object.</strong></p>
402
+
403
+ <p>You really shouldn&rsquo;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">&#182;</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&rsquo;s usage:</p>
421
+
422
+ <pre><code>class BookRep
423
+ initialize_with :book_model
424
+ fields :title =&gt; :default
425
+ forward :title =&gt; :book_model
426
+ end
427
+
428
+ Book.all.map(&amp;BookRep) # =&gt; [{ :title =&gt; "Moby Dick" }, { :title =&gt; "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&rsquo;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 &lt; 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(&amp;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>