post_json 1.0.11 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fb5fea92951fd8ec7e82b70905b2ae78c5e28071
4
- data.tar.gz: 412eaafc6a0b9c802dd02116898dc173f194d454
3
+ metadata.gz: 52a70dfcec7f3478c9b2fab7e465c5b33c97229b
4
+ data.tar.gz: 7d302f9d087bc00360e203320e0afd5b64e3eb1d
5
5
  SHA512:
6
- metadata.gz: 4874f99abaec969d3544de9ad4e1db5f651a16d50625ecb9112e2913b2048e7c6183b5ab154efcec482a1d0a6ab957a37a7a8cb22ec33417db76e548e4f16fe0
7
- data.tar.gz: 9fc7845cf1082ed2f0670d43aa8ab07ab27780340122d0f3d139b2e2c9f8a1f8d0aae686f073a4e2f44e8191b05f743cdecf98a8efe94f08ff86462ccc6791ff
6
+ metadata.gz: b17682fa4780f53680fc7c7c55bb423974208d79356ba6ec70a9ab2ebc16ce9c83a7cc60b6101b71b507797434d84ef33cf9c818b40bbeed268c4b7045d69782
7
+ data.tar.gz: 99bbe826b5aab7659aaa74fa2e8413023b398940b4bf50aa98ed540989d339a2488c9e7300fb0af0c35cebcbcb4c37dc7d54d7180f41c5f4509e486ce476d4b2
data/README.md CHANGED
@@ -1,41 +1,38 @@
1
1
  # Welcome to PostJson
2
2
 
3
- PostJson is everything you expect of ActiveRecord and PostgreSQL, but with the dynamic nature of document databases
4
- (free as a bird - no schemas).
3
+ PostJson is everything you expect of ActiveRecord and PostgreSQL, with the added power and dynamic nature of a document database (Free as a bird! No schemas!).
5
4
 
