post_json 1.0.11 → 1.0.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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