standardapi 6.0.0.32 → 7.1.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +80 -58
  3. data/lib/standard_api/access_control_list.rb +148 -0
  4. data/lib/standard_api/controller.rb +116 -27
  5. data/lib/standard_api/helpers.rb +17 -10
  6. data/lib/standard_api/includes.rb +9 -0
  7. data/lib/standard_api/middleware/query_encoding.rb +3 -3
  8. data/lib/standard_api/middleware.rb +5 -0
  9. data/lib/standard_api/railtie.rb +30 -2
  10. data/lib/standard_api/route_helpers.rb +64 -14
  11. data/lib/standard_api/test_case/calculate_tests.rb +15 -8
  12. data/lib/standard_api/test_case/create_tests.rb +7 -9
  13. data/lib/standard_api/test_case/destroy_tests.rb +19 -7
  14. data/lib/standard_api/test_case/index_tests.rb +10 -6
  15. data/lib/standard_api/test_case/schema_tests.rb +7 -1
  16. data/lib/standard_api/test_case/show_tests.rb +8 -7
  17. data/lib/standard_api/test_case/update_tests.rb +15 -15
  18. data/lib/standard_api/test_case.rb +13 -3
  19. data/lib/standard_api/version.rb +1 -1
  20. data/lib/standard_api/views/application/_record.json.jbuilder +18 -17
  21. data/lib/standard_api/views/application/_record.streamer +40 -37
  22. data/lib/standard_api/views/application/_schema.json.jbuilder +20 -8
  23. data/lib/standard_api/views/application/_schema.streamer +22 -8
  24. data/lib/standard_api/views/application/new.streamer +1 -1
  25. data/lib/standard_api.rb +5 -0
  26. data/test/standard_api/caching_test.rb +14 -4
  27. data/test/standard_api/controller/include_test.rb +107 -0
  28. data/test/standard_api/controller/subresource_test.rb +157 -0
  29. data/test/standard_api/helpers_test.rb +34 -17
  30. data/test/standard_api/nested_attributes/belongs_to_test.rb +71 -0
  31. data/test/standard_api/nested_attributes/has_and_belongs_to_many_test.rb +70 -0
  32. data/test/standard_api/nested_attributes/has_many_test.rb +85 -0
  33. data/test/standard_api/nested_attributes/has_one_test.rb +71 -0
  34. data/test/standard_api/route_helpers_test.rb +56 -0
  35. data/test/standard_api/standard_api_test.rb +182 -44
  36. data/test/standard_api/test_app/app/controllers/acl/account_acl.rb +15 -0
  37. data/test/standard_api/test_app/app/controllers/acl/camera_acl.rb +7 -0
  38. data/test/standard_api/test_app/app/controllers/acl/photo_acl.rb +13 -0
  39. data/test/standard_api/test_app/app/controllers/acl/property_acl.rb +33 -0
  40. data/test/standard_api/test_app/app/controllers/acl/reference_acl.rb +7 -0
  41. data/test/standard_api/test_app/controllers.rb +28 -43
  42. data/test/standard_api/test_app/models.rb +76 -7
  43. data/test/standard_api/test_app/test/factories.rb +7 -3
  44. data/test/standard_api/test_app/views/photos/_photo.json.jbuilder +1 -0
  45. data/test/standard_api/test_app/views/photos/_photo.streamer +2 -1
  46. data/test/standard_api/test_app/views/sessions/create.json.jbuilder +1 -0
  47. data/test/standard_api/test_app/views/sessions/create.streamer +3 -0
  48. data/test/standard_api/test_app.rb +12 -1
  49. data/test/standard_api/test_helper.rb +21 -0
  50. metadata +59 -16
@@ -1,46 +1,49 @@
1
1
  json.object! do
2
-
3
- record.attributes.each do |name, value|
2
+
3
+ record.attribute_names.each do |name|
4
4
  # Skip if attribute is included in excludes
5
- next if defined?(excludes) && excludes[record.model_name.singular.to_sym].try(:find) { |x| x.to_s == name.to_s }
6
- json.set! name, value
5
+ next if defined?(excludes) && excludes[record.model_name.singular.to_sym].try(:find) { |x| x.to_s == name }
6
+
7
+ serialize_attribute(json, record, name, record.type_for_attribute(name).type)
7
8
  end
