contentful_model 0.2.0 → 1.0.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 (84) hide show
  1. checksums.yaml +5 -5
  2. data/lib/contentful_model.rb +17 -13
  3. data/lib/contentful_model/asset.rb +30 -0
  4. data/lib/contentful_model/asset_dimension_query.rb +70 -0
  5. data/lib/contentful_model/asset_dimensions.rb +54 -0
  6. data/lib/contentful_model/associations/associations.rb +10 -4
  7. data/lib/contentful_model/associations/belongs_to.rb +9 -4
  8. data/lib/contentful_model/associations/belongs_to_many.rb +22 -45
  9. data/lib/contentful_model/associations/has_many.rb +15 -13
  10. data/lib/contentful_model/associations/has_many_nested.rb +39 -37
  11. data/lib/contentful_model/associations/has_one.rb +22 -14
  12. data/lib/contentful_model/base.rb +115 -74
  13. data/lib/contentful_model/client.rb +14 -3
  14. data/lib/contentful_model/errors.rb +0 -1
  15. data/lib/contentful_model/manageable.rb +49 -21
  16. data/lib/contentful_model/management.rb +1 -0
  17. data/lib/contentful_model/migrations/content_type.rb +24 -24
  18. data/lib/contentful_model/migrations/content_type_factory.rb +9 -6
  19. data/lib/contentful_model/migrations/migration.rb +9 -4
  20. data/lib/contentful_model/queries.rb +60 -9
  21. data/lib/contentful_model/query.rb +148 -5
  22. data/lib/contentful_model/validations/lambda_validation.rb +17 -0
  23. data/lib/contentful_model/validations/validates_presence_of.rb +4 -1
  24. data/lib/contentful_model/validations/validations.rb +56 -8
  25. data/lib/contentful_model/version.rb +1 -1
  26. data/spec/asset_spec.rb +141 -0
  27. data/spec/associations/belongs_to_many_spec.rb +38 -0
  28. data/spec/associations/belongs_to_spec.rb +22 -0
  29. data/spec/associations/has_many_nested_spec.rb +321 -0
  30. data/spec/associations/has_many_spec.rb +33 -0
  31. data/spec/associations/has_one_spec.rb +32 -0
  32. data/spec/base_spec.rb +199 -0
  33. data/spec/chainable_queries_spec.rb +199 -0
  34. data/spec/client_spec.rb +11 -0
  35. data/spec/contentful_model_spec.rb +25 -0
  36. data/spec/fixtures/vcr_cassettes/asset/all.yml +161 -0
  37. data/spec/fixtures/vcr_cassettes/asset/find.yml +118 -0
  38. data/spec/fixtures/vcr_cassettes/association/belongs_to_many.yml +439 -0
  39. data/spec/fixtures/vcr_cassettes/association/has_many.yml +161 -0
  40. data/spec/fixtures/vcr_cassettes/association/has_one.yml +161 -0
  41. data/spec/fixtures/vcr_cassettes/association/nested_with_root_root.yml +240 -0
  42. data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_higher_include.yml +123 -0
  43. data/spec/fixtures/vcr_cassettes/association/nested_without_root_child_parent.yml +609 -0
  44. data/spec/fixtures/vcr_cassettes/association/nested_without_root_childless.yml +688 -0
  45. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle.yml +319 -0
  46. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_higher_include.yml +161 -0
  47. data/spec/fixtures/vcr_cassettes/association/nested_without_root_middle_parent.yml +489 -0
  48. data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless.yml +331 -0
  49. data/spec/fixtures/vcr_cassettes/association/nested_without_root_parentless_higher_include.yml +82 -0
  50. data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle.yml +161 -0
  51. data/spec/fixtures/vcr_cassettes/associations/nested_without_root_middle_higher_include.yml +82 -0
  52. data/spec/fixtures/vcr_cassettes/base/content_type.yml +185 -0
  53. data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty.yml +202 -0
  54. data/spec/fixtures/vcr_cassettes/base/return_nil_for_empty_with_value.yml +124 -0
  55. data/spec/fixtures/vcr_cassettes/client.yml +98 -0
  56. data/spec/fixtures/vcr_cassettes/dog.yml +161 -0
  57. data/spec/fixtures/vcr_cassettes/human.yml +199 -0
  58. data/spec/fixtures/vcr_cassettes/management/client.yml +1362 -0
  59. data/spec/fixtures/vcr_cassettes/management/nyancat.yml +1449 -0
  60. data/spec/fixtures/vcr_cassettes/management/nyancat_2.yml +1449 -0
  61. data/spec/fixtures/vcr_cassettes/management/nyancat_publish.yml +481 -0
  62. data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_fail.yml +558 -0
  63. data/spec/fixtures/vcr_cassettes/management/nyancat_refetch_and_save.yml +620 -0
  64. data/spec/fixtures/vcr_cassettes/management/nyancat_save.yml +479 -0
  65. data/spec/fixtures/vcr_cassettes/management/nyancat_save_2.yml +479 -0
  66. data/spec/fixtures/vcr_cassettes/nyancat.yml +196 -0
  67. data/spec/fixtures/vcr_cassettes/playground/nyancat.yml +177 -0
  68. data/spec/fixtures/vcr_cassettes/query/each_entry.yml +319 -0
  69. data/spec/fixtures/vcr_cassettes/query/each_page.yml +319 -0
  70. data/spec/fixtures/vcr_cassettes/query/empty.yml +180 -0
  71. data/spec/fixtures/vcr_cassettes/query/load.yml +197 -0
  72. data/spec/fixtures/vcr_cassettes/query/manual_pagination.yml +161 -0
  73. data/spec/fixtures/vcr_cassettes/query/nyancat_invalid_elements.yml +247 -0
  74. data/spec/manageable_spec.rb +186 -0
  75. data/spec/management_spec.rb +17 -0
  76. data/spec/migrations/content_type_factory_spec.rb +41 -0
  77. data/spec/migrations/content_type_spec.rb +176 -0
  78. data/spec/migrations/migration_spec.rb +75 -0
  79. data/spec/queries_spec.rb +85 -0
  80. data/spec/query_spec.rb +126 -0
  81. data/spec/spec_helper.rb +58 -0
  82. data/spec/validations/validations_spec.rb +182 -0
  83. metadata +213 -19
  84. data/lib/contentful_model/chainable_queries.rb +0 -104
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ class NameValidation
4
+ def validate(entry)
5
+ return [] if entry.name == 'Nyan Cat'
6
+ ['Invalid Entry']
7
+ end
8
+ end
9
+
10
+ class InvalidatedCat < ContentfulModel::Base
11
+ self.content_type_id = 'cat'
12
+
13
+ validate_with NameValidation.new, on_load: true
14
+ end
15
+
16
+ describe ContentfulModel::Queries do
17
+ before :each do
18
+ ContentfulModel.configure do |config|
19
+ config.space = 'cfexampleapi'
20
+ config.access_token = 'b4c0n73n7fu1'
21
+ config.entry_mapping = {}
22
+ end
23
+
24
+ Cat.client = nil
25
+ Cat.instance_variable_set(:@query, ContentfulModel::Query.new(Cat))
26
+
27
+ InvalidatedCat.client = nil
28
+ InvalidatedCat.instance_variable_set(:@query, ContentfulModel::Query.new(InvalidatedCat))
29
+ end
30
+
31
+ describe 'class methods' do
32
+ describe '::load' do
33
+ it 'returns the base query "/entries"' do
34
+ vcr('query/load') {
35
+ response = Cat.load
36
+ expect(response).to be_a ::Contentful::Array
37
+ expect(response.items.size).to eq 3
38
+ expect(response.total).to eq 3
39
+ }
40
+ end
41
+
42
+ it 'filters out invalid elements' do
43
+ vcr('query/load') {
44
+ response = InvalidatedCat.load
45
+ expect(response).to be_a ::Contentful::Array
46
+ expect(response.items.size).to eq 1
47
+ expect(response.total).to eq 3
48
+ }
49
+ end
50
+ end
51
+
52
+ it '::find' do
53
+ vcr('nyancat') {
54
+ nyancat = Cat.find('nyancat')
55
+ expect(nyancat).to be_a Cat
56
+ expect(nyancat.name).to eq 'Nyan Cat'
57
+ expect(nyancat.id).to eq 'nyancat'
58
+ }
59
+ end
60
+
61
+ it '::paginate' do
62
+ vcr('query/manual_pagination') {
63
+ happy_cat = Cat.paginate(2, 2).load.first
64
+ expect(happy_cat.name).to eq 'Happy Cat'
65
+ }
66
+ end
67
+
68
+ it '::each_page' do
69
+ vcr('query/each_page') {
70
+ Cat.each_page(2) do |page|
71
+ expect(page).to be_a ::Contentful::Array
72
+ expect(page.first).to be_a Cat
73
+ end
74
+ }
75
+ end
76
+
77
+ it '::each_entry' do
78
+ vcr('query/each_entry') {
79
+ Cat.each_entry(2) do |cat|
80
+ expect(cat).to be_a Cat
81
+ end
82
+ }
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ class Foo < ContentfulModel::Base
4
+ end
5
+
6
+ class OneInclude < ContentfulModel::Base
7
+ has_one :foo
8
+ end
9
+
10
+ class MultiInclude < ContentfulModel::Base
11
+ has_one :one_include
12
+ end
13
+
14
+ class CircularInclude < ContentfulModel::Base
15
+ has_many :to_circulars
16
+ end
17
+
18
+ class ToCircular < ContentfulModel::Base
19
+ has_one :circular_include
20
+ end
21
+
22
+ class Bar < ContentfulModel::Base
23
+ end
24
+
25
+ class MultiIncludeWithVaryingReferenceDepth < ContentfulModel::Base
26
+ has_one :one_include
27
+ has_one :bar
28
+ end
29
+
30
+ describe ContentfulModel::Query do
31
+ let(:parameters) { { 'sys.id' => 'foo' } }
32
+ let(:entry) { vcr('nyancat') { Cat.find('nyancat') } }
33
+ subject { described_class.new(Cat, parameters) }
34
+
35
+ before :each do
36
+ ContentfulModel.configure do |config|
37
+ config.space = 'cfexampleapi'
38
+ config.access_token = 'b4c0n73n7fu1'
39
+ config.entry_mapping = {}
40
+ end
41
+ end
42
+
43
+ describe 'attributes' do
44
+ it ':parameters' do
45
+ expect(subject.parameters).to eq parameters
46
+ end
47
+ end
48
+
49
+ describe 'instance_methods' do
50
+ before :each do
51
+ Cat.client = nil
52
+ end
53
+
54
+ it '#<< updates parameters' do
55
+ expect(subject.parameters).to eq parameters
56
+
57
+ subject << {foo: 'bar'}
58
+
59
+ expect(subject.parameters).to eq parameters.merge(foo: 'bar')
60
+ end
61
+
62
+ it '#default_parameters' do
63
+ expect(subject.default_parameters).to eq('content_type' => 'cat')
64
+ end
65
+
66
+ it '#client' do
67
+ vcr('client') {
68
+ expect(subject.client).to eq Cat.client
69
+ }
70
+ end
71
+
72
+ it '#reset' do
73
+ subject << {'foo' => 'bar'}
74
+
75
+ subject.reset
76
+
77
+ expect(subject.parameters).to eq subject.default_parameters
78
+ end
79
+
80
+ describe '#execute' do
81
+ it 'when response is empty' do
82
+ vcr('query/empty') {
83
+ expect(subject.execute.items).to eq []
84
+ }
85
+ end
86
+
87
+ it 'when response contains items' do
88
+ query = described_class.new(Cat, 'sys.id' => 'nyancat')
89
+ vcr('nyancat') {
90
+ entries = query.execute
91
+ expect(entries.first.id).to eq 'nyancat'
92
+ }
93
+ end
94
+ end
95
+
96
+ describe '#discover_includes' do
97
+ it 'defaults to 1 for a class without associations' do
98
+ query = described_class.new(Cat)
99
+ expect(query.discover_includes).to eq 1
100
+ end
101
+
102
+ it 'adds an include level if it finds another nested item' do
103
+ query = described_class.new(OneInclude)
104
+ expect(query.discover_includes).to eq 2
105
+ end
106
+
107
+ it 'follows the include chain' do
108
+ query = described_class.new(MultiInclude)
109
+ expect(query.discover_includes).to eq 3
110
+ end
111
+
112
+ it 'can support circular references' do
113
+ query = described_class.new(CircularInclude)
114
+ expect(query.discover_includes).to eq 2
115
+
116
+ query = described_class.new(ToCircular)
117
+ expect(query.discover_includes).to eq 2
118
+ end
119
+
120
+ it 'when having multiple reference chains, include is set to the maximum chain legth' do
121
+ query = described_class.new(MultiIncludeWithVaryingReferenceDepth)
122
+ expect(query.discover_includes).to eq 3
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,58 @@
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+
4
+ require 'contentful_model'
5
+ require 'rspec'
6
+ require 'vcr'
7
+
8
+ RSpec.configure do |config|
9
+ config.filter_run :focus => true
10
+ config.run_all_when_everything_filtered = true
11
+ end
12
+
13
+ VCR.configure do |c|
14
+ c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
15
+ c.ignore_localhost = true
16
+ c.hook_into :webmock
17
+ c.default_cassette_options = { record: :once }
18
+ end
19
+
20
+ def vcr(name, &block)
21
+ VCR.use_cassette(name, &block)
22
+ end
23
+
24
+ class MockSpace
25
+ attr_reader :id
26
+
27
+ def initialize(id)
28
+ @id = id
29
+ end
30
+ end
31
+
32
+ class MockClient
33
+ attr_accessor :response
34
+
35
+ def initialize(response = [])
36
+ @response = response
37
+ end
38
+
39
+ def entries(query = {})
40
+ response
41
+ end
42
+ end
43
+
44
+ class Cat < ContentfulModel::Base
45
+ self.content_type_id = 'cat'
46
+ end
47
+
48
+ class CoercedCat < ContentfulModel::Base
49
+ self.content_type_id = 'cat'
50
+
51
+ coerce_field name: -> (name) { name.gsub('Nyan', 'Fat') }
52
+ coerce_field created_at: 'String'
53
+ coerce_field updated_at: 'Date'
54
+ end
55
+
56
+ class TestModel < ContentfulModel::Base
57
+ self.content_type_id = 'testModel'
58
+ end
@@ -0,0 +1,182 @@
1
+ require 'spec_helper'
2
+
3
+ class MockValidatableNoFields
4
+ include ContentfulModel::Validations
5
+ end
6
+
7
+ class MockValidatable
8
+ include ContentfulModel::Validations
9
+
10
+ attr_reader :fields
11
+
12
+ def initialize(fields = [])
13
+ @fields = fields
14
+ fields.each do |f|
15
+ define_singleton_method f do
16
+ "#{f}_value"
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ class MockValidatableWithAdditionalValidation < MockValidatable
23
+ validates_presence_of 'foo'
24
+ end
25
+
26
+ class BestFriendValidation
27
+ def validate(entry)
28
+ return ['Best Friend is not happycat'] unless entry.best_friend.try(:id) == 'happycat'
29
+ []
30
+ end
31
+ end
32
+
33
+ class LivesValidation
34
+ def initialize(lives)
35
+ @lives = lives
36
+ end
37
+
38
+ def validate(entry)
39
+ return ["Lives are not #{@lives}"] unless entry.lives == @lives
40
+ []
41
+ end
42
+ end
43
+
44
+ class ValidationsCat < ContentfulModel::Base
45
+ self.content_type_id = 'cat'
46
+
47
+ # validation lambda
48
+ validate :name, -> (e) { e.name == 'Nyan Cat' }, on_load: true
49
+
50
+ # validation block
51
+ validate :id, on_load: true do |e|
52
+ e.id == 'nyancat'
53
+ end
54
+
55
+ # validation class
56
+ validate_with BestFriendValidation, on_load: true
57
+
58
+ # validation object
59
+ validate_with LivesValidation.new(1337), on_load: true
60
+
61
+ # validation only on save
62
+ validate :name, -> (e) { e.name == 'foobar' }
63
+
64
+ # presence validation
65
+ validates_presence_of :likes
66
+ end
67
+
68
+
69
+ describe ContentfulModel::Validations do
70
+ let(:validatable) { MockValidatable.new }
71
+ let(:non_validatable) { MockValidatableNoFields.new }
72
+ let(:validatable_with_fields) { MockValidatable.new(['foo']) }
73
+ let(:validatable_with_validations) { MockValidatableWithAdditionalValidation.new }
74
+
75
+ describe 'class methods' do
76
+ describe '::validations' do
77
+ it 'is nil by default' do
78
+ expect(validatable.class.validations).to eq nil
79
+ end
80
+
81
+ it 'is an array of validations' do
82
+ expect(validatable_with_validations.class.validations).to be_a Array
83
+ expect(validatable_with_validations.class.validations.size).to eq 1
84
+ end
85
+ end
86
+ end
87
+
88
+ describe 'instance methods' do
89
+ describe '#validate' do
90
+ it 'returns false and sets error if its not an entry (aka. has no fields)' do
91
+ expect(non_validatable.validate).to be_falsey
92
+ expect(non_validatable.errors).to eq ["Entity doesn't respond to the fields() method"]
93
+ expect(non_validatable.valid?).to be_falsey
94
+ expect(non_validatable.invalid?).to be_truthy
95
+ end
96
+
97
+ it 'returns false and sets error if validation does not pass' do
98
+ expect(validatable_with_validations.validate).to be_falsey
99
+ expect(validatable_with_validations.errors).to eq ["foo is required"]
100
+ expect(validatable_with_validations.valid?).to be_falsey
101
+ expect(validatable_with_validations.invalid?).to be_truthy
102
+ end
103
+
104
+ it 'returns true if validations pass' do
105
+ ok_validatable = MockValidatableWithAdditionalValidation.new(['foo'])
106
+
107
+ expect(ok_validatable.validate).to be_truthy
108
+ expect(ok_validatable.errors).to be_empty
109
+ expect(ok_validatable.valid?).to be_truthy
110
+ expect(ok_validatable.invalid?).to be_falsey
111
+ end
112
+ end
113
+ end
114
+
115
+ describe 'integration' do
116
+ before :each do
117
+ ContentfulModel.configure do |config|
118
+ config.space = 'cfexampleapi'
119
+ config.access_token = 'b4c0n73n7fu1'
120
+ config.entry_mapping = {}
121
+ end
122
+
123
+ ValidationsCat.client = nil
124
+ end
125
+
126
+ it 'can have many kinds of validations' do
127
+ expect(ValidationsCat.validations.size).to eq 5
128
+ expect(ValidationsCat.save_validations.size).to eq 1
129
+ end
130
+
131
+ it 'can pass all validations' do
132
+ vcr('nyancat') {
133
+ nyancat = ValidationsCat.find('nyancat')
134
+ expect(nyancat.validate).to be_truthy
135
+ expect(nyancat.invalid?).to be_falsey
136
+ expect(nyancat.valid?).to be_truthy
137
+ expect(nyancat.errors).to be_empty
138
+ }
139
+ end
140
+
141
+ it 'can validate save validations' do
142
+ vcr('nyancat') {
143
+ nyancat = ValidationsCat.find('nyancat')
144
+
145
+ expect(nyancat.validate(true)).to be_falsey
146
+ expect(nyancat.invalid?(true)).to be_truthy
147
+ expect(nyancat.valid?(true)).to be_falsey
148
+ expect(nyancat.errors.size).to eq 1
149
+ }
150
+ end
151
+
152
+ it 'can fail a validation' do
153
+ vcr('nyancat') {
154
+ nyancat = ValidationsCat.find('nyancat')
155
+ nyancat.name = 'foobar'
156
+ expect(nyancat.validate).to be_falsey
157
+ expect(nyancat.invalid?).to be_truthy
158
+ expect(nyancat.errors).to eq ["name: validation not met"]
159
+ }
160
+ end
161
+
162
+ it 'can fail multiple validations' do
163
+ vcr('nyancat') {
164
+ nyancat = ValidationsCat.find('nyancat')
165
+ nyancat.name = 'foobar'
166
+ nyancat.lives = 1000
167
+ nyancat.best_friend = nil
168
+ nyancat.sys[:id] = 'foo'
169
+ nyancat.likes = nil
170
+
171
+ expect(nyancat.validate).to be_falsey
172
+ expect(nyancat.errors).to match_array [
173
+ "name: validation not met",
174
+ "Lives are not 1337",
175
+ "Best Friend is not happycat",
176
+ "id: validation not met",
177
+ "likes is required"
178
+ ]
179
+ }
180
+ end
181
+ end
182
+ end