contentful_model 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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