8
9
 
9
10
  includes.each do |inc, subinc|
10
11
  next if ["limit", "offset", "order", "when", "where", "distinct", "distinct_on"].include?(inc)
11
-
12
+
12
13
  case association = record.class.reflect_on_association(inc)
13
- when ActiveRecord::Reflection::HasManyReflection, ActiveRecord::Reflection::HasAndBelongsToManyReflection, ActiveRecord::Reflection::ThroughReflection
14
- can_cache = can_cache_relation?(record.class, inc, subinc)
15
- json.cache_if!(can_cache, can_cache ? association_cache_key(record, inc, subinc) : nil) do
16
- partial = model_partial(association.klass)
17
- json.set! inc do
18
- # TODO limit causes preloaded assocations to reload
19
- sub_records = record.send(inc)
14
+ when ActiveRecord::Reflection::AbstractReflection
15
+ if association.collection?
16
+ can_cache = can_cache_relation?(record, inc, subinc)
17
+ json.cache_if!(can_cache, can_cache ? [inc, association_cache_key(record, inc, subinc)] : nil) do
18
+ json.set! inc do
19
+ partial = model_partial(association.klass)
20
+ # TODO limit causes preloaded assocations to reload
21
+ sub_records = record.send(inc)
20
22
 
21
- sub_records = sub_records.limit(subinc['limit']) if subinc['limit']
22
- sub_records = sub_records.offset(subinc['offset']) if subinc['offset']
23
- sub_records = sub_records.order(subinc['order']) if subinc['order']
24
- sub_records = sub_records.filter(subinc['where']) if subinc['where']
25
- sub_records = sub_records.distinct if subinc['distinct']
26
- sub_records = sub_records.distinct_on(subinc['distinct_on']) if subinc['distinct_on']
23
+ sub_records = sub_records.limit(subinc["limit"]) if subinc["limit"]
24
+ sub_records = sub_records.offset(subinc["offset"]) if subinc["offset"]
25
+ sub_records = sub_records.reorder(subinc["order"]) if subinc["order"]
26
+ sub_records = sub_records.filter(subinc["where"]) if subinc["where"]
27
+ sub_records = sub_records.distinct if subinc["distinct"]
28
+ sub_records = sub_records.distinct_on(subinc["distinct_on"]) if subinc["distinct_on"]
27
29
 
28
- json.array! sub_records, partial: partial, as: partial.split('/').last, locals: { includes: subinc }
30
+ json.array! sub_records, partial: partial, as: partial.split("/").last, locals: { includes: subinc }
31
+ end
29
32
  end
30
- end
31
- when ActiveRecord::Reflection::BelongsToReflection, ActiveRecord::Reflection::HasOneReflection
32
- can_cache = can_cache_relation?(record.class, inc, subinc)
33
- if association.is_a?(ActiveRecord::Reflection::BelongsToReflection)
34
- can_cache = can_cache && !record.send(association.foreign_key).nil?
35
- end
36
- json.cache_if!(can_cache, can_cache ? association_cache_key(record, inc, subinc) : nil) do
37
- value = record.send(inc)
38
- if value.nil?
39
- json.set! inc, nil
40
- else
41
- partial = model_partial(value)
33
+ else
34
+ can_cache = can_cache_relation?(record, inc, subinc)
35
+ if association.is_a?(ActiveRecord::Reflection::BelongsToReflection)
36
+ can_cache = can_cache && !record.send(association.foreign_key).nil?
37
+ end
38
+ json.cache_if!(can_cache, can_cache ? [inc, association_cache_key(record, inc, subinc)] : nil) do
42
39
  json.set! inc do
43
- json.partial! partial, partial.split('/').last.to_sym => value, includes: subinc
40
+ value = record.send(inc)
41
+ if value.nil?
42
+ json.value! nil
43
+ else
44
+ partial = model_partial(value)
45
+ json.partial! partial, partial.split("/").last.to_sym => value, includes: subinc
46
+ end
44
47
  end
45
48
  end
46
49
  end