6
- PostJson take full advantage of PostgreSQL 9.2+ support for JavaScript (Google's V8 engine). We started the work on
7
- PostJson, because we love document databases and PostgreSQL. PostJson combine features of Ruby, ActiveRecord and
8
- PostgreSQL to provide a great document database.
5
+ PostJson combines features of Ruby, ActiveRecord and PostgreSQL to provide a great document database by taking advantage of PostgreSQL 9.2+ support for JavaScript (Google's V8 engine). We started the work on PostJson, because we love document databases **and** PostgreSQL.
9
6
 
10
- See example of how we use PostJson as part of <a href="https://github.com/webnuts/jumpstarter">Jumpstarter</a>.
7
+ See the example of how we use PostJson as part of [Jumpstarter](https://github.com/webnuts/jumpstarter).
11
8
 
12
9
 
13
- ## Getting started
10
+ ## Installation
14
11
 
15
- ### Add the gem to your Ruby on Rails application `Gemfile`:
12
+ Add the gem to your `Gemfile`:
16
13
 
17
14
  gem 'post_json'
18
-
19
- ### At the command prompt, install the gem, run the generator, and migrate the db:
20
15
 
21
- bundle install
22
- rails g post_json:install
23
- rake db:migrate
16
+ Then:
17
+
18
+ $ bundle install
19
+
20
+ Run the generator and migrate the db:
21
+
22
+ $ rails g post_json:install
23
+ $ rake db:migrate
24
24
 
25
25
  That's it!
26
26
 
27
27
  (See POSTGRESQL_INSTALL.md if you need the install instructions for PostgreSQL with PLV8)
28
28
 
29
- ## Using it
30
-
31
- You should feel home right away, if you already know ActiveRecord. PostJson try hard to respect the ActiveRecord
32
- API, so methods work and do as you would expect from ActiveRecord.
29
+ ## Usage
33
30
 
34
- PostJson is all about collections. All models represent a collection.
31
+ PostJson also tries hard to respect the ActiveRecord API, so, if you have experience with ActiveRecord, the model methods work as you would expect.
35
32
 
36
- Also, __notice you don't have to define model attributes anywhere!__
33
+ ### Model
37
34
 
38
- ### Lets create your first model.
35
+ All PostJson models represent a collection.
39
36
 
40
37
  ```ruby
41
38
  class Person < PostJson::Collection["people"]
@@ -43,12 +40,11 @@ end
43
40
 
44
41
  me = Person.create(name: "Jacob")
45
42
  ```
43
+
44
+ __Notice you don't have to define model attributes anywhere!__
46
45
 
47
- As you can see it look the same as ActiveRecord, except you define `PostJson::Collection["people"]` instead of
48
- `ActiveRecord::Base`.
49
-
50
- `Person` can do the same as any model class inheriting `ActiveRecord::Base`.
51
-
46
+ As you can see, this is very similar to a standard ActiveRecord model. `PostJson::Collection["people"]` inherits from `PostJson::Base`, which, in turn, inherits from `ActiveRecord::Base`. This is part of the reason the `Person` model will seem so familiar.
47
+
52
48
  You can also skip the creation of a class:
53
49
 
54
50
  ```ruby
@@ -56,7 +52,9 @@ people = PostJson::Collection["people"]
56
52
  me = people.create(name: "Jacob")
57
53
  ```
58
54
 
59
- ### Adding some validation:
55
+ ### Validations
56
+
57
+ Use standard `ActiveRecord` validations in your models:
60
58
 
61
59
  ```ruby
62
60
  class Person < PostJson::Collection["people"]
@@ -64,20 +62,13 @@ class Person < PostJson::Collection["people"]
64
62
  end
65
63
  ```
66
64
 
67
- PostJson::Collection["people"] returns a class, which is based on `PostJson::Base`, which is based on
68
- `ActiveRecord::Base`. So its the exact same validation as you may know.
69
-
70
65
  Read the <a href="http://guides.rubyonrails.org/active_record_validations.html" target="_blank">Rails guide about validation</a> if you need more information.
71
66
 
72
- ### Lets create a more complex document and do a query:
67
+ ### Querying
73
68
 
74
69
  ```ruby
75
70
  me = Person.create(name: "Jacob", details: {age: 33})
76
- ```
77
71
 
78
- Now we can make a query and get the document:
79
-
80
- ```ruby
81
72
  # PostJson supports filtering on nested attributes
82
73
  also_me_1 = Person.where(details: {age: 33}).first
83
74
  also_me_2 = Person.where("details.age" => 33).first
@@ -89,9 +80,7 @@ also_me_3 = Person.where("function(doc) { return doc.details.age == 33; }").firs
89
80
  also_me_4 = Person.where("json_details.age = ?", 33).first
90
81
  ```
91
82
 
92
- ### Accessing attributes:
93
-
94
- Like you would expect with ActiveRecord:
83
+ ### Accessing attributes
95
84
 
96
85
  ```ruby
97
86
  person = Person.create(name: "Jacob")
@@ -115,22 +104,55 @@ puts person.name_changed? # => false
115
104
  puts person.name_change # => nil
116
105
  ```
117
106
 
118
- ### Introduction to select and selectors.
107
+ ### Transformation with `select`
119
108
 
120
- Sometimes we need a transformed version of documents. This is very easy with `select`
109
+ The `select` method allows you to transform a collection of documents into an array of hashes that contain only the attributes you want. The hash passed to `select` maps keys to selectors of arbitrary depth.
110
+
111
+ > In this example we only want the 'name' and 'age' attributes from the `Person` but 'age' is nested under 'details'.
121
112
 
122
113
  ```ruby
114
+ # create a person with age nested under details
123
115
  me = Person.create(name: "Jacob", details: {age: 33})
124
116
 
117
+ # the dot (.) signifies that the selector is looking for a nested attribute
125
118
  other_me = Person.limit(1).select({name: "name", age: "details.age"}).first
126
119
 
127
120
  puts other_me
128
121
  # => {name: "Jacob", age: 33}
122
+ ```
123
+
124
+ ### Dates
125
+
126
+ Dates are not natively supported by JSON. This is why dates are persisted as strings.
127
+
128
+ ```ruby
129
+ me = Person.create(name: "Jacob", nested: {now: Time.now})
130
+ puts me.attributes
131
+ # => {"name"=>"Jacob", "nested"=>{"now"=>2013-10-24 16:15:05 +0200}, "id"=>"fb9ef4bb-1441-4392-a95d-6402f72829db", "version"=>1, "created_at"=>Thu, 24 Oct 2013 14:15:05 UTC +00:00, "updated_at"=>Thu, 24 Oct 2013 14:15:05 UTC +00:00}
132
+ ```
133
+
134
+ Lets reload it and see how it is stored:
129
135
 
136
+ ```ruby
137
+ me.reload
138
+ puts me.attributes
139
+ # => {"name"=>"Jacob", "nested"=>{"now"=>"2013-10-24T14:15:05.783Z"}, "id"=>"fb9ef4bb-1441-4392-a95d-6402f72829db", "version"=>1, "created_at"=>"2013-10-24T14:15:05.831Z", "updated_at"=>"2013-10-24T14:15:05.831Z"}
130
140
  ```
131
- `select` takes a hash as argument and return an array of hashes. The value of each key/value pair in the hash argument is a selector. Selectors can point at attributes at root level, but also nested attributes. Each level of attributes is seperated with a dot (.).
132
141
 
133
- ### Check out the initializer at `config/initializers/post_json.rb`
142
+ PostJson will serialize Time and DateTime to format `strftime('%Y-%m-%dT%H:%M:%S.%LZ')` when persisting documents.
143
+
144
+ PostJson will also parse an attribute's value to a `Time` object, if the value is a string and matches the format.
145
+
146
+ ### Supported methods
147
+
148
+ all, any?, blank?, count, delete, delete_all, destroy, destroy_all, each, empty?, except, exists?, find, find_by,
149
+ find_by!, find_each, find_in_batches, first, first!, first_or_create, first_or_initialize, ids, last, limit, load,
150
+ many?, offset, only, order, pluck, reorder, reverse_order, select, size, take, take!, to_a, to_sql, and where.
151
+
152
+ We also added `page(page, per_page)`, which translate into `offset((page-1)*per_page).limit(per_page)`.
153
+
154
+
155
+ ## Configuration Options
134
156
 
135
157
  ```ruby
136
158
  PostJson.setup "people" do |collection|
@@ -144,14 +166,7 @@ PostJson.setup "people" do |collection|
144
166
  end
145
167
  ```
146
168
 
147
- #### All of the following methods are supported
148
-
149
- all, any?, blank?, count, delete, delete_all, destroy, destroy_all, each, empty?, except, exists?, find, find_by,
150
- find_by!, find_each, find_in_batches, first, first!, first_or_create, first_or_initialize, ids, last, limit, load,
151
- many?, offset, only, order, pluck, reorder, reverse_order, select, size, take, take!, to_a, to_sql, and where.
152
-
153
- We also added `page(page, per_page)`, which translate into `offset((page-1)*per_page).limit(per_page)`.
154
-
169
+ For a Rails project this configuration could go in an initializer (`config/initializers/post_json.rb`).
155
170
 
156
171
  ## Performance
157
172
 
@@ -163,7 +178,7 @@ test_model = PostJson::Collection["test"]
163
178
  content = test_model.last.content
164
179
 
165
180
  result = test_model.where(content: content).count
166
- # Rails debug tells me the duration was 975.5ms
181
+ # Rails debug duration was 975.5ms
167
182
  ```
168
183
 
169
184
  The duration was above 50ms as you can see.
@@ -174,7 +189,7 @@ Now lets see how the performance will be on the second and future queries using
174
189
 
175
190
  ```ruby
176
191
  result = test_model.where(content: content).count
177
- # Rails debug tells me the duration was 1.5ms
192
+ # Rails debug duration was 1.5ms
178
193
  ```
179
194
 
180
195
  It shows PostgreSQL as a document database combined with indexing has great performance out of the box.
@@ -183,45 +198,46 @@ See the next section about "Dynamic Indexes" for details.
183
198
 
184
199
  ## Dynamic Indexes
185
200
 
186
- Most applications do the same queries over and over again. This is why we think it is useful, if PostJson create indexes on slow queries.
201
+ PostJson will measure the duration of each `SELECT` query and instruct PostgreSQL to create an index,
202
+ if the query duration is above a specified threshold. This feature is called `Dynamic Index`. Since most
203
+ applications perform the same queries over and over again we think you'll find this useful.
187
204
 
188
- So we have created a feature we call `Dynamic Index`. It will automatically create indexes on slow queries,
189
- so queries speed up considerably.
205
+ Each collection (like `PostJson::Collection["people"]` above) has two index attributes:
190
206
 
191
- PostJson will measure the duration of each `SELECT` query and instruct PostgreSQL to create an Index,
192
- if the query duration is above a specified threshold.
207
+ * **use_dynamic_index** (default: true)
208
+ * **create_dynamic_index_milliseconds_threshold** (default: 50)
193
209
 
194
- Each collection (like PostJson::Collection["people"]) have attribute `use_dynamic_index` (which is true by default) and
195
- attribute `create_dynamic_index_milliseconds_threshold` (which is 50 by default).
210
+ ### Example
196
211
 
197
- Lets say that you execute the following query and the duration is above the threshold of 50 milliseconds:
212
+ ```ruby
213
+ PostJson::Collection["people"].where(name: "Jacob").count
198
214
 
199
- `PostJson::Collection["people"].where(name: "Jacob").count`
215
+ # => query duration > 50ms
216
+ ```
200
217
 
201
- PostJson will create (unless it already exists) an Index on `name` behind the scenes. The next time
202
- you execute a query with `name` the performance will be much improved.
218
+ PostJson will check for an index on `name` and create it if it doesn't exist.
203
219
 
204
- You can adjust the settings:
220
+ ### Index configuration
205
221
 
206
222
  ```ruby
207
223
  class Person < PostJson::Collection["people"]
208
224
  self.create_dynamic_index_milliseconds_threshold = 75
209
225
  end
226
+ ```
210
227
 
211
- # Or you can do:
228
+ or:
212
229
 
230
+ ```ruby
213
231
  PostJson::Collection["people"].create_dynamic_index_milliseconds_threshold = 75
214
-
215
- # Now indexes are only created if queries are slower than 75 milliseconds.
216
232
  ```
217
233
 
218
- You might already know this about User Interfaces, but it is usual considered good practice if auto-complete responses are served to the user within 100 milliseconds. Other results are usual okay within 500 milliseconds. So leave room for application processing and network delay.
234
+ ### WARNING
219
235
 
220
- Do not set create_dynamic_index_milliseconds_threshold too low as PostJson will try to create an index for every query. Like a threshold of 1 millisecond will be less than the duration of almost all queries.
236
+ Do not set the dynamic index threshold too low as PostJson will try to create an index for every query. A threshold of 1 millisecond would be less than the duration of almost all queries.
221
237
 
222
238
  ## Primary Keys
223
239
 
224
- PostJson assign UUID as primary key (id):
240
+ PostJson assigns UUID as primary key (id):
225
241
 
226
242
  ```ruby
227
243
  me = Person.create(name: "Jacob")
@@ -230,13 +246,13 @@ puts me.id
230
246
  # => "297a2500-a456-459b-b3e9-e876f59602c2"
231
247
  ```
232
248
 
233
- But you also set the primary key yourself:
249
+ or you can set it directly:
234
250
 
235
251
  ```ruby
236
252
  john_doe = Person.create(id: "John Doe")
237
253
  ```
238
254
 
239
- Notice the primary key is downcased when doing a query or finding records:
255
+ The primary key is downcased when doing a query or finding records:
240
256
 
241
257
  ```ruby
242
258
  found = Person.where(id: "JOhN DoE").first
@@ -110,15 +110,15 @@ module PostJson
110
110
  def __doc__body_convert_attribute_type(attribute_name, value)
111
111
  case value
112
112
  when /^[0-9]{4}-[0-1][0-9]-[0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]\.[0-9]{3}Z$/
113
- Time.parse(value).in_time_zone
113
+ Time.zone.parse(value)
114
114
  when Hash
115
115
  value.inject(HashWithIndifferentAccess.new) do |result, (key, value)|
116
- result[key] = convert_document_attribute_type("#{attribute_name}.#{key}", value)
116
+ result[key] = __doc__body_convert_attribute_type("#{attribute_name}.#{key}", value)
117
117
  result
118
118
  end
119
119
  when Array
120
120
  value.map.with_index do |array_value, index|
121
- convert_document_attribute_type("#{attribute_name}[#{index}]", array_value)
121
+ __doc__body_convert_attribute_type("#{attribute_name}[#{index}]", array_value)
122
122
  end
123
123
  else
124
124
  value
@@ -173,11 +173,11 @@ module PostJson
173
173
  method_name
174
174
  end
175
175
 
176
- if attribute_name.in?(attribute_names) == false
176
+ if attribute_name.in?(attribute_names) || self.class.column_names.include?(attribute_name) || super_respond_to?(attribute_name.to_sym)
177
+ super
178
+ else
177
179
  self.class.define_attribute_accessor(attribute_name)
178
180
  send(method_symbol, *args)
179
- else
180
- super
181
181
  end
182
182
  end
183
183
 
@@ -253,6 +253,54 @@ module PostJson
253
253
  end
254
254
  RUBY
255
255
  end
256
+
257
+ def convert_attribute_value_before_save(primary_key, selector, value)
258
+ case value
259
+ when Time
260
+ value.in_time_zone
261
+ when DateTime
262
+ value.to_time.in_time_zone
263
+ else
264
+ value
265
+ end
266
+ end
267
+
268
+ def convert_document_hash_before_save(primary_key, document_hash, prefix = nil)
269
+ if document_hash
270
+ document_hash.inject(HashWithIndifferentAccess.new) do |result_hash, (key, value)|
271
+ selector = if prefix
272
+ "#{prefix}.#{key}"
273
+ else
274
+ key
275
+ end
276
+ case value
277
+ when Hash
278
+ result_hash[key] = convert_document_hash_before_save(primary_key, value, selector)
279
+ when Array
280
+ result_hash[key] = convert_document_array_before_save(primary_key, value, selector)
281
+ else
282
+ result_hash[key] = convert_attribute_value_before_save(primary_key, selector, value)
283
+ end
284
+ result_hash
285
+ end
286
+ end
287
+ end
288
+
289
+ def convert_document_array_before_save(primary_key, document_array, prefix = nil)
290
+ if document_array
291
+ document_array.map.with_index do |value, index|
292
+ selector = "#{prefix}[#{index}]"
293
+ case value
294
+ when Hash
295
+ convert_document_hash_before_save(primary_key, value, selector)
296
+ when Array
297
+ convert_document_array_before_save(primary_key, value, selector)
298
+ else
299
+ convert_attribute_value_before_save(primary_key, selector, value)
300
+ end
301
+ end
302
+ end
303
+ end
256
304
  end
257
305
 
258
306
  protected
@@ -280,10 +328,11 @@ module PostJson
280
328
  end
281
329
 
282
330
  if self.class.persisted_settings.use_timestamps
283
- __local__current_time = Time.zone.now.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
331
+ __local__current_time = Time.zone.now
284
332
  __doc__body_write_attribute(self.class.persisted_settings.created_at_attribute_name, __local__current_time)
285
333
  __doc__body_write_attribute(self.class.persisted_settings.updated_at_attribute_name, __local__current_time)
286
334
  end
335
+
287
336
  super
288
337
  end
289
338
 
@@ -302,10 +351,20 @@ module PostJson
302
351
  end
303
352
 
304
353
  if self.class.persisted_settings.use_timestamps && __doc__body_attribute_changed?(self.class.persisted_settings.updated_at_attribute_name)
305
- __local__current_time = Time.zone.now.strftime('%Y-%m-%dT%H:%M:%S.%LZ')
354
+ __local__current_time = Time.zone.now
306
355
  __doc__body_write_attribute(self.class.persisted_settings.updated_at_attribute_name, __local__current_time)
307
356
  end
308
357
  super
309
358
  end
359
+
360
+ def typecasted_attribute_value(name)
361
+ result = super
362
+ name = name.to_s
363
+ if name == '__doc__body'
364
+ self.class.convert_document_hash_before_save(self[self.primary_key], result)
365
+ else
366
+ result
367
+ end
368
+ end
310
369
  end
311
370
  end
@@ -1,3 +1,3 @@
1
1
  module PostJson
2
- VERSION = "1.0.11"
2
+ VERSION = "1.0.12"
3
3
  end
@@ -433,4 +433,52 @@ describe "Base model" do
433
433
  it { subject.find("John").age.should == 25 }
434
434
  end
435
435
  end
436
- end
436
+
437
+ context "dates" do
438
+ let(:time) { Time.now }
439
+ let(:time_in_zone) { time.in_time_zone }
440
+ let(:date_time) { time.to_datetime }
441
+ let(:time_result) { Time.parse(time_in_zone.strftime('%Y-%m-%dT%H:%M:%S.%LZ')) }
442
+ let(:time_hash) { { 'time' => time, 'time_in_zone' => time_in_zone, 'date_time' => date_time } }
443
+ let(:time_array) { [time, time_in_zone, date_time] }
444
+ let(:record) { PostJson::Collection["dates"].new(time: time, time_in_zone: time_in_zone, date_time: date_time, time_hash: time_hash, time_array: time_array) }
445
+
446
+ context "before save" do
447
+ subject { record }
448
+
449
+ its(:time) { should == time }
450
+ its(:time_in_zone) { should == time_in_zone }
451
+ its(:date_time) { should == date_time }
452
+ its(:time_hash) { should == time_hash }
453
+ its(:time_array) { should == time_array }
454
+ end
455
+
456
+ context "after save" do
457
+ subject { record }
458
+
459
+ before do
460
+ subject.save!
461
+ end
462
+
463
+ its(:time) { should == time }
464
+ its(:time_in_zone) { should == time_in_zone }
465
+ its(:date_time) { should == date_time }
466
+ its(:time_hash) { should == time_hash }
467
+ its(:time_array) { should == time_array }
468
+
469
+ context "and reload" do
470
+
471
+
472
+ before do
473
+ subject.reload
474
+ end
475
+
476
+ its(:time) { should == time_result }
477
+ its(:time_in_zone) { should == time_result }
478
+ its(:date_time) { should == time_result }
479
+ its(:time_hash) { should == { 'time' => time_result, 'time_in_zone' => time_result, 'date_time' => time_result } }
480
+ its(:time_array) { should == [time_result]*3 }
481
+ end
482
+ end
483
+ end
484
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: post_json
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.11
4
+ version: 1.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacob Madsen and Martin Thoegersen