mongoid-cached-json 1.3.0 → 1.4.0
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.
- 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:
|