@@ -52,20 +55,20 @@ json.object! do
52
55
  elsif value.is_a?(ActiveModel::Model)
53
56
  json.set! inc do
54
57
  partial = model_partial(value)
55
- json.partial! partial, partial.split('/').last.to_sym => value, includes: subinc
58
+ json.partial! partial, partial.split("/").last.to_sym => value, includes: subinc
56
59
  end
57
60
  else
58
61
  json.set! inc, value.as_json
59
62
  end
60
63
  end
61
64
  end
62
-
65
+
63
66
  end
64
-
67
+
65
68
  if !record.errors.blank?
66
69
  errs = record.errors.to_hash
67
70
  errs.default_proc = nil
68
- json.set! 'errors', errs
71
+ json.set! "errors", errs
69
72
  end
70
-
73
+
71
74
  end
@@ -50,14 +50,26 @@ else
50
50
 
51
51
  json.set! 'attributes' do
52
52
  model.columns.each do |column|
53
- json.set! column.name, {
54
- type: json_column_type(column.sql_type),
55
- default: column.default || column.default_function,
56
- primary_key: column.name == model.primary_key,
57
- null: column.null,
58
- array: column.array,
59
- comment: column.comment
60
- }
53
+ default = column.default ? model.connection.lookup_cast_type_from_column(column).deserialize(column.default) : nil
54
+ type = case model.type_for_attribute(column.name)
55
+ when ActiveRecord::Enum::EnumType
56
+ default = model.defined_enums[column.name].key(default)
57
+ "string"
58
+ else
59
+ json_column_type(column.sql_type)
60
+ end
61
+
62
+ json.set! column.name do
63
+ json.set! 'type', type
64
+ json.set! 'default', default
65
+ json.set! 'primary_key', column.name == model.primary_key
66
+ json.set! 'null', column.null
67
+ json.set! 'array', column.array
68
+ json.set! 'comment', column.comment
69
+ # TODO: it would be nice if rails responded with a true or false here
70
+ # instead of the function itself
71
+ json.set! 'auto_populated', !!column.auto_populated? if column.respond_to?(:auto_populated?)
72
+ end
61
73
  end
62
74
  end
63
75
 
@@ -59,14 +59,28 @@ else
59
59
  json.set! 'attributes' do
60
60
  json.object! do
61
61
  model.columns.each do |column|
62
- json.set! column.name, {
63
- type: json_column_type(column.sql_type),
64
- default: column.default || column.default_function,
65
- primary_key: column.name == model.primary_key,
66
- null: column.null,
67
- array: column.array,
68
- comment: column.comment
69
- }
62
+ default = column.default ? model.connection.lookup_cast_type_from_column(column).deserialize(column.default) : nil
63
+ type = case model.type_for_attribute(column.name)
64
+ when ActiveRecord::Enum::EnumType
65
+ default = model.defined_enums[column.name].key(default)
66
+ "string"
67
+ else
68
+ json_column_type(column.sql_type)
69
+ end
70
+
71
+ json.set! column.name do
72
+ json.object! do
73
+ json.set! 'type', type
74
+ json.set! 'default', default
75
+ json.set! 'primary_key', column.name == model.primary_key
76
+ json.set! 'null', column.null
77
+ json.set! 'array', column.array
78
+ json.set! 'comment', column.comment
79
+ # TODO: it would be nice if rails responded with a true or false here
80
+ # instead of the function itself
81
+ json.set! 'auto_populated', !!column.auto_populated? if column.respond_to?(:auto_populated?)
82
+ end
83
+ end
70
84
  end
71
85
  end
72
86
  end
@@ -1 +1 @@
1
- json.partial! model_partial(model), model_partial(model).split('/').last.to_sym => instance_variable_get("@#{model.model_name.singular}"), includes: includes
1
+ json.partial! model_partial(model), model_partial(model).split('/').last.to_sym => instance_variable_get("@#{model.model_name.singular}"), includes: includes
data/lib/standard_api.rb CHANGED
@@ -16,3 +16,8 @@ require 'standard_api/helpers'
16
16
  require 'standard_api/route_helpers'
17
17
  require 'standard_api/active_record/connection_adapters/postgresql/schema_statements'
18
18
  require 'standard_api/railtie'
