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,33 @@
1
+ require 'spec_helper'
2
+
3
+ class HasManyTestModel < ContentfulModel::Base
4
+ self.content_type_id = 'hasManyTestModel'
5
+
6
+ has_many :test_models
7
+ end
8
+
9
+ describe ContentfulModel::Associations::HasOne do
10
+ before :each do
11
+ ContentfulModel.configure do |c|
12
+ c.space = 'a22o2qgm356c'
13
+ c.access_token = '60229df5876f14f499870d0d26f37b1323764ed637289353a5f74a5ea190c214'
14
+ c.entry_mapping = {}
15
+ end
16
+
17
+ HasManyTestModel.add_entry_mapping
18
+ TestModel.add_entry_mapping
19
+ end
20
+
21
+ it 'resolves children references' do
22
+ vcr('association/has_many') {
23
+ parent = HasManyTestModel.first
24
+
25
+ expect(parent).to be_a HasManyTestModel
26
+ expect(parent.name).to eq 'Has Many - Parent 1'
27
+
28
+ expect(parent.test_models).to be_a ::Array
29
+ expect(parent.test_models.size).to eq 2
30
+ expect(parent.test_models.first.name).to eq 'Has Many - Child 1'
31
+ }
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ class HasOneTestModel < ContentfulModel::Base
4
+ self.content_type_id = 'hasOneTestModel'
5
+
6
+ has_one :test_model
7
+ end
8
+
9
+ describe ContentfulModel::Associations::HasOne do
10
+ before :each do
11
+ ContentfulModel.configure do |c|
12
+ c.space = 'a22o2qgm356c'
13
+ c.access_token = '60229df5876f14f499870d0d26f37b1323764ed637289353a5f74a5ea190c214'
14
+ c.entry_mapping = {}
15
+ end
16
+
17
+ HasOneTestModel.add_entry_mapping
18
+ TestModel.add_entry_mapping
19
+ end
20
+
21
+ it 'resolves children references' do
22
+ vcr('association/has_one') {
23
+ parent = HasOneTestModel.first
24
+
25
+ expect(parent).to be_a HasOneTestModel
26
+ expect(parent.name).to eq 'Has One - Parent 1'
27
+
28
+ expect(parent.test_model).to be_a TestModel
29
+ expect(parent.test_model.name).to eq 'Has One - Child'
30
+ }
31
+ end
32
+ end
data/spec/base_spec.rb ADDED
@@ -0,0 +1,199 @@
1
+ require 'spec_helper'
2
+
3
+ class HumanWithMethods < ContentfulModel::Base
4
+ self.content_type_id = 'human'
5
+
6
+ def foo
7
+ 'bar'
8
+ end
9
+
10
+ def profile
11
+ [
12
+ "Name: #{name}",
13
+ "Description: #{description}",
14
+ "Likes: #{likes.join(', ')}"
15
+ ].join("\n")
16
+ end
17
+ end
18
+
19
+ module Models
20
+ class NestedDog < ContentfulModel::Base
21
+ self.content_type_id = 'dog'
22
+ end
23
+ end
24
+
25
+ class ModelWithEmptyFields < ContentfulModel::Base
26
+ self.content_type_id = 'modelWithEmpty'
27
+
28
+ return_nil_for_empty :empty
29
+ end
30
+
31
+ describe ContentfulModel::Base do
32
+ subject { vcr('nyancat') { Cat.find('nyancat') } }
33
+
34
+ before :each do
35
+ ContentfulModel.configure do |config|
36
+ config.space = 'cfexampleapi'
37
+ config.access_token = 'b4c0n73n7fu1'
38
+ config.entry_mapping = {}
39
+ end
40
+ end
41
+
42
+ describe 'class methods' do
43
+ it '::client' do
44
+ vcr('client') {
45
+ expect(Cat.client).to be_a ContentfulModel::Client
46
+ }
47
+ end
48
+
49
+ it '::content_type' do
50
+ vcr('base/content_type') {
51
+ content_type = Cat.content_type
52
+ expect(content_type.id).to eq 'cat'
53
+ expect(content_type).to be_a Contentful::ContentType
54
+ }
55
+ end
56
+
57
+ it '::descendents' do
58
+ child_classes = ContentfulModel::Base.descendents
59
+ expect(child_classes).to be_a ::Array
60
+ expect(child_classes).to include(Cat)
61
+ end
62
+
63
+ describe '::return_nil_for_empty' do
64
+ before :each do
65
+ ContentfulModel.configure do |config|
66
+ config.space = 'a22o2qgm356c'
67
+ config.access_token = '60229df5876f14f499870d0d26f37b1323764ed637289353a5f74a5ea190c214'
68
+ config.entry_mapping = {}
69
+ end
70
+ ModelWithEmptyFields.add_entry_mapping
71
+ end
72
+
73
+ it 'returns nil for undefined field' do
74
+ vcr('base/return_nil_for_empty') {
75
+ empty_entry = ModelWithEmptyFields.find('3QBNQLVctWIUMy4MEMUECK')
76
+ expect(empty_entry.empty).to be_nil
77
+ }
78
+ end
79
+
80
+ it 'returns value for defined field' do
81
+ vcr('base/return_nil_for_empty_with_value') {
82
+ empty_entry = ModelWithEmptyFields.find('JwGJyMyZQ44QWmWGS6sSy')
83
+ expect(empty_entry.empty).to eq 'foo'
84
+ }
85
+ end
86
+ end
87
+ end
88
+
89
+ describe 'initialization' do
90
+ it 'creates getters on initialize for each field' do
91
+ expect(subject.respond_to?(:foobar)).to be_falsey
92
+ expect(subject.respond_to?(:name)).to be_truthy
93
+ expect(subject.respond_to?(:lives)).to be_truthy
94
+ end
95
+ end
96
+
97
+ describe "coercion" do
98
+ it "applies to the getters created for each field" do
99
+ vcr('nyancat') {
100
+ nyancat = CoercedCat.find('nyancat')
101
+ expect(nyancat.name).to eq 'Fat Cat'
102
+ expect(nyancat.created_at).to eq '2013-06-27T22:46:19+00:00'
103
+ }
104
+ end
105
+ end
106
+
107
+ describe "#coerce_field" do
108
+ it "returns the value for fields that have no coercion" do
109
+ title = 'CMS of the future'
110
+ expect(CoercedCat.coerce_value(:title, title)).to eq title
111
+ end
112
+
113
+ context "the fields coercion is a Proc" do
114
+ it "call the proc with the value and returns the result" do
115
+ expect(CoercedCat.coerce_value(:name, 'Nyan man')).to eq 'Fat man'
116
+ end
117
+ end
118
+
119
+ context "the fields coercion is a Symbol" do
120
+ it "calls the builtin coercion with the value and returns the result" do
121
+ date = '2016-12-06T11:00+00:00'
122
+ expect(CoercedCat.coerce_value(:updated_at, date))
123
+ .to be_instance_of(DateTime)
124
+ end
125
+ end
126
+ end
127
+
128
+ describe "#cache_key" do
129
+ subject(:contentful_object) { vcr('nyancat') { Cat.find('nyancat') } }
130
+
131
+ it "can be found by #responds_to?" do
132
+ expect(contentful_object).to respond_to(:cache_key)
133
+ end
134
+
135
+ it "starts with the objects model name"do
136
+ expect(contentful_object.cache_key).to start_with("cat/")
137
+ end
138
+
139
+ it "contains the objects id" do
140
+ expect(contentful_object.cache_key).to include(contentful_object.id.to_s)
141
+ end
142
+
143
+ it "contains the objects updated timestamp" do
144
+ expect(contentful_object.cache_key)
145
+ .to include(contentful_object.updated_at.utc.to_s(:usec))
146
+ end
147
+ end
148
+
149
+ it '#hash' do
150
+ vcr('nyancat') {
151
+ nyancat = Cat.find('nyancat')
152
+ expect(nyancat.hash).to eq 'cat-nyancat'.hash
153
+ }
154
+ end
155
+
156
+ it '#eql?' do
157
+ nyancat = nil
158
+ other_nyan = nil
159
+
160
+ vcr('nyancat') { nyancat = Cat.find('nyancat') }
161
+ vcr('nyancat') { other_nyan = Cat.find('nyancat') }
162
+
163
+ expect(nyancat == other_nyan).to be_truthy
164
+ end
165
+
166
+ describe 'base children should be able to have user defined methods' do
167
+ it 'has a user defined method' do
168
+ vcr('human') {
169
+ human = HumanWithMethods.first
170
+ expect(human.foo).to eq 'bar'
171
+ }
172
+ end
173
+
174
+ it 'has a user defined method that uses some entry field' do
175
+ vcr('human') {
176
+ human = HumanWithMethods.first
177
+ profile = human.profile
178
+
179
+ expect(profile).to include("Name: Finn")
180
+ expect(profile).to include("Description: Fearless adventurer! Defender of pancakes.")
181
+ expect(profile).to include("Likes: adventure")
182
+ }
183
+ end
184
+ end
185
+
186
+ describe 'base children can be defined in nested modules' do
187
+ it 'does not fail to fetch entries' do
188
+ dog = nil
189
+ vcr('dog') {
190
+ expect {
191
+ dog = Models::NestedDog.first
192
+ }.not_to raise_error
193
+
194
+ expect(dog.name).to eq 'Jake'
195
+ }
196
+ end
197
+ end
198
+ end
199
+
@@ -0,0 +1,199 @@
1
+ require 'spec_helper'
2
+
3
+ class MockChainQueriable
4
+ include ContentfulModel::Queries
5
+
6
+ class << self
7
+ attr_accessor :content_type_id
8
+ end
9
+ end
10
+
11
+ class MockChainQueriableEntry < MockChainQueriable
12
+ self.content_type_id = 'foo'
13
+
14
+ def self.client
15
+ @@client ||= MockClient.new
16
+ end
17
+
18
+ def invalid?
19
+ false
20
+ end
21
+ end
22
+
23
+ describe ContentfulModel::Base do
24
+ subject { MockChainQueriableEntry }
25
+
26
+ before do
27
+ subject.content_type_id = 'foo'
28
+ end
29
+
30
+ describe 'class methods' do
31
+ describe '::all' do
32
+ it 'fails if no content type is set' do
33
+ subject.content_type_id = nil
34
+ expect { subject.all }.to raise_error 'You need to set self.content_type in your model class'
35
+ end
36
+
37
+ it 'returns a query object' do
38
+ expect(subject.all).to be_a ContentfulModel::Query
39
+ end
40
+ end
41
+
42
+ it '::params' do
43
+ query = subject.params({'include' => 1})
44
+ expect(query.parameters).to include('include' => 1)
45
+ end
46
+
47
+ it '::first' do
48
+ expect_any_instance_of(ContentfulModel::Query).to receive(:load) { ['first'] }
49
+ expect(subject.first).to eq 'first'
50
+ end
51
+
52
+ it '::skip' do
53
+ query = subject.skip(2)
54
+ expect(query.parameters).to include('skip' => 2)
55
+ end
56
+
57
+ it '::offset' do
58
+ query = subject.offset(3)
59
+ expect(query.parameters).to include('skip' => 3)
60
+ end
61
+
62
+ it '::limit' do
63
+ query = subject.limit(4)
64
+ expect(query.parameters).to include('limit' => 4)
65
+ end
66
+
67
+ it '::locale' do
68
+ query = subject.locale('en-US')
69
+ expect(query.parameters).to include('locale' => 'en-US')
70
+ end
71
+
72
+ it '::load_children' do
73
+ query = subject.load_children(4)
74
+ expect(query.parameters).to include('include' => 4)
75
+ end
76
+
77
+ describe '::order' do
78
+ describe 'when parameter is a hash' do
79
+ it 'ascending' do
80
+ query = subject.order(foo: :asc)
81
+ expect(query.parameters).to include('order' => 'fields.foo')
82
+ end
83
+
84
+ it 'descending' do
85
+ query = subject.order(foo: :desc)
86
+ expect(query.parameters).to include('order' => '-fields.foo')
87
+ end
88
+ end
89
+
90
+ it 'when param is a symbol' do
91
+ query = subject.order(:foo)
92
+ expect(query.parameters).to include('order' => 'fields.foo')
93
+ end
94
+
95
+ it 'when param is a string' do
96
+ query = subject.order('foo')
97
+ expect(query.parameters).to include('order' => 'fields.foo')
98
+ end
99
+
100
+ it 'when param is a sys property' do
101
+ query = subject.order(:created_at)
102
+ expect(query.parameters).to include('order' => 'sys.createdAt')
103
+ end
104
+ end
105
+
106
+ describe '::find_by' do
107
+ it 'when value is an array' do
108
+ query = subject.find_by(foo: [1, 2, 3])
109
+ expect(query.parameters).to include('fields.foo[in]' => '1,2,3')
110
+ end
111
+
112
+ it 'when value is a string' do
113
+ query = subject.find_by(foo: 'bar')
114
+ expect(query.parameters).to include('fields.foo' => 'bar')
115
+ end
116
+
117
+ it 'when value is a number' do
118
+ query = subject.find_by(foo: 1)
119
+ expect(query.parameters).to include('fields.foo' => 1)
120
+ end
121
+
122
+ it 'when value is a boolean' do
123
+ query = subject.find_by(foo: true)
124
+ expect(query.parameters).to include('fields.foo' => true)
125
+ end
126
+
127
+ it 'when value is a hash' do
128
+ query = subject.find_by(foo: {gte: 123})
129
+ expect(query.parameters).to include('fields.foo[gte]' => 123)
130
+ end
131
+
132
+ it 'when multiple fields' do
133
+ query = subject.find_by(foo: true, bar: 123)
134
+ expect(query.parameters).to include('fields.foo' => true , 'fields.bar' => 123)
135
+ end
136
+
137
+ it 'supports sys fields' do
138
+ query = subject.find_by('sys.id': 'foo')
139
+ expect(query.parameters).to include('sys.id' => 'foo')
140
+ end
141
+
142
+ it 'support sys properties without prefix' do
143
+ query = subject.find_by('updatedAt' => 'foo')
144
+ expect(query.parameters).to include('sys.updatedAt' => 'foo')
145
+ end
146
+ end
147
+
148
+ describe '::paginate' do
149
+ it 'defaults to first page and 100 items and sort by updatedAt' do
150
+ query = subject.paginate
151
+ expect(query.parameters).to include('limit' => 100, 'skip' => 0, 'order' => 'sys.updatedAt')
152
+ end
153
+
154
+ it 'requesting second page will add page_size to skip' do
155
+ query = subject.paginate(2)
156
+ expect(query.parameters).to include('limit' => 100, 'skip' => 100)
157
+ end
158
+
159
+ it 'can change page_size' do
160
+ query = subject.paginate(1, 20)
161
+ expect(query.parameters).to include('limit' => 20, 'skip' => 0)
162
+ end
163
+
164
+ it 'can change page_size and select a different page' do
165
+ query = subject.paginate(3, 20)
166
+ expect(query.parameters).to include('limit' => 20, 'skip' => 40)
167
+ end
168
+
169
+ it 'outliers are changed to default values' do
170
+ query = subject.paginate(-1, 'foo')
171
+ expect(query.parameters).to include('limit' => 100, 'skip' => 0)
172
+ end
173
+
174
+ it 'can sort by a different field' do
175
+ query = subject.paginate(1, 100, 'sys.createdAt')
176
+ expect(query.parameters).to include('limit' => 100, 'skip' => 0, 'order' => 'sys.createdAt')
177
+ end
178
+ end
179
+
180
+ describe '::search' do
181
+ describe 'when parameter is a hash' do
182
+ it 'when value is a string performs "match"' do
183
+ query = subject.search(foo: 'bar')
184
+ expect(query.parameters).to include('fields.foo[match]' => 'bar')
185
+ end
186
+
187
+ it 'when value is a hash performs query based on hash key' do
188
+ query = subject.search(foo: {gte: 123})
189
+ expect(query.parameters).to include('fields.foo[gte]' => 123)
190
+ end
191
+ end
192
+
193
+ it 'when parameter is a string, performs full text search using "query"' do
194
+ query = subject.search('foobar')
195
+ expect(query.parameters).to include('query' => 'foobar')
196
+ end
197
+ end
198
+ end
199
+ end