mongoid-cached-json 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +18 -3
- data/lib/mongoid-cached-json.rb +5 -1
- data/lib/mongoid-cached-json/array.rb +22 -0
- data/lib/mongoid-cached-json/cached_json.rb +102 -38
- data/lib/mongoid-cached-json/hash.rb +23 -0
- data/lib/mongoid-cached-json/key_references.rb +26 -0
- data/lib/mongoid-cached-json/mongoid_criteria.rb +24 -0
- data/lib/mongoid-cached-json/version.rb +1 -1
- metadata +23 -3
data/README.md
CHANGED
@@ -10,9 +10,8 @@ Using `Mongoid::CachedJson` we were able to cut our JSON API average response ti
|
|
10
10
|
Resources
|
11
11
|
---------
|
12
12
|
|
13
|
-
* [Need Help?](http://groups.google.com/group/mongoid-cached-json)
|
13
|
+
* [Need Help? Google Group](http://groups.google.com/group/mongoid-cached-json)
|
14
14
|
* [Source Code](http://github.com/dblock/mongoid-cached-json)
|
15
|
-
* [Travis CI](https://secure.travis-ci.org/dblock/mongoid-cached-json)
|
16
15
|
|
17
16
|
Quickstart
|
18
17
|
----------
|
@@ -231,10 +230,26 @@ describe "updating a person" do
|
|
231
230
|
end
|
232
231
|
```
|
233
232
|
|
233
|
+
Performance
|
234
|
+
-----------
|
235
|
+
|
236
|
+
This implements two interesting optimizations.
|
237
|
+
|
238
|
+
### Bulk Reference Resolving w/ Local Store
|
239
|
+
|
240
|
+
Consider an array of Mongoid instances, each with numerous references to other objects. It's typical to see such instances reference the same object. `Mongoid::CachedJson` first collects all JSON references, then resolves them after suppressing duplicates. This significantly reduces the number of cache queries.
|
241
|
+
|
242
|
+
### Fetching Cache Data in Bulk
|
243
|
+
|
244
|
+
Various cache stores, including Memcached, support bulk read operations. The [Dalli](https://github.com/mperham/dalli) gem exposes this via the `read_multi` method. `Mongoid::CachedJson` will always invoke `read_multi` where available, which significantly reduces the number of network roundtrips to the cache servers.
|
245
|
+
|
234
246
|
Contributing
|
235
247
|
------------
|
236
248
|
|
237
|
-
Fork the project.
|
249
|
+
* Fork the project.
|
250
|
+
* Make your feature addition or bug fix with tests.
|
251
|
+
* Don't forget to update `CHANGELOG.md`.
|
252
|
+
* Send a pull request. Bonus points for topic branches.
|
238
253
|
|
239
254
|
Copyright and License
|
240
255
|
---------------------
|
data/lib/mongoid-cached-json.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
require 'mongoid'
|
3
2
|
require 'active_support/concern'
|
3
|
+
require 'mongoid'
|
4
|
+
require 'mongoid-cached-json/key_references'
|
5
|
+
require 'mongoid-cached-json/array'
|
6
|
+
require 'mongoid-cached-json/hash'
|
7
|
+
require 'mongoid-cached-json/mongoid_criteria'
|
4
8
|
require 'mongoid-cached-json/version'
|
5
9
|
require 'mongoid-cached-json/config'
|
6
10
|
require 'mongoid-cached-json/cached_json'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
def as_json_partial(options = {})
|
4
|
+
json_keys = nil
|
5
|
+
json = map do |i|
|
6
|
+
if i.respond_to?(:as_json_partial)
|
7
|
+
partial_json_keys, json = i.as_json_partial(options)
|
8
|
+
json_keys = json_keys ? json_keys.merge_set(partial_json_keys) : partial_json_keys
|
9
|
+
json
|
10
|
+
else
|
11
|
+
i.as_json(options)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
[ json_keys, json ]
|
15
|
+
end
|
16
|
+
|
17
|
+
def as_json(options = {})
|
18
|
+
json_keys, json = as_json_partial(options)
|
19
|
+
Mongoid::CachedJson.materialize_json_references_with_read_multi(json_keys, json)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
module Mongoid
|
3
3
|
module CachedJson
|
4
4
|
extend ActiveSupport::Concern
|
5
|
-
|
5
|
+
|
6
6
|
included do
|
7
7
|
class_attribute :all_json_properties
|
8
8
|
class_attribute :all_json_versions
|
@@ -10,9 +10,9 @@ module Mongoid
|
|
10
10
|
class_attribute :cached_json_reference_defs
|
11
11
|
class_attribute :hide_as_child_json_when
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
module ClassMethods
|
15
|
-
|
15
|
+
|
16
16
|
# Define JSON fields for a class.
|
17
17
|
#
|
18
18
|
# @param [ hash ] defs JSON field definition.
|
@@ -44,7 +44,27 @@ module Mongoid
|
|
44
44
|
end
|
45
45
|
before_save :expire_cached_json
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
|
+
# Materialize a cached JSON within a cache block.
|
49
|
+
def materialize_cached_json(clazz, id, object_reference, options)
|
50
|
+
is_top_level_json = options[:is_top_level_json] || false
|
51
|
+
object_reference = clazz.where({ :_id => id }).first if !object_reference
|
52
|
+
if !object_reference || (!is_top_level_json && options[:properties] != :all && clazz.hide_as_child_json_when.call(object_reference))
|
53
|
+
nil
|
54
|
+
else
|
55
|
+
Hash[clazz.cached_json_field_defs[options[:properties]].map do |field, definition|
|
56
|
+
# version match
|
57
|
+
versions = ([definition[:version] ] | Array(definition[:versions])).compact
|
58
|
+
next unless versions.empty? or versions.include?(options[:version])
|
59
|
+
json_value = (definition[:definition].is_a?(Symbol) ? object_reference.send(definition[:definition]) : definition[:definition].call(object_reference))
|
60
|
+
Mongoid::CachedJson.config.transform.each do |t|
|
61
|
+
json_value = t.call(field, definition, json_value)
|
62
|
+
end
|
63
|
+
[field, json_value]
|
64
|
+
end]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
48
68
|
# Given an object definition in the form of either an object or a class, id pair,
|
49
69
|
# grab the as_json representation from the cache if possible, otherwise create
|
50
70
|
# the as_json representation by loading the object from the database. For any
|
@@ -61,36 +81,29 @@ module Mongoid
|
|
61
81
|
object_reference = nil
|
62
82
|
clazz, id = object_def[:clazz], object_def[:id]
|
63
83
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
else
|
69
|
-
Hash[clazz.cached_json_field_defs[options[:properties]].map do |field, definition|
|
70
|
-
# version match
|
71
|
-
versions = ([definition[:version] ] | Array(definition[:versions])).compact
|
72
|
-
next unless versions.empty? or versions.include?(options[:version])
|
73
|
-
json_value = (definition[:definition].is_a?(Symbol) ? object_reference.send(definition[:definition]) : definition[:definition].call(object_reference))
|
74
|
-
Mongoid::CachedJson.config.transform.each do |t|
|
75
|
-
json_value = t.call(field, definition, json_value)
|
76
|
-
end
|
77
|
-
[field, json_value]
|
78
|
-
end]
|
79
|
-
end
|
80
|
-
end
|
84
|
+
key = self.cached_json_key(options, clazz, id)
|
85
|
+
json = { :_ref => { :_clazz => self, :_key => key, :_materialize_cached_json => [ clazz, id, object_reference, options ] }}
|
86
|
+
keys = KeyReferences.new
|
87
|
+
keys.set_and_add(key, json)
|
81
88
|
reference_defs = clazz.cached_json_reference_defs[options[:properties]]
|
82
89
|
if !reference_defs.empty?
|
83
90
|
object_reference = clazz.where({ :_id => id }).first if !object_reference
|
84
91
|
if object_reference and (is_top_level_json or options[:properties] == :all or !clazz.hide_as_child_json_when.call(object_reference))
|
85
|
-
json
|
92
|
+
json.merge!(Hash[reference_defs.map do |field, definition|
|
86
93
|
json_properties_type = (options[:properties] == :all) ? :all : :short
|
87
|
-
|
94
|
+
reference_keys, reference = clazz.resolve_json_reference(options.merge({ :properties => json_properties_type, :is_top_level_json => false}), object_reference, field, definition)
|
95
|
+
if (reference.is_a?(Hash) && ref = reference[:_ref])
|
96
|
+
ref[:_parent] = json
|
97
|
+
ref[:_field] = field
|
98
|
+
end
|
99
|
+
keys.merge_set(reference_keys)
|
100
|
+
[field, reference]
|
88
101
|
end])
|
89
102
|
end
|
90
103
|
end
|
91
|
-
json
|
104
|
+
[ keys, json ]
|
92
105
|
end
|
93
|
-
|
106
|
+
|
94
107
|
# Cache key.
|
95
108
|
def cached_json_key(options, cached_class, cached_id)
|
96
109
|
base_class_name = cached_class.collection_name.to_s.singularize.camelize
|
@@ -102,6 +115,7 @@ module Mongoid
|
|
102
115
|
# be able to load the as_json representation from the cache without even getting the
|
103
116
|
# model from the database and materializing it through Mongoid. We'll try to do this first.
|
104
117
|
def resolve_json_reference(options, object, field, reference_def)
|
118
|
+
keys = nil
|
105
119
|
reference_json = nil
|
106
120
|
if reference_def[:metadata]
|
107
121
|
key = reference_def[:metadata].key.to_sym
|
@@ -112,10 +126,10 @@ module Mongoid
|
|
112
126
|
end
|
113
127
|
if reference_def[:metadata].relation == Mongoid::Relations::Referenced::ManyToMany
|
114
128
|
reference_json = object.send(key).map do |id|
|
115
|
-
materialize_json(options, { :clazz => clazz, :id => id })
|
129
|
+
materialize_keys, json = materialize_json(options, { :clazz => clazz, :id => id })
|
130
|
+
keys = keys ? keys.merge_set(materialize_keys) : materialize_keys
|
131
|
+
json
|
116
132
|
end.compact
|
117
|
-
elsif reference_def[:metadata].relation == Mongoid::Relations::Referenced::In
|
118
|
-
reference_json = materialize_json(options, { :clazz => clazz, :id => object.send(key) })
|
119
133
|
end
|
120
134
|
end
|
121
135
|
# If we get to this point and reference_json is still nil, there's no chance we can
|
@@ -123,23 +137,73 @@ module Mongoid
|
|
123
137
|
if ! reference_json
|
124
138
|
reference_def_definition = reference_def[:definition]
|
125
139
|
reference = reference_def_definition.is_a?(Symbol) ? object.send(reference_def_definition) : reference_def_definition.call(object)
|
126
|
-
reference_json =
|
140
|
+
reference_json = nil
|
141
|
+
if reference
|
142
|
+
if reference.respond_to?(:as_json_partial)
|
143
|
+
reference_keys, reference_json = reference.as_json_partial(options)
|
144
|
+
keys = keys ? keys.merge_set(reference_keys) : reference_keys
|
145
|
+
else
|
146
|
+
reference_json = reference.as_json(options)
|
147
|
+
end
|
148
|
+
end
|
127
149
|
end
|
128
|
-
reference_json
|
150
|
+
[ keys, reference_json ]
|
129
151
|
end
|
130
|
-
|
152
|
+
|
131
153
|
end
|
132
|
-
|
133
|
-
|
154
|
+
|
155
|
+
# Check whether the cache supports :read_multi and prefetch the data if it does.
|
156
|
+
def self.materialize_json_references_with_read_multi(key_refs, partial_json)
|
157
|
+
unfrozen_keys = key_refs.keys.to_a.map(&:dup) if key_refs # see https://github.com/mperham/dalli/pull/320
|
158
|
+
local_cache = unfrozen_keys && Mongoid::CachedJson.config.cache.respond_to?(:read_multi) ? Mongoid::CachedJson.config.cache.read_multi(unfrozen_keys) : {}
|
159
|
+
Mongoid::CachedJson.materialize_json_references(key_refs, local_cache) if key_refs
|
160
|
+
partial_json
|
161
|
+
end
|
162
|
+
|
163
|
+
# Materialize all the JSON references in place.
|
164
|
+
def self.materialize_json_references(key_refs, local_cache = {})
|
165
|
+
key_refs.each_pair do |key, refs|
|
166
|
+
refs.each do |ref|
|
167
|
+
_ref = ref.delete(:_ref)
|
168
|
+
key = _ref[:_key]
|
169
|
+
fetched_json = local_cache[key] if local_cache.has_key?(key)
|
170
|
+
fetched_json ||= (local_cache[key] = Mongoid::CachedJson.config.cache.fetch(key, { :force => !! Mongoid::CachedJson.config.disable_caching }) do
|
171
|
+
_ref[:_clazz].materialize_cached_json(* _ref[:_materialize_cached_json])
|
172
|
+
end)
|
173
|
+
if fetched_json
|
174
|
+
ref.merge! fetched_json
|
175
|
+
elsif _ref[:_parent]
|
176
|
+
# a single _ref that resolved to a nil
|
177
|
+
_ref[:_parent][_ref[:_field]] = nil
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Return a partial JSON without resolved references and all the keys.
|
184
|
+
def as_json_partial(options = {})
|
134
185
|
options ||= {}
|
135
186
|
if options[:properties] and ! self.all_json_properties.member?(options[:properties])
|
136
187
|
raise ArgumentError.new("Unknown properties option: #{options[:properties]}")
|
137
188
|
end
|
138
|
-
|
189
|
+
# partial, unmaterialized JSON
|
190
|
+
keys, partial_json = self.class.materialize_json({
|
139
191
|
:properties => :short, :is_top_level_json => true, :version => Mongoid::CachedJson.config.default_version
|
140
192
|
}.merge(options), { :object => self })
|
193
|
+
[ keys, partial_json ]
|
141
194
|
end
|
142
|
-
|
195
|
+
|
196
|
+
# Fetch the partial JSON and materialize all JSON references.
|
197
|
+
def as_json_cached(options = {})
|
198
|
+
keys, json = as_json_partial(options)
|
199
|
+
Mongoid::CachedJson.materialize_json_references_with_read_multi(keys, json)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Return the JSON representation of the object.
|
203
|
+
def as_json(options = {})
|
204
|
+
as_json_cached(options)
|
205
|
+
end
|
206
|
+
|
143
207
|
# Expire all JSON entries for this class.
|
144
208
|
def expire_cached_json
|
145
209
|
self.all_json_properties.each do |properties|
|
@@ -152,9 +216,9 @@ module Mongoid
|
|
152
216
|
end
|
153
217
|
end
|
154
218
|
end
|
155
|
-
|
219
|
+
|
156
220
|
class << self
|
157
|
-
|
221
|
+
|
158
222
|
# Set the configuration options. Best used by passing a block.
|
159
223
|
#
|
160
224
|
# @example Set up configuration options.
|
@@ -168,6 +232,6 @@ module Mongoid
|
|
168
232
|
end
|
169
233
|
alias :config :configure
|
170
234
|
end
|
171
|
-
|
235
|
+
|
172
236
|
end
|
173
237
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Hash
|
2
|
+
|
3
|
+
def as_json_partial(options = {})
|
4
|
+
json_keys = nil
|
5
|
+
json = inject({}) do |h, (k, v)|
|
6
|
+
if v.respond_to?(:as_json_partial)
|
7
|
+
partial_json_keys, partial_json = v.as_json_partial(options)
|
8
|
+
json_keys = json_keys ? json_keys.merge_set(partial_json_keys) : partial_json_keys
|
9
|
+
h[k] = partial_json
|
10
|
+
else
|
11
|
+
h[k] = v.as_json(options)
|
12
|
+
end
|
13
|
+
h
|
14
|
+
end
|
15
|
+
[ json_keys, json ]
|
16
|
+
end
|
17
|
+
|
18
|
+
def as_json(options = {})
|
19
|
+
json_keys, json = as_json_partial(options)
|
20
|
+
Mongoid::CachedJson.materialize_json_references_with_read_multi(json_keys, json)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Keep key references to be replaced once the entire JSON is available.
|
2
|
+
# encoding: utf-8
|
3
|
+
module Mongoid
|
4
|
+
module CachedJson
|
5
|
+
class KeyReferences < Hash
|
6
|
+
|
7
|
+
def merge_set(keys)
|
8
|
+
if keys
|
9
|
+
keys.each_pair do |k, jsons|
|
10
|
+
self[k] ||= []
|
11
|
+
self[k].concat(jsons)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_and_add(key, json)
|
18
|
+
self[key] ||= []
|
19
|
+
self[key] << json
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Mongoid
|
2
|
+
class Criteria
|
3
|
+
|
4
|
+
def as_json_partial(options = {})
|
5
|
+
json_keys = nil
|
6
|
+
json = map do |i|
|
7
|
+
if i.respond_to?(:as_json_partial)
|
8
|
+
partial_json_keys, partial_json = i.as_json_partial(options)
|
9
|
+
json_keys = json_keys ? json_keys.merge_set(partial_json_keys) : partial_json_keys
|
10
|
+
partial_json
|
11
|
+
else
|
12
|
+
i.as_json(options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
[ json_keys, json ]
|
16
|
+
end
|
17
|
+
|
18
|
+
def as_json(options = {})
|
19
|
+
json_keys, json = as_json_partial(options)
|
20
|
+
Mongoid::CachedJson.materialize_json_references_with_read_multi(json_keys, json)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid-cached-json
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date:
|
14
|
+
date: 2013-01-20 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
@@ -125,6 +125,22 @@ dependencies:
|
|
125
125
|
- - ~>
|
126
126
|
- !ruby/object:Gem::Version
|
127
127
|
version: '0.6'
|
128
|
+
- !ruby/object:Gem::Dependency
|
129
|
+
name: dalli
|
130
|
+
requirement: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ~>
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '2.6'
|
136
|
+
type: :development
|
137
|
+
prerelease: false
|
138
|
+
version_requirements: !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ~>
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '2.6'
|
128
144
|
description: Cached-json is a DSL for describing JSON representations of Mongoid models.
|
129
145
|
email: dblock@dblock.org
|
130
146
|
executables: []
|
@@ -134,8 +150,12 @@ extra_rdoc_files:
|
|
134
150
|
- README.md
|
135
151
|
files:
|
136
152
|
- lib/mongoid-cached-json.rb
|
153
|
+
- lib/mongoid-cached-json/array.rb
|
137
154
|
- lib/mongoid-cached-json/cached_json.rb
|
138
155
|
- lib/mongoid-cached-json/config.rb
|
156
|
+
- lib/mongoid-cached-json/hash.rb
|
157
|
+
- lib/mongoid-cached-json/key_references.rb
|
158
|
+
- lib/mongoid-cached-json/mongoid_criteria.rb
|
139
159
|
- lib/mongoid-cached-json/version.rb
|
140
160
|
- LICENSE.md
|
141
161
|
- README.md
|
@@ -154,7 +174,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
154
174
|
version: '0'
|
155
175
|
segments:
|
156
176
|
- 0
|
157
|
-
hash:
|
177
|
+
hash: -1533991509568558766
|
158
178
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
159
179
|
none: false
|
160
180
|
requirements:
|