19
+
20
+ module StandardAPI
21
+ autoload :AccessControlList, 'standard_api/access_control_list'
22
+ autoload :Middleware, 'standard_api/middleware'
23
+ end
@@ -1,6 +1,6 @@
1
1
  require 'standard_api/test_helper'
2
2
 
3
- class AccountsControllerTest < ActionController::TestCase
3
+ class AccountsControllerTest < ActionDispatch::IntegrationTest
4
4
 
5
5
  test 'include with cache' do
6
6
  account = create(:account, photos: [])
@@ -14,20 +14,30 @@ class AccountsControllerTest < ActionController::TestCase
14
14
 
15
15
  # Cache Miss
16
16
  Account.any_instance.stubs(:photos_cached_at).returns(t1)
17
- get :show, params: {id: account.id, include: :photos}, format: :json
17
+ get account_path(account, include: :photos, format: :json)
18
18
  assert_equal [photo.id], JSON(response.body)['photos'].map{|x| x['id']}
19
19
 
20
20
  # Cache Hit
21
21
  Account.any_instance.stubs(:photos).returns([])
22
22
  Account.any_instance.stubs(:photos_cached_at).returns(t1)
23
- get :show, params: {id: account.id, include: :photos}, format: :json
23
+ get account_path(account, include: :photos, format: :json)
24
24
  assert_equal [photo.id], JSON(response.body)['photos'].map{|x| x['id']}
25
25
 
26
26
  # Cache Miss, photos_cached_at updated
27
27
  Account.any_instance.stubs(:photos).returns(Photo.where('false = true'))
28
28
  Account.any_instance.stubs(:photos_cached_at).returns(t2)
29
- get :show, params: {id: account.id, include: :photos}, format: :json
29
+ get account_path(account, include: :photos, format: :json)
30
30
  assert_equal [], JSON(response.body)['photos'].map{|x| x['id']}
31
+
32
+ # Two associations that reference the same model
33
+ property = create(:property)
34
+ account = create(:account, property: property, subject: property)
35
+ Account.any_instance.expects(:property_cached_at).twice.returns(t1)
36
+ Account.any_instance.expects(:subject_cached_at).twice.returns(t1)
37
+ get account_path(account, include: { property: true, subject: true }, format: 'json')
38
+ json = JSON(response.body)
39
+ assert json.has_key?('property')
40
+ assert json.has_key?('subject')
31
41
  end
32
42
 
33
43
  end
