mongoid-cached-json 1.5.1 → 1.5.2
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 +7 -0
- data/.gitignore +21 -0
- data/.rspec +3 -0
- data/.rubocop.yml +6 -0
- data/.rubocop_todo.yml +80 -0
- data/.travis.yml +16 -0
- data/CHANGELOG.md +85 -0
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +21 -0
- data/README.md +49 -52
- data/Rakefile +34 -0
- data/lib/mongoid-cached-json.rb +0 -1
- data/lib/mongoid-cached-json/array.rb +1 -3
- data/lib/mongoid-cached-json/cached_json.rb +52 -50
- data/lib/mongoid-cached-json/config.rb +20 -21
- data/lib/mongoid-cached-json/hash.rb +1 -3
- data/lib/mongoid-cached-json/key_references.rb +0 -3
- data/lib/mongoid-cached-json/mongoid_criteria.rb +1 -3
- data/lib/mongoid-cached-json/version.rb +1 -2
- data/mongoid-cached-json.gemspec +17 -0
- data/spec/array_spec.rb +48 -0
- data/spec/benchmark_spec.rb +115 -0
- data/spec/cached_json_spec.rb +501 -0
- data/spec/config_spec.rb +21 -0
- data/spec/dalli_spec.rb +37 -0
- data/spec/hash_spec.rb +66 -0
- data/spec/mongoid_criteria_spec.rb +78 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/awesome_artwork.rb +11 -0
- data/spec/support/awesome_image.rb +14 -0
- data/spec/support/fast_json_artwork.rb +15 -0
- data/spec/support/fast_json_image.rb +12 -0
- data/spec/support/fast_json_url.rb +12 -0
- data/spec/support/json_embedded_foobar.rb +5 -0
- data/spec/support/json_employee.rb +12 -0
- data/spec/support/json_foobar.rb +19 -0
- data/spec/support/json_manager.rb +14 -0
- data/spec/support/json_math.rb +9 -0
- data/spec/support/json_parent_foobar.rb +11 -0
- data/spec/support/json_polymorphic_embedded_foobar.rb +9 -0
- data/spec/support/json_polymorphic_referenced_foobar.rb +9 -0
- data/spec/support/json_referenced_foobar.rb +5 -0
- data/spec/support/json_supervisor.rb +13 -0
- data/spec/support/json_transform.rb +13 -0
- data/spec/support/matchers/invalidate.rb +22 -0
- data/spec/support/person.rb +20 -0
- data/spec/support/poly_company.rb +10 -0
- data/spec/support/poly_person.rb +10 -0
- data/spec/support/poly_post.rb +9 -0
- data/spec/support/prison_cell.rb +11 -0
- data/spec/support/prison_inmate.rb +12 -0
- data/spec/support/secret_parent.rb +11 -0
- data/spec/support/sometimes_secret.rb +11 -0
- data/spec/support/tool.rb +11 -0
- data/spec/support/tool_box.rb +10 -0
- metadata +62 -137
@@ -1,6 +1,5 @@
|
|
1
1
|
module Mongoid
|
2
2
|
class Criteria
|
3
|
-
|
4
3
|
def as_json_partial(options = {})
|
5
4
|
json_keys = nil
|
6
5
|
json = map do |i|
|
@@ -12,13 +11,12 @@ module Mongoid
|
|
12
11
|
i.as_json(options)
|
13
12
|
end
|
14
13
|
end
|
15
|
-
[
|
14
|
+
[json_keys, json]
|
16
15
|
end
|
17
16
|
|
18
17
|
def as_json(options = {})
|
19
18
|
json_keys, json = as_json_partial(options)
|
20
19
|
Mongoid::CachedJson.materialize_json_references_with_read_multi(json_keys, json)
|
21
20
|
end
|
22
|
-
|
23
21
|
end
|
24
22
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
|
2
|
+
require 'mongoid-cached-json/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'mongoid-cached-json'
|
6
|
+
s.version = Mongoid::CachedJson::VERSION
|
7
|
+
s.authors = ['Aaron Windsor', 'Daniel Doubrovkine', 'Frank Macreery']
|
8
|
+
s.email = 'dblock@dblock.org'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.required_rubygems_version = '>= 1.3.6'
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.require_paths = ['lib']
|
13
|
+
s.homepage = 'http://github.com/dblock/mongoid-cached-json'
|
14
|
+
s.licenses = ['MIT']
|
15
|
+
s.summary = 'Cached-json is a DSL for describing JSON representations of Mongoid models.'
|
16
|
+
s.add_dependency 'mongoid', '>= 3.0'
|
17
|
+
end
|
data/spec/array_spec.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Array do
|
4
|
+
it 'array' do
|
5
|
+
expect([:x, 'y'].as_json).to eq(%w(x y))
|
6
|
+
end
|
7
|
+
it 'materializes multiple objects that may or may not respond to as_json_partial' do
|
8
|
+
foobar1 = JsonFoobar.create!(foo: 'FOO1', baz: 'BAZ', bar: 'BAR')
|
9
|
+
foobar2 = JsonFoobar.create!(foo: 'FOO2', baz: 'BAZ', bar: 'BAR')
|
10
|
+
expect([[:x, :y], foobar1, foobar2, foobar1, { x: foobar1, y: 'z' }].as_json).to eq([
|
11
|
+
%w(x y),
|
12
|
+
{ :foo => 'FOO1', 'Baz' => 'BAZ', :default_foo => 'DEFAULT_FOO' },
|
13
|
+
{ :foo => 'FOO2', 'Baz' => 'BAZ', :default_foo => 'DEFAULT_FOO' },
|
14
|
+
{ :foo => 'FOO1', 'Baz' => 'BAZ', :default_foo => 'DEFAULT_FOO' },
|
15
|
+
{ x: { :foo => 'FOO1', 'Baz' => 'BAZ', :default_foo => 'DEFAULT_FOO' }, y: 'z' }
|
16
|
+
])
|
17
|
+
end
|
18
|
+
context 'without read_multi' do
|
19
|
+
before :each do
|
20
|
+
Mongoid::CachedJson.config.cache.instance_eval { undef :read_multi }
|
21
|
+
end
|
22
|
+
it 'uses a local cache to fetch repeated objects' do
|
23
|
+
tool = Tool.create!(name: 'hammer')
|
24
|
+
expect(Mongoid::CachedJson.config.cache).to receive(:fetch).once.and_return(
|
25
|
+
x: :y
|
26
|
+
)
|
27
|
+
expect([tool, tool, tool].as_json(properties: :all)).to eq([
|
28
|
+
{ tool_box: nil, x: :y },
|
29
|
+
{ tool_box: nil, x: :y },
|
30
|
+
{ tool_box: nil, x: :y }
|
31
|
+
])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
context 'with read_multi' do
|
35
|
+
it 'uses a local cache to fetch repeated objects' do
|
36
|
+
tool = Tool.create!(name: 'hammer')
|
37
|
+
tool_key = "as_json/unspecified/Tool/#{tool.id}/all/true"
|
38
|
+
expect(Mongoid::CachedJson.config.cache).to receive(:read_multi).once.with(tool_key).and_return(
|
39
|
+
tool_key => { x: :y }
|
40
|
+
)
|
41
|
+
expect([tool, tool, tool].as_json(properties: :all)).to eq([
|
42
|
+
{ tool_box: nil, x: :y },
|
43
|
+
{ tool_box: nil, x: :y },
|
44
|
+
{ tool_box: nil, x: :y }
|
45
|
+
])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'benchmark'
|
3
|
+
require 'active_support/cache/dalli_store'
|
4
|
+
|
5
|
+
describe Mongoid::CachedJson do
|
6
|
+
before(:all) do
|
7
|
+
@n = 100
|
8
|
+
puts "Benchmarking #{Mongoid::CachedJson::VERSION} with #{RUBY_DESCRIPTION}, Dalli #{Dalli::VERSION}"
|
9
|
+
end
|
10
|
+
|
11
|
+
before do
|
12
|
+
# flat record
|
13
|
+
@flat = JsonFoobar.create!(foo: SecureRandom.uuid, baz: SecureRandom.uuid, bar: SecureRandom.uuid)
|
14
|
+
# has_many
|
15
|
+
@manager = JsonManager.create(name: 'Boss')
|
16
|
+
@manager.json_employees.create(name: 'Peon')
|
17
|
+
@manager.json_employees.create(name: 'Indentured servant')
|
18
|
+
@manager.json_employees.create(name: 'Serf', nickname: 'Vince')
|
19
|
+
# has_one
|
20
|
+
@artwork = AwesomeArtwork.create(name: 'Mona Lisa')
|
21
|
+
@artwork.create_awesome_image(name: 'Picture of Mona Lisa')
|
22
|
+
# child and parent with secrets
|
23
|
+
@child_secret = SometimesSecret.create(should_tell_secret: true)
|
24
|
+
@child_not_secret = SometimesSecret.create(should_tell_secret: false)
|
25
|
+
@parent_with_secret = SecretParent.create(name: 'Parent')
|
26
|
+
@parent_with_secret.create_sometimes_secret(should_tell_secret: true)
|
27
|
+
# habtm
|
28
|
+
@habtm = FastJsonArtwork.create
|
29
|
+
@habtm_image = @habtm.create_fast_json_image
|
30
|
+
@habtm_image.fast_json_urls.create
|
31
|
+
@habtm_image.fast_json_urls.create
|
32
|
+
@habtm_image.fast_json_urls.create
|
33
|
+
# transform
|
34
|
+
Mongoid::CachedJson.config.transform do |_field, definition, value|
|
35
|
+
definition[:transform] ? value.send(definition[:transform].to_sym) : value
|
36
|
+
end
|
37
|
+
@transform = JsonTransform.create!(upcase: 'upcase', downcase: 'DOWNCASE', nochange: 'eLiTe')
|
38
|
+
# polymorphic
|
39
|
+
@embedded = JsonEmbeddedFoobar.new(foo: 'embedded')
|
40
|
+
@referenced = JsonReferencedFoobar.new(foo: 'referenced')
|
41
|
+
@poly_parent = JsonParentFoobar.create!(
|
42
|
+
json_polymorphic_embedded_foobar: @embedded,
|
43
|
+
json_polymorphic_referenced_foobar: @referenced
|
44
|
+
)
|
45
|
+
@referenced.save!
|
46
|
+
# polymorphic relationships
|
47
|
+
@company = PolyCompany.create!
|
48
|
+
@company_post = PolyPost.create!(postable: @company)
|
49
|
+
@person = PolyPerson.create!
|
50
|
+
@person_post = PolyPost.create!(postable: @person)
|
51
|
+
# embeds_many
|
52
|
+
@cell = PrisonCell.create!(number: 42)
|
53
|
+
@cell.inmates.create!(nickname: 'Joe', person: Person.create!(first: 'Joe'))
|
54
|
+
@cell.inmates.create!(nickname: 'Bob', person: Person.create!(first: 'Bob'))
|
55
|
+
# belongs_to
|
56
|
+
@tool_box = ToolBox.create!(color: 'red')
|
57
|
+
@hammer = Tool.create!(name: 'hammer', tool_box: @tool_box)
|
58
|
+
@screwdriver = Tool.create!(name: 'screwdriver', tool_box: @tool_box)
|
59
|
+
@saw = Tool.create!(name: 'saw', tool_box: @tool_box)
|
60
|
+
end
|
61
|
+
|
62
|
+
[:dalli_store, :memory_store].each do |cache_store|
|
63
|
+
context cache_store do
|
64
|
+
before :each do
|
65
|
+
@cache = Mongoid::CachedJson::Config.cache
|
66
|
+
Mongoid::CachedJson.configure do |config|
|
67
|
+
config.cache = ActiveSupport::Cache.lookup_store(cache_store)
|
68
|
+
config.cache.clear
|
69
|
+
end
|
70
|
+
end
|
71
|
+
after :each do
|
72
|
+
Mongoid::CachedJson::Config.cache = @cache
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'benchmark' do
|
76
|
+
all_times = []
|
77
|
+
[
|
78
|
+
:flat, :manager, :artwork,
|
79
|
+
:child_secret, :child_not_secret, :parent_with_secret,
|
80
|
+
:habtm, :habtm_image,
|
81
|
+
:transform,
|
82
|
+
:poly_parent, :embedded, :referenced,
|
83
|
+
:company, :person, :company_post, :person_post,
|
84
|
+
:cell,
|
85
|
+
:tool_box, :hammer, :screwdriver, :saw
|
86
|
+
].each do |record|
|
87
|
+
times = []
|
88
|
+
times << Benchmark.realtime do
|
89
|
+
[:short, :public, :all].each do |properties|
|
90
|
+
instance = instance_variable_get("@#{record}".to_sym)
|
91
|
+
expect(instance).not_to be_nil
|
92
|
+
@n.times do
|
93
|
+
# instance
|
94
|
+
json = instance.as_json(properties: properties)
|
95
|
+
expect(json).not_to be_nil
|
96
|
+
expect(json).not_to eq({})
|
97
|
+
end
|
98
|
+
# class
|
99
|
+
if instance.class.respond_to?(:all)
|
100
|
+
json = instance.class.all.as_json(properties: properties)
|
101
|
+
expect(json).not_to be_nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
all_times.concat(times)
|
106
|
+
avg = times.reduce { |sum, time| sum + time } / times.size
|
107
|
+
puts "#{cache_store}:#{record} => #{avg}"
|
108
|
+
end
|
109
|
+
avg = all_times.reduce { |sum, time| sum + time } / all_times.size
|
110
|
+
puts '=' * 40
|
111
|
+
puts "#{cache_store} => #{avg}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,501 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Mongoid::CachedJson do
|
4
|
+
it 'has a version' do
|
5
|
+
expect(Mongoid::CachedJson::VERSION).not_to be_nil
|
6
|
+
expect(Mongoid::CachedJson::VERSION.to_f).to be > 0
|
7
|
+
end
|
8
|
+
|
9
|
+
[:dalli_store, :memory_store].each do |cache_store|
|
10
|
+
context "#{cache_store}" do
|
11
|
+
before :each do
|
12
|
+
@cache = Mongoid::CachedJson::Config.cache
|
13
|
+
Mongoid::CachedJson.configure do |config|
|
14
|
+
config.cache = ActiveSupport::Cache.lookup_store(cache_store)
|
15
|
+
end
|
16
|
+
if cache_store == :memory_store && Mongoid::CachedJson.config.cache.respond_to?(:read_multi)
|
17
|
+
Mongoid::CachedJson.config.cache.instance_eval { undef :read_multi }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
after :each do
|
21
|
+
Mongoid::CachedJson::Config.cache = @cache
|
22
|
+
end
|
23
|
+
context 'with basic fields defined for export with json_fields' do
|
24
|
+
it 'returns public JSON if you nil options' do
|
25
|
+
example = JsonFoobar.create(foo: 'FOO', baz: 'BAZ', bar: 'BAR')
|
26
|
+
expect(example.as_json(nil)).to eq(example.as_json(properties: :short))
|
27
|
+
end
|
28
|
+
it 'allows subsets of fields to be returned by varying the properties definition' do
|
29
|
+
example = JsonFoobar.create(foo: 'FOO', baz: 'BAZ', bar: 'BAR')
|
30
|
+
# :short is a subset of the fields in :public and :public is a subset of the fields in :all
|
31
|
+
expect(example.as_json(properties: :short)).to eq(:foo => 'FOO', 'Baz' => 'BAZ', :default_foo => 'DEFAULT_FOO')
|
32
|
+
expect(example.as_json(properties: :public)).to eq(:foo => 'FOO', 'Baz' => 'BAZ', :bar => 'BAR', :default_foo => 'DEFAULT_FOO')
|
33
|
+
expect(example.as_json(properties: :all)).to eq(:foo => 'FOO', :bar => 'BAR', 'Baz' => 'BAZ', :renamed_baz => 'BAZ', :default_foo => 'DEFAULT_FOO', :computed_field => 'FOOBAR')
|
34
|
+
end
|
35
|
+
it 'throws an error if you ask for an undefined property type' do
|
36
|
+
expect { JsonFoobar.create.as_json(properties: :special) }.to raise_error(ArgumentError)
|
37
|
+
end
|
38
|
+
it "does not raise an error if you don't specify properties" do
|
39
|
+
expect { JsonFoobar.create.as_json({}) }.to_not raise_error
|
40
|
+
end
|
41
|
+
it 'should hit the cache for subsequent as_json calls after the first' do
|
42
|
+
foobar = JsonFoobar.create(foo: 'FOO', bar: 'BAR', baz: 'BAZ')
|
43
|
+
all_result = foobar.as_json(properties: :all)
|
44
|
+
public_result = foobar.as_json(properties: :public)
|
45
|
+
short_result = foobar.as_json(properties: :short)
|
46
|
+
expect(all_result).not_to eq(public_result)
|
47
|
+
expect(all_result).not_to eq(short_result)
|
48
|
+
expect(public_result).not_to eq(short_result)
|
49
|
+
3.times { expect(foobar.as_json(properties: :all)).to eq(all_result) }
|
50
|
+
3.times { expect(foobar.as_json(properties: :public)).to eq(public_result) }
|
51
|
+
3.times { expect(foobar.as_json(properties: :short)).to eq(short_result) }
|
52
|
+
end
|
53
|
+
it 'should remove values from the cache when a model is saved' do
|
54
|
+
foobar = JsonFoobar.create(foo: 'FOO', bar: 'BAR', baz: 'BAZ')
|
55
|
+
all_result = foobar.as_json(properties: :all)
|
56
|
+
public_result = foobar.as_json(properties: :public)
|
57
|
+
short_result = foobar.as_json(properties: :short)
|
58
|
+
foobar.foo = 'updated'
|
59
|
+
# Not saved yet, so we should still be hitting the cache
|
60
|
+
3.times { expect(foobar.as_json(properties: :all)).to eq(all_result) }
|
61
|
+
3.times { expect(foobar.as_json(properties: :public)).to eq(public_result) }
|
62
|
+
3.times { expect(foobar.as_json(properties: :short)).to eq(short_result) }
|
63
|
+
foobar.save
|
64
|
+
3.times { expect(foobar.as_json(properties: :all)).to eq(all_result.merge(foo: 'updated', computed_field: 'updatedBAR')) }
|
65
|
+
3.times { expect(foobar.as_json(properties: :public)).to eq(public_result.merge(foo: 'updated')) }
|
66
|
+
3.times { expect(foobar.as_json(properties: :short)).to eq(short_result.merge(foo: 'updated')) }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context 'invalidate callbacks' do
|
70
|
+
before :each do
|
71
|
+
@foobar = JsonFoobar.create!(foo: 'FOO')
|
72
|
+
end
|
73
|
+
it 'should invalidate cache when a model is saved' do
|
74
|
+
expect do
|
75
|
+
@foobar.update_attributes!(foo: 'BAR')
|
76
|
+
end.to invalidate @foobar
|
77
|
+
end
|
78
|
+
it 'should also invalidate cache when a model is saved without changes' do
|
79
|
+
expect do
|
80
|
+
@foobar.save!
|
81
|
+
end.to invalidate @foobar
|
82
|
+
end
|
83
|
+
it 'should invalidate cache when a model is destroyed' do
|
84
|
+
expect do
|
85
|
+
@foobar.destroy
|
86
|
+
end.to invalidate @foobar
|
87
|
+
end
|
88
|
+
end
|
89
|
+
context 'many-to-one relationships' do
|
90
|
+
it 'uses the correct properties on the base object and passes :short or :all as appropriate' do
|
91
|
+
manager = JsonManager.create(name: 'Boss')
|
92
|
+
peon = manager.json_employees.create(name: 'Peon')
|
93
|
+
manager.json_employees.create(name: 'Indentured servant')
|
94
|
+
manager.json_employees.create(name: 'Serf', nickname: 'Vince')
|
95
|
+
3.times do
|
96
|
+
3.times do
|
97
|
+
manager_short_json = manager.as_json(properties: :short)
|
98
|
+
expect(manager_short_json.length).to eq(2)
|
99
|
+
expect(manager_short_json[:name]).to eq('Boss')
|
100
|
+
expect(manager_short_json[:employees].member?(name: 'Peon')).to be_truthy
|
101
|
+
expect(manager_short_json[:employees].member?(name: 'Indentured servant')).to be_truthy
|
102
|
+
expect(manager_short_json[:employees].member?(name: 'Serf')).to be_truthy
|
103
|
+
expect(manager_short_json[:employees].member?(nickname: 'Serf')).to be_falsey
|
104
|
+
end
|
105
|
+
3.times do
|
106
|
+
manager_public_json = manager.as_json(properties: :public)
|
107
|
+
expect(manager_public_json.length).to eq(2)
|
108
|
+
expect(manager_public_json[:name]).to eq('Boss')
|
109
|
+
expect(manager_public_json[:employees].member?(name: 'Peon')).to be_truthy
|
110
|
+
expect(manager_public_json[:employees].member?(name: 'Indentured servant')).to be_truthy
|
111
|
+
expect(manager_public_json[:employees].member?(name: 'Serf')).to be_truthy
|
112
|
+
expect(manager_public_json[:employees].member?(nickname: 'Serf')).to be_falsey
|
113
|
+
end
|
114
|
+
3.times do
|
115
|
+
manager_all_json = manager.as_json(properties: :all)
|
116
|
+
expect(manager_all_json.length).to eq(3)
|
117
|
+
expect(manager_all_json[:name]).to eq('Boss')
|
118
|
+
expect(manager_all_json[:ssn]).to eq('123-45-6789')
|
119
|
+
expect(manager_all_json[:employees].member?(name: 'Peon', nickname: 'My Favorite')).to be_truthy
|
120
|
+
expect(manager_all_json[:employees].member?(name: 'Indentured servant', nickname: 'My Favorite')).to be_truthy
|
121
|
+
expect(manager_all_json[:employees].member?(name: 'Serf', nickname: 'Vince')).to be_truthy
|
122
|
+
end
|
123
|
+
3.times do
|
124
|
+
expect(peon.as_json(properties: :short)).to eq(name: 'Peon')
|
125
|
+
end
|
126
|
+
3.times do
|
127
|
+
expect(peon.as_json(properties: :all)).to eq(name: 'Peon', nickname: 'My Favorite')
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
it 'correctly updates fields when either the parent or child class changes' do
|
132
|
+
manager = JsonManager.create(name: 'JsonManager')
|
133
|
+
employee = manager.json_employees.create(name: 'JsonEmployee')
|
134
|
+
3.times do
|
135
|
+
expect(manager.as_json(properties: :short)).to eq(name: 'JsonManager', employees: [{ name: 'JsonEmployee' }])
|
136
|
+
expect(employee.as_json(properties: :short)).to eq(name: 'JsonEmployee')
|
137
|
+
end
|
138
|
+
manager.name = 'New JsonManager'
|
139
|
+
manager.save
|
140
|
+
3.times { expect(manager.as_json(properties: :short)).to eq(name: 'New JsonManager', employees: [{ name: 'JsonEmployee' }]) }
|
141
|
+
employee.name = 'New JsonEmployee'
|
142
|
+
employee.save
|
143
|
+
3.times { expect(manager.as_json(properties: :short)).to eq(name: 'New JsonManager', employees: [{ name: 'New JsonEmployee' }]) }
|
144
|
+
end
|
145
|
+
context 'reference_properties' do
|
146
|
+
it 'limits the json fields of a child relationship' do
|
147
|
+
supervisor = JsonSupervisor.create(name: 'JsonSupervisor')
|
148
|
+
manager = JsonManager.create(name: 'JsonManager', supervisor: supervisor)
|
149
|
+
json = supervisor.as_json(properties: :all)
|
150
|
+
expect(json[:managers][0].key?(:ssn)).to be_falsey
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
context 'one-to-one relationships' do
|
155
|
+
before(:each) do
|
156
|
+
@artwork = AwesomeArtwork.create(name: 'Mona Lisa')
|
157
|
+
end
|
158
|
+
it 'uses the correct properties on the base object and passes :all to any sub-objects for :all properties' do
|
159
|
+
3.times do
|
160
|
+
expect(@artwork.as_json(properties: :all)).to eq(name: 'Mona Lisa', image: nil)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
context 'with the relationship present' do
|
164
|
+
before(:each) do
|
165
|
+
@image = @artwork.create_awesome_image(name: 'Picture of Mona Lisa')
|
166
|
+
end
|
167
|
+
it 'uses the correct properties on the base object and passes :short to any sub-objects for :public and :short properties' do
|
168
|
+
3.times do
|
169
|
+
expect(@artwork.as_json(properties: :short)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Mona' })
|
170
|
+
expect(@artwork.as_json(properties: :public)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Mona' })
|
171
|
+
expect(@artwork.as_json(properties: :all)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Mona', url: 'http://example.com/404.html' })
|
172
|
+
expect(@image.as_json(properties: :short)).to eq(name: 'Picture of Mona Lisa', nickname: 'Mona')
|
173
|
+
expect(@image.as_json(properties: :public)).to eq(name: 'Picture of Mona Lisa', nickname: 'Mona', url: 'http://example.com/404.html')
|
174
|
+
end
|
175
|
+
end
|
176
|
+
it 'uses the correct properties on the base object and passes :all to any sub-objects for :all properties' do
|
177
|
+
3.times do
|
178
|
+
expect(@artwork.as_json(properties: :all)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Mona', url: 'http://example.com/404.html' })
|
179
|
+
end
|
180
|
+
end
|
181
|
+
it 'correctly updates fields when either the parent or child class changes' do
|
182
|
+
# Call as_json for all properties so that the json will get cached
|
183
|
+
[:short, :public, :all].each { |properties| @artwork.as_json(properties: properties) }
|
184
|
+
@image.nickname = 'Worst Painting Ever'
|
185
|
+
# Nothing has been saved yet, cached json for referenced document should reflect the truth in the database
|
186
|
+
3.times do
|
187
|
+
expect(@artwork.as_json(properties: :short)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Mona' })
|
188
|
+
expect(@artwork.as_json(properties: :public)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Mona' })
|
189
|
+
expect(@artwork.as_json(properties: :all)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Mona', url: 'http://example.com/404.html' })
|
190
|
+
end
|
191
|
+
@image.save
|
192
|
+
3.times do
|
193
|
+
expect(@artwork.as_json(properties: :short)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Worst Painting Ever' })
|
194
|
+
expect(@artwork.as_json(properties: :public)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Worst Painting Ever' })
|
195
|
+
expect(@artwork.as_json(properties: :all)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Worst Painting Ever', url: 'http://example.com/404.html' })
|
196
|
+
end
|
197
|
+
@image.name = 'Picture of Mona Lisa Watercolor'
|
198
|
+
3.times do
|
199
|
+
expect(@artwork.as_json(properties: :short)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Worst Painting Ever' })
|
200
|
+
expect(@artwork.as_json(properties: :public)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Worst Painting Ever' })
|
201
|
+
expect(@artwork.as_json(properties: :all)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa', nickname: 'Worst Painting Ever', url: 'http://example.com/404.html' })
|
202
|
+
end
|
203
|
+
@image.save
|
204
|
+
3.times do
|
205
|
+
expect(@artwork.as_json(properties: :short)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa Watercolor', nickname: 'Worst Painting Ever' })
|
206
|
+
expect(@artwork.as_json(properties: :public)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa Watercolor', nickname: 'Worst Painting Ever' })
|
207
|
+
expect(@artwork.as_json(properties: :all)).to eq(name: 'Mona Lisa', image: { name: 'Picture of Mona Lisa Watercolor', nickname: 'Worst Painting Ever', url: 'http://example.com/404.html' })
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
context 'with a hide_as_child_json_when definition' do
|
213
|
+
it 'should yield JSON when as_json is called directly and hide_as_child_json_when returns false on an instance' do
|
214
|
+
c = SometimesSecret.create(should_tell_secret: true)
|
215
|
+
expect(c.as_json(properties: :short)).to eq(secret: 'Afraid of the dark')
|
216
|
+
end
|
217
|
+
it 'should yield JSON when as_json is called directly and hide_as_child_json_when returns true on an instance' do
|
218
|
+
c = SometimesSecret.create(should_tell_secret: false)
|
219
|
+
expect(c.as_json(properties: :short)).to eq(secret: 'Afraid of the dark')
|
220
|
+
end
|
221
|
+
it 'should yield JSON without an instance of a child' do
|
222
|
+
p = SecretParent.create(name: 'Parent')
|
223
|
+
expect(p.as_json(properties: :all)[:child]).to be_nil
|
224
|
+
end
|
225
|
+
it 'should yield child JSON when as_json is called on the parent and hide_as_child_json_when returns false on an instance' do
|
226
|
+
p = SecretParent.create(name: 'Parent')
|
227
|
+
p.create_sometimes_secret(should_tell_secret: true)
|
228
|
+
expect(p.as_json(properties: :short)[:child]).to eq(secret: 'Afraid of the dark')
|
229
|
+
end
|
230
|
+
it 'should not yield child JSON when as_json is called on the parent and hide_as_child_json_when returns true on an instance' do
|
231
|
+
p = SecretParent.create(name: 'Parent')
|
232
|
+
p.create_sometimes_secret(should_tell_secret: false)
|
233
|
+
expect(p.as_json(properties: :short)).to eq(name: 'Parent', child: nil)
|
234
|
+
expect(p.as_json(properties: :short)[:child]).to be_nil
|
235
|
+
end
|
236
|
+
end
|
237
|
+
context 'relationships with a multi-level hierarchy' do
|
238
|
+
before(:each) do
|
239
|
+
@artwork = FastJsonArtwork.create
|
240
|
+
@image = @artwork.create_fast_json_image
|
241
|
+
@url1 = @image.fast_json_urls.create
|
242
|
+
@url2 = @image.fast_json_urls.create
|
243
|
+
@url3 = @image.fast_json_urls.create
|
244
|
+
@common_url = @url1.url
|
245
|
+
end
|
246
|
+
it 'uses the correct properties on the base object and passes :short to any sub-objects for :short and :public' do
|
247
|
+
3.times do
|
248
|
+
expect(@artwork.as_json(properties: :short)).to eq(
|
249
|
+
name: 'Artwork',
|
250
|
+
image: { name: 'Image',
|
251
|
+
urls: [
|
252
|
+
{ url: @common_url },
|
253
|
+
{ url: @common_url },
|
254
|
+
{ url: @common_url }
|
255
|
+
]
|
256
|
+
}
|
257
|
+
)
|
258
|
+
expect(@artwork.as_json(properties: :public)).to eq(
|
259
|
+
name: 'Artwork',
|
260
|
+
display_name: 'Awesome Artwork',
|
261
|
+
image: { name: 'Image',
|
262
|
+
urls: [
|
263
|
+
{ url: @common_url },
|
264
|
+
{ url: @common_url },
|
265
|
+
{ url: @common_url }
|
266
|
+
]
|
267
|
+
}
|
268
|
+
)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
it 'uses the correct properties on the base object and passes :all to any sub-objects for :all' do
|
272
|
+
3.times do
|
273
|
+
expect(@artwork.as_json(properties: :all)).to eq(
|
274
|
+
name: 'Artwork',
|
275
|
+
display_name: 'Awesome Artwork',
|
276
|
+
price: 1000,
|
277
|
+
image: { name: 'Image',
|
278
|
+
urls: [
|
279
|
+
{ url: @common_url, is_public: false },
|
280
|
+
{ url: @common_url, is_public: false },
|
281
|
+
{ url: @common_url, is_public: false }]
|
282
|
+
}
|
283
|
+
)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
it 'correctly updates json for all classes in the hierarchy when saves occur' do
|
287
|
+
# Call as_json once to make sure the json is cached before we modify the referenced model locally
|
288
|
+
@artwork.as_json(properties: :short)
|
289
|
+
new_url = 'http://chee.sy/omg.jpg'
|
290
|
+
@url1.url = new_url
|
291
|
+
# No save has happened, so as_json shouldn't update yet
|
292
|
+
3.times do
|
293
|
+
expect(@artwork.as_json(properties: :short)).to eq(
|
294
|
+
name: 'Artwork',
|
295
|
+
image: { name: 'Image',
|
296
|
+
urls: [
|
297
|
+
{ url: @common_url },
|
298
|
+
{ url: @common_url },
|
299
|
+
{ url: @common_url }
|
300
|
+
]
|
301
|
+
}
|
302
|
+
)
|
303
|
+
end
|
304
|
+
@url1.save
|
305
|
+
3.times do
|
306
|
+
json = @artwork.as_json
|
307
|
+
expect(json[:name]).to eq('Artwork')
|
308
|
+
expect(json[:image][:name]).to eq('Image')
|
309
|
+
expect(json[:image][:urls].map { |u| u[:url] }.sort).to eq([@common_url, @common_url, new_url].sort)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
context 'transform' do
|
314
|
+
context 'upcase' do
|
315
|
+
before :each do
|
316
|
+
Mongoid::CachedJson.config.transform do |_field, _definition, value|
|
317
|
+
value.upcase
|
318
|
+
end
|
319
|
+
end
|
320
|
+
it 'transforms every value in returned JSON' do
|
321
|
+
expect(JsonFoobar.new(foo: 'foo', bar: 'Bar', baz: 'BAZ').as_json).to eq('Baz' => 'BAZ', :default_foo => 'DEFAULT_FOO', :foo => 'FOO')
|
322
|
+
end
|
323
|
+
end
|
324
|
+
context 'with options' do
|
325
|
+
before :each do
|
326
|
+
Mongoid::CachedJson.config.transform do |_field, definition, value|
|
327
|
+
definition[:transform] ? value.send(definition[:transform].to_sym) : value
|
328
|
+
end
|
329
|
+
end
|
330
|
+
it 'transforms every value in returned JSON using the :transform attribute' do
|
331
|
+
expect(JsonTransform.new(upcase: 'upcase', downcase: 'DOWNCASE', nochange: 'eLiTe').as_json).to eq(upcase: 'UPCASE', downcase: 'downcase', nochange: 'eLiTe')
|
332
|
+
end
|
333
|
+
end
|
334
|
+
context 'with mutliple transformations' do
|
335
|
+
before :each do
|
336
|
+
Mongoid::CachedJson.config.transform do |_field, _definition, value|
|
337
|
+
value.to_i + 1
|
338
|
+
end
|
339
|
+
Mongoid::CachedJson.config.transform do |_field, _definition, value|
|
340
|
+
value.to_i / 2
|
341
|
+
end
|
342
|
+
end
|
343
|
+
it 'transforms every value in returned JSON using the :transform attribute' do
|
344
|
+
expect(JsonMath.new(number: 9).as_json).to eq(number: 5)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
context 'with cache disabled' do
|
349
|
+
before :each do
|
350
|
+
allow(Mongoid::CachedJson.config).to receive(:disable_caching).and_return(true)
|
351
|
+
end
|
352
|
+
it 'forces a cache miss' do
|
353
|
+
example = JsonFoobar.create(foo: 'FOO', baz: 'BAZ', bar: 'BAR')
|
354
|
+
key = "as_json/unspecified/JsonFoobar/#{example.id}/short/true"
|
355
|
+
case cache_store
|
356
|
+
when :memory_store then
|
357
|
+
expect(Mongoid::CachedJson.config.cache).to receive(:fetch).with(key, force: true).twice
|
358
|
+
when :dalli_store then
|
359
|
+
expect(Mongoid::CachedJson.config.cache).not_to receive(:write)
|
360
|
+
else
|
361
|
+
fail ArgumentError, "invalid cache store: #{cache_store}"
|
362
|
+
end
|
363
|
+
2.times { example.as_json }
|
364
|
+
end
|
365
|
+
end
|
366
|
+
context 'versioning' do
|
367
|
+
it 'returns JSON for version 2' do
|
368
|
+
example = JsonFoobar.create(foo: 'FOO', baz: 'BAZ', bar: 'BAR')
|
369
|
+
expect(example.as_json(properties: :short, version: :v2)).to eq(:foo => 'FOO', 'Taz' => 'BAZ', 'Naz' => 'BAZ', :default_foo => 'DEFAULT_FOO')
|
370
|
+
end
|
371
|
+
it 'returns JSON for version 3' do
|
372
|
+
example = JsonFoobar.create(foo: 'FOO', baz: 'BAZ', bar: 'BAR')
|
373
|
+
expect(example.as_json(properties: :short, version: :v3)).to eq(:foo => 'FOO', 'Naz' => 'BAZ', :default_foo => 'DEFAULT_FOO')
|
374
|
+
end
|
375
|
+
it "returns default JSON for version 4 that hasn't been declared" do
|
376
|
+
example = JsonFoobar.create(foo: 'FOO', baz: 'BAZ', bar: 'BAR')
|
377
|
+
expect(example.as_json(properties: :short, version: :v4)).to eq(foo: 'FOO', default_foo: 'DEFAULT_FOO')
|
378
|
+
end
|
379
|
+
it 'returns JSON for the default version' do
|
380
|
+
Mongoid::CachedJson.config.default_version = :v2
|
381
|
+
example = JsonFoobar.create(foo: 'FOO', baz: 'BAZ', bar: 'BAR')
|
382
|
+
expect(example.as_json(properties: :short)).to eq(:foo => 'FOO', 'Taz' => 'BAZ', 'Naz' => 'BAZ', :default_foo => 'DEFAULT_FOO')
|
383
|
+
end
|
384
|
+
it 'returns correct JSON for Person used in README' do
|
385
|
+
person = Person.create(first: 'John', middle: 'F.', last: 'Kennedy', born: 'May 29, 1917')
|
386
|
+
expect(person.as_json).to eq(name: 'John F. Kennedy')
|
387
|
+
expect(person.as_json(version: :v2)).to eq(first: 'John', middle: 'F.', last: 'Kennedy', name: 'John F. Kennedy')
|
388
|
+
expect(person.as_json(version: :v3)).to eq(first: 'John', middle: 'F.', last: 'Kennedy', name: 'John F. Kennedy', born: 'May 29, 1917')
|
389
|
+
end
|
390
|
+
end
|
391
|
+
context 'polymorphic objects' do
|
392
|
+
before(:each) do
|
393
|
+
@json_embedded_foobar = JsonEmbeddedFoobar.new(foo: 'embedded')
|
394
|
+
@json_referenced_foobar = JsonReferencedFoobar.new(foo: 'referenced')
|
395
|
+
@json_parent_foobar = JsonParentFoobar.create(
|
396
|
+
json_polymorphic_embedded_foobar: @json_embedded_foobar,
|
397
|
+
json_polymorphic_referenced_foobar: @json_referenced_foobar
|
398
|
+
)
|
399
|
+
@json_referenced_foobar.save!
|
400
|
+
|
401
|
+
# Cache...
|
402
|
+
[:all, :short, :public].each do |prop|
|
403
|
+
@json_parent_foobar.as_json(properties: prop)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
it 'returns correct JSON when a child (embedded) polymorphic document is changed' do
|
407
|
+
expect(@json_parent_foobar.as_json(properties: :all)[:json_polymorphic_embedded_foobar][:foo]).to eq('embedded')
|
408
|
+
expect(@json_embedded_foobar.as_json(properties: :all)[:foo]).to eq('embedded')
|
409
|
+
@json_embedded_foobar.update_attributes!(foo: 'EMBEDDED')
|
410
|
+
expect(@json_embedded_foobar.as_json(properties: :all)[:foo]).to eq('EMBEDDED')
|
411
|
+
expect(@json_parent_foobar.as_json(properties: :all)[:json_polymorphic_embedded_foobar][:foo]).to eq('EMBEDDED')
|
412
|
+
end
|
413
|
+
it 'returns correct JSON when a child (referenced) polymorphic document is changed' do
|
414
|
+
expect(@json_parent_foobar.as_json(properties: :all)[:json_polymorphic_referenced_foobar][:foo]).to eq('referenced')
|
415
|
+
expect(@json_referenced_foobar.as_json(properties: :all)[:foo]).to eq('referenced')
|
416
|
+
@json_referenced_foobar.update_attributes!(foo: 'REFERENCED')
|
417
|
+
expect(@json_referenced_foobar.as_json(properties: :all)[:foo]).to eq('REFERENCED')
|
418
|
+
expect(@json_parent_foobar.as_json(properties: :all)[:json_polymorphic_referenced_foobar][:foo]).to eq('REFERENCED')
|
419
|
+
end
|
420
|
+
end
|
421
|
+
context 'polymorhphic relationships' do
|
422
|
+
before :each do
|
423
|
+
@company = PolyCompany.create!
|
424
|
+
@company_post = PolyPost.create!(postable: @company)
|
425
|
+
@person = PolyPerson.create!
|
426
|
+
@person_post = PolyPost.create!(postable: @person)
|
427
|
+
end
|
428
|
+
it 'returns the correct JSON' do
|
429
|
+
expect(@company_post.as_json).to eq(parent: { id: @company.id, type: 'PolyCompany' })
|
430
|
+
expect(@person_post.as_json).to eq(parent: { id: @person.id, type: 'PolyPerson' })
|
431
|
+
end
|
432
|
+
end
|
433
|
+
context 'cache key' do
|
434
|
+
it 'correctly generates a cached json key' do
|
435
|
+
example = JsonFoobar.create(foo: 'FOO', baz: 'BAZ', bar: 'BAR')
|
436
|
+
expect(JsonFoobar.cached_json_key({ properties: :short, is_top_level_json: true, version: :v1 }, example.class, example.id)).to eq("as_json/v1/JsonFoobar/#{example.id}/short/true")
|
437
|
+
end
|
438
|
+
end
|
439
|
+
context 'embeds_many relationships' do
|
440
|
+
before :each do
|
441
|
+
@cell = PrisonCell.create!(number: 42)
|
442
|
+
@cell.inmates.create!(nickname: 'Joe', person: Person.create!(first: 'Joe'))
|
443
|
+
@cell.inmates.create!(nickname: 'Bob', person: Person.create!(first: 'Bob'))
|
444
|
+
end
|
445
|
+
it 'returns the correct JSON' do
|
446
|
+
expect(@cell.as_json(properties: :all)).to eq(number: 42,
|
447
|
+
inmates: [
|
448
|
+
{ nickname: 'Joe', person: { name: 'Joe' } },
|
449
|
+
{ nickname: 'Bob', person: { name: 'Bob' } }
|
450
|
+
]
|
451
|
+
)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
context 'with repeated objects in the JSON' do
|
455
|
+
before :each do
|
456
|
+
@cell = PrisonCell.create!(number: 42)
|
457
|
+
@person = Person.create!(first: 'Evil')
|
458
|
+
@cell.inmates.create!(nickname: 'Joe', person: @person)
|
459
|
+
@cell.inmates.create!(nickname: 'Bob', person: @person)
|
460
|
+
end
|
461
|
+
it 'returns the correct JSON' do
|
462
|
+
expect(@cell.as_json(properties: :all)).to eq(number: 42,
|
463
|
+
inmates: [
|
464
|
+
{ nickname: 'Joe', person: { name: 'Evil' } },
|
465
|
+
{ nickname: 'Bob', person: { name: 'Evil' } }
|
466
|
+
]
|
467
|
+
)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
context 'belongs_to relationship' do
|
471
|
+
before :each do
|
472
|
+
@tool = Tool.create!(name: 'hammer')
|
473
|
+
end
|
474
|
+
it 'returns a nil reference' do
|
475
|
+
expect(@tool.as_json(properties: :all)).to eq(tool_box: nil, name: 'hammer')
|
476
|
+
end
|
477
|
+
context 'persisted' do
|
478
|
+
before :each do
|
479
|
+
@tool_box = ToolBox.create!(color: 'red')
|
480
|
+
@tool.update_attributes!(tool_box: @tool_box)
|
481
|
+
end
|
482
|
+
it 'returns a reference' do
|
483
|
+
expect(@tool.as_json(properties: :all)).to eq(tool_box: { color: 'red' }, name: 'hammer')
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
context 'many-to-many relationships' do
|
488
|
+
before :each do
|
489
|
+
@image = FastJsonImage.create!
|
490
|
+
end
|
491
|
+
it 'resolves a default empty relationship' do
|
492
|
+
expect(@image.as_json(properties: :all)).to eq(name: 'Image', urls: [])
|
493
|
+
end
|
494
|
+
it 'resolves a nil relationship on destroy' do
|
495
|
+
@image.destroy
|
496
|
+
expect(@image.as_json(properties: :all)).to eq(name: 'Image', urls: [])
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|