@@ -0,0 +1,107 @@
1
+ require 'standard_api/test_helper'
2
+
3
+ class ControllerIncludesTest < ActionDispatch::IntegrationTest
4
+
5
+ # = Including an invalid include
6
+
7
+ test "Controller#create with a valid include" do
8
+ property = build(:property)
9
+
10
+ json = assert_difference 'Property.count', 1 do
11
+ post "/properties", params: { property: property.attributes, include: [:photos] }, as: :json
12
+ JSON.parse(response.body)
13
+ end
14
+
15
+ assert_response :created
16
+ assert_equal [], json['photos']
17
+ end
18
+
19
+ test "Controller#update with a valid include" do
20
+ photo = create(:photo)
21
+ property = create(:property, {name: "A", photos: [photo]})
22
+ patch "/properties/#{property.id}", params: { property: {name: "B"}, include: [:photos] }, as: :json
23
+
24
+ json = JSON.parse(response.body)
25
+ assert_response :ok
26
+ assert_equal [photo.id], json['photos'].map { |j| j['id'] }
27
+ end
28
+
29
+ # test "Controller#destroy with a valid include" do
30
+ # No body is returned on a destroy so includes are used
31
+ # end
32
+
33
+ test "Controller#create_resource with a valid include" do
34
+ property = create(:property, accounts: [])
35
+ account = build(:account)
36
+
37
+ post "/properties/#{property.id}/accounts", params: { account: account.attributes, include: [:photos] }, as: :json
38
+
39
+ json = JSON.parse(response.body)
40
+ assert_response :created
41
+ assert_equal [], json['photos']
42
+ end
43
+
44
+ # test "Controller#add_resource with a valid include" do
45
+ # No body is returned on a destroy so includes are used
46
+ # end
47
+
48
+ # test "Controller#remove_resource with an invalid include" do
49
+ # No body is returned on a destroy so includes are used
50
+ # end
51
+
52
+ # = Including an invalid include
53
+
54
+ test "Controller#create with an invalid include" do
55
+ property = build(:property)
56
+
57
+ assert_no_difference 'Property.count' do
58
+ post "/properties", params: { property: property.attributes, include: [:accounts] }, as: :json
59
+ end
60
+
61
+ assert_response :bad_request
62
+ end
63
+
64
+ test "Controller#update with an invalid include" do
65
+ property = create(:property, {name: "A"})
66
+ patch "/properties/#{property.id}", params: { property: {name: "B"}, include: [:accounts] }, as: :json
67
+
68
+ assert_response :bad_request
69
+ assert_equal 'A', property.reload.name
70
+ end
71
+
72
+ # test "Controller#destroy with an invalid include" do
73
+ # No body is returned on a destroy so includes are used
74
+ # end
75
+
76
+ test "Controller#create_resource with an invalid include" do
77
+ property = create(:property, photos: [])
78
+ photo = build(:photo)
79
+
80
+ post "/properties/#{property.id}/photos", params: { photo: photo.attributes, include: [:camera] }, as: :json
81
+ assert_equal 0, property.reload.photos.count
82
+ assert_response :bad_request
83
+ end
84
+
85
+ # This test passes because includes are not used, response is a HEAD response
86
+ # and no includes are used.
87
+ test "Controller#add_resource with an invalid include" do
88
+ property = create(:property, photos: [])
89
+ photo = create(:photo)
90
+
91
+ post "/properties/#{property.id}/photos/#{photo.id}", params: { include: [:camera] }, as: :json
92
+ assert_equal 1, property.reload.photos.count
93
+ assert_response :created
94
+ end
95
+
96
+ # This test passes because includes are not used, response is a HEAD response
97
+ # and no includes are used.
98
+ test "Controller#remove_resource with an invalid include" do
99
+ photo = create(:photo)
100
+ property = create(:property, photos: [photo])
101
+
102
+ delete "/properties/#{property.id}/photos/#{photo.id}", params: { include: [:camera] }, as: :json
103
+ assert_equal 0, property.reload.photos.count
104
+ assert_response :no_content
105
+ end
106
+
107
+ end
@@ -0,0 +1,157 @@
1
+ require 'standard_api/test_helper'
2
+
3
+ class ControllerSubresourceTest < ActionDispatch::IntegrationTest
4
+
5
+ # add_resource
6
+ test 'Controller#add_resource with has_many' do
7
+ property = create(:property, photos: [])
8
+ photo = create(:photo)
9
+
10
+ post "/properties/#{property.id}/photos/#{photo.id}"
11
+ assert_equal property.photos.reload.map(&:id), [photo.id]
12
+ assert_response :created
13
+
14
+ assert_raises ActiveRecord::RecordNotFound do
15
+ post "/properties/#{property.id}/photos/9999999"
16
+ end
17
+ end
18
+
19
+ test 'Controller#add_resource with has_and_belongs_to_many' do
20
+ photo1 = create(:photo)
21
+ photo2 = create(:photo)
22
+ property = create(:property, photos: [photo1])
23
+
24
+ post "/properties/#{property.id}/photos/#{photo2.id}"
25
+ assert_equal property.photos.reload.map(&:id), [photo1.id, photo2.id]
26
+ assert_response :created
27
+
28
+ assert_raises ActiveRecord::RecordNotFound do
29
+ post "/properties/#{property.id}/photos/9999999"
30
+ end
31
+ end
32
+
33
+ test 'Controller#add_resource with belongs_to' do
34
+ photo = create(:photo)
35
+ account = create(:account)
36
+
37
+ post "/photos/#{photo.id}/account/#{account.id}"
38
+ assert_equal photo.reload.account_id, account.id
39
+ assert_response :created
40
+ end
41
+
42
+ test 'Controller#add_resource with has_one' do
43
+ photo = create(:document)
44
+ property = create(:property)
45
+ post "/properties/#{property.id}/document/#{photo.id}"
46
+ assert_equal property.reload.document, photo
47
+ assert_response :created
48
+ end
49
+
50
+ test 'Controller#add_resource that is already there' do
51
+ photo = create(:photo)
52
+ property = create(:property, photos: [photo])
53
+
54
+ post "/properties/#{property.id}/photos/#{photo.id}"
55
+ assert_response :bad_request
56
+ assert_equal JSON(response.body), {
57
+ "errors" => [
58
+ "Relationship between Property and Photo violates unique constraints"
59
+ ]
60
+ }
61
+
62
+ assert_raises ActiveRecord::RecordNotFound do
63
+ post "/properties/#{property.id}/photos/9999999"
64
+ end
65
+ end
66
+
67
+ # create_resource
68
+ test 'Controller#create_resource with has_many' do
69
+ property = create(:property, photos: [])
70
+ photo = build(:photo)
71
+
72
+ post "/properties/#{property.id}/photos", params: { photo: photo.attributes }, as: :json
73
+ assert_equal property.photos.reload.map(&:id), [JSON.parse(response.body)['id']]
74
+ assert_equal property.photos.count, 1
75
+ assert_response :created
76
+ end
77
+
78
+ test 'Controller#create_resource with has_and_belongs_to_many' do
79
+ photo1 = create(:photo)
80
+ photo2 = build(:photo)
81
+ property = create(:property, photos: [photo1])
82
+
83
+ post "/properties/#{property.id}/photos", params: { photo: photo2.attributes }, as: :json
84
+ assert_equal property.photos.reload.map(&:id).sort, [photo1.id, JSON.parse(response.body)['id']].sort
85
+ assert_equal property.photos.count, 2
86
+ assert_response :created
87
+ end
88
+
89
+ test 'Controller#create_resource with belongs_to' do
90
+ photo = create(:photo)
91
+ account = build(:account)
92
+
93
+ post "/photos/#{photo.id}/account", params: { account: account.attributes }, as: :json
94
+ assert_equal photo.reload.account_id, JSON.parse(response.body)['id']
95
+ assert_response :created
96
+ end
97
+
98
+ test 'Controller#create_resource with has_one' do
99
+ account = build(:account)
100
+ property = create(:property)
101
+ post "/properties/#{property.id}/landlord", params: { landlord: account.attributes }, as: :json
102
+ assert_equal property.reload.landlord.id, JSON.parse(response.body)['id']
103
+ assert_response :created
104
+ end
105
+
106
+ # remove_resource
107
+ test 'Controller#remove_resource' do
108
+ photo = create(:photo)
109
+ property = create(:property, photos: [photo])
110
+ assert_equal property.photos.reload, [photo]
111
+ delete "/properties/#{property.id}/photos/#{photo.id}"
112
+ assert_equal property.photos.reload, []
113
+ assert_response :no_content
114
+
115
+ assert_raises ActiveRecord::RecordNotFound do
116
+ delete "/properties/#{property.id}/photos/9999999"
117
+ end
118
+ end
119
+
120
+ test 'Controller#remove_resource with has_one' do
121
+ photo = create(:document)
122
+ property = create(:property, document: photo)
123
+ assert_equal property.document, photo
124
+ delete "/properties/#{property.id}/document/#{photo.id}"
125
+ assert_nil property.reload.document
126
+ assert_response :no_content
127
+ end
128
+
129
+ test 'Controller#remove_resource with belongs_to' do
130
+ account = create(:account)
131
+ photo = create(:photo, account: account)
132
+
133
+ delete "/photos/#{photo.id}/account/#{account.id}"
134
+ assert_nil photo.reload.account_id
135
+ assert_response :no_content
136
+ end
137
+
138
+ test 'Controller#remove_resource with belongs_to unless not match' do
139
+ account1 = create(:account)
140
+ account2 = create(:account)
141
+ photo = create(:photo, account: account1)
142
+
143
+ delete "/photos/#{photo.id}/account/#{account2.id}"
144
+ assert_equal photo.reload.account_id, account1.id
145
+ assert_response :not_found
146
+ end
147
+
148
+ test 'Controller#remove_resource with belongs_to unless not match and is nil' do
149
+ account = create(:account)
150
+ photo = create(:photo)
151
+
152
+ delete "/photos/#{photo.id}/account/#{account.id}"
153
+ assert_nil photo.reload.account_id
154
+ assert_response :not_found
155
+ end
156
+
157
+ end
@@ -45,86 +45,102 @@ class HelpersTest < ActionView::TestCase
45
45
  assert can_cache?(Account, {photos: {account: {}}})
46
46
  end
47
47
 
48
- test '::can_cache_relation?' do
48
+ test '::can_cache_relation? with non-persisted record' do
49
+ account = build(:account)
50
+ assert !can_cache_relation?(account, :photos, {})
51
+ assert !can_cache_relation?(account, :photos, {})
52
+ assert !can_cache_relation?(account, :photos, {account: {}})
53
+ assert !can_cache_relation?(account, :photos, {account: {}})
54
+ end
55
+
56
+ test '::can_cache_relation? with persisted record' do
57
+ account = create(:account)
58
+
49
59
  Account.expects(:column_names).returns(['id', 'cached_at'])
50
- assert !can_cache_relation?(Account, :photos, {})
60
+ assert !can_cache_relation?(account, :photos, {})
51
61
 
52
62
  Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at'])
53
- assert can_cache_relation?(Account, :photos, {})
63
+ assert can_cache_relation?(account, :photos, {})
54
64
 
55
65
  Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at'])
56
- assert !can_cache_relation?(Account, :photos, {account: {}})
66
+ assert !can_cache_relation?(account, :photos, {account: {}})
57
67
 
58
68
  Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_account_cached_at'])
59
- assert can_cache_relation?(Account, :photos, {account: {}})
69
+ assert can_cache_relation?(account, :photos, {account: {}})
60
70
  end
61
71
 
62
72
  test '::association_cache_key(record, relation, subincludes)' do
63
73
  account = create(:account)
74
+ photo = create(:photo, account: account)
64
75
  t1 = Time.now
65
76
  t2 = 1.day.from_now
66
77
  t3 = 2.days.from_now
67
78
 
79
+ Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at'])
68
80
  account.expects(:photos_cached_at).returns(t1)
69
81
 
70
82
  assert_equal(
71
- "accounts/#{account.id}/photos-#{t1.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
83
+ "accounts/#{account.id}/photos-#{t1.utc.to_fs(ActiveRecord::Base.cache_timestamp_format)}",
72
84
  association_cache_key(account, :photos, {})
73
85
  )
74
86
 
75
-
87
+ Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at'])
76
88
  account.expects(:photos_cached_at).returns(t1)
77
89
  account.expects(:photos_property_cached_at).returns(t2)
78
90
  assert_equal(
79
- "accounts/#{account.id}/photos-2ea683a694a33359514c41435f8f0646-#{t2.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
91
+ "accounts/#{account.id}/photos-2ea683a694a33359514c41435f8f0646-#{t2.utc.to_fs(ActiveRecord::Base.cache_timestamp_format)}",
80
92
  association_cache_key(account, :photos, {property: {}})
81
93
  )
82
94
 
95
+ Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at'])
83
96
  account.expects(:photos_cached_at).returns(t1)
84
97
  account.expects(:photos_property_cached_at).returns(t2)
85
98
  assert_equal(
86
- "accounts/#{account.id}/photos-779c17ef027655fd8c06c3083d2df64b-#{t2.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
99
+ "accounts/#{account.id}/photos-779c17ef027655fd8c06c3083d2df64b-#{t2.utc.to_fs(ActiveRecord::Base.cache_timestamp_format)}",
87
100
  association_cache_key(account, :photos, { "property" => { "order" => { "x" => "desc" }}})
88
101
  )
89
102
 
103
+ Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at', 'photos_agents_cached_at'])
90
104
  account.expects(:photos_cached_at).returns(t1)
91
105
  account.expects(:photos_property_cached_at).returns(t2)
92
106
  account.expects(:photos_agents_cached_at).returns(t3)
93
107
  assert_equal(
94
- "accounts/#{account.id}/photos-abbee2d4535400c162c8dbf14bbef6d5-#{t3.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
108
+ "accounts/#{account.id}/photos-abbee2d4535400c162c8dbf14bbef6d5-#{t3.utc.to_fs(ActiveRecord::Base.cache_timestamp_format)}",
95
109
  association_cache_key(account, :photos, {property: {}, agents: {}})
96
110
  )
97
111
 
112
+ Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at', 'photos_property_agents_cached_at'])
98
113
  account.expects(:photos_cached_at).returns(t1)
99
114
  account.expects(:photos_property_cached_at).returns(t2)
100
115
  account.expects(:photos_property_agents_cached_at).returns(t3)
101
116
  assert_equal(
102
- "accounts/#{account.id}/photos-0962ae73347c5c605d329eaa25e2be49-#{t3.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
117
+ "accounts/#{account.id}/photos-0962ae73347c5c605d329eaa25e2be49-#{t3.utc.to_fs(ActiveRecord::Base.cache_timestamp_format)}",
103
118
  association_cache_key(account, :photos, {property: {agents: {}}})
104
119
  )
105
120
 
121
+ Account.expects(:column_names).returns(['id', 'cached_at', 'photos_cached_at', 'photos_property_cached_at', 'photos_agents_cached_at', 'photos_property_addresses_cached_at'])
106
122
  account.expects(:photos_cached_at).returns(t1)
107
123
  account.expects(:photos_property_cached_at).returns(t2)
108
124
  account.expects(:photos_agents_cached_at).returns(t2)
109
125
  account.expects(:photos_property_addresses_cached_at).returns(t3)
110
126
  assert_equal(
111
- "accounts/#{account.id}/photos-00ea6afe3ff68037f8b4dcdb275e2a24-#{t3.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
127
+ "accounts/#{account.id}/photos-00ea6afe3ff68037f8b4dcdb275e2a24-#{t3.utc.to_fs(ActiveRecord::Base.cache_timestamp_format)}",
112
128
  association_cache_key(account, :photos, {property: {addresses: {}}, agents: {}})
113
129
  )
114
130
 
115
131
  # Belongs to
116
- photo = create(:photo, account: account)
132
+ Photo.expects(:column_names).returns(['id', 'cached_at', 'account_cached_at'])
117
133
  photo.expects(:account_cached_at).returns(t1)
118
134
  assert_equal(
119
- "accounts/#{account.id}-#{t1.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
135
+ "accounts/#{account.id}-#{t1.utc.to_fs(ActiveRecord::Base.cache_timestamp_format)}",
120
136
  association_cache_key(photo, :account, {})
121
137
  )
122
138
 
123
- photo = create(:photo, account: account)
139
+ Photo.expects(:column_names).returns(['id', 'cached_at', 'account_cached_at', 'account_photos_cached_at'])
124
140
  photo.expects(:account_cached_at).returns(t1)
125
141
  photo.expects(:account_photos_cached_at).returns(t2)
126
142
  assert_equal(
127
- "accounts/#{account.id}/07437ce3863467f4cd715ae1ef930f08-#{t2.utc.to_s(ActiveRecord::Base.cache_timestamp_format)}",
143
+ "accounts/#{account.id}/07437ce3863467f4cd715ae1ef930f08-#{t2.utc.to_fs(ActiveRecord::Base.cache_timestamp_format)}",
128
144
  association_cache_key(photo, :account, {photos: {}})
129
145
  )
130
146
  end
@@ -134,6 +150,7 @@ class HelpersTest < ActionView::TestCase
134
150
  assert_equal 'string', json_column_type('character varying(2)')
135
151
  assert_equal 'string', json_column_type('character varying(255)')
136
152
  assert_equal 'datetime', json_column_type('timestamp without time zone')
153
+ assert_equal 'datetime', json_column_type('timestamp(6) without time zone')
137
154
  assert_equal 'datetime', json_column_type('time without time zone')
138
155
  assert_equal 'string', json_column_type('text')
139
156
  assert_equal 'hash', json_column_type('json')
@@ -151,6 +168,6 @@ class HelpersTest < ActionView::TestCase
151
168
  assert_equal 'boolean', json_column_type('boolean')
152
169
  assert_equal 'ewkb', json_column_type('geometry')
153
170
  assert_equal 'string', json_column_type('uuid')
154
- end
171
+ end
155
172
 
156
173
  end