contentful 0.2.0 → 0.3.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.
@@ -0,0 +1,10 @@
1
+ require_relative 'resource'
2
+
3
+ module Contentful
4
+ # Resource class for deleted entries
5
+ # https://www.contentful.com/developers/documentation/content-delivery-api/http/#sync-item-types
6
+ class DeletedAsset
7
+ include Contentful::Resource
8
+ include Contentful::Resource::SystemProperties
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require_relative 'resource'
2
+
3
+ module Contentful
4
+ # Resource class for deleted entries
5
+ # https://www.contentful.com/developers/documentation/content-delivery-api/http/#sync-item-types
6
+ class DeletedEntry
7
+ include Contentful::Resource
8
+ include Contentful::Resource::SystemProperties
9
+ end
10
+ end
@@ -8,6 +8,8 @@ module Contentful
8
8
  def initialize(client, endpoint, query = {}, id = nil)
9
9
  @client = client
10
10
  @endpoint = endpoint
11
+ @absolute = true if @endpoint.start_with?('http')
12
+
11
13
  @query = if query && !query.empty?
12
14
  normalize_query(query)
13
15
  end
@@ -31,6 +33,11 @@ module Contentful
31
33
  client.get(self)
32
34
  end
33
35
 
36
+ # Returns true if endpoint is an absolute url
37
+ def absolute?
38
+ !! @absolute
39
+ end
40
+
34
41
  # Returns a new Request object with the same data
35
42
  def copy
36
43
  Marshal.load(Marshal.dump(self))
@@ -19,10 +19,12 @@ module Contentful
19
19
  date: ->(v) { DateTime.parse(v) }
20
20
  }
21
21
 
22
- attr_reader :properties, :request, :client
22
+ attr_reader :properties, :request, :client, :default_locale
23
23
 
24
- def initialize(object, request = nil, client = nil)
24
+ def initialize(object, request = nil, client = nil, nested_locale_fields = false, default_locale = Contentful::Client::DEFAULT_CONFIGURATION[:default_locale] )
25
25
  self.class.update_coercions!
26
+ @nested_locale_fields = nested_locale_fields
27
+ @default_locale = default_locale
26
28
 
27
29
  @properties = extract_from_object object, :property, self.class.property_coercions.keys
28
30
  @request = request
@@ -39,6 +41,11 @@ module Contentful
39
41
  false
40
42
  end
41
43
 
44
+ # By default, fields come flattened in the current locale. This is different for syncs
45
+ def nested_locale_fields?
46
+ !! @nested_locale_fields
47
+ end
48
+
42
49
  # Resources that don't include SystemProperties return nil for #sys
43
50
  def sys
44
51
  nil
@@ -96,6 +103,11 @@ module Contentful
96
103
 
97
104
  # Register the resources properties on class level by using the #property method
98
105
  module ClassMethods
106
+ # By default, fields come flattened in the current locale. This is different for sync
107
+ def nested_locale_fields?
108
+ false
109
+ end
110
+
99
111
  def property_coercions
100
112
  @property_coercions ||= {}
101
113
  end
@@ -0,0 +1,32 @@
1
+ module Contentful
2
+ module Resource
3
+ # Useful methods for array-like resources that can be included if an
4
+ # :items property exists
5
+ module ArrayLike
6
+ include Enumerable
7
+
8
+ # Returns true for array-like resources
9
+ def array?
10
+ true
11
+ end
12
+
13
+ # Delegates to items#each
14
+ def each_item(&block)
15
+ items.each(&block)
16
+ end
17
+ alias each each_item
18
+
19
+ # Delegates to items#empty?
20
+ def empty?
21
+ items.empty?
22
+ end
23
+
24
+ # Delegetes to items#size
25
+ def size
26
+ items.size
27
+ end
28
+ alias length size
29
+
30
+ end
31
+ end
32
+ end
@@ -6,14 +6,13 @@ module Contentful
6
6
  # It depends on system properties being available
7
7
  module Fields
8
8
  # Returns all fields of the asset
9
- def fields
10
- @fields[locale]
9
+ def fields(wanted_locale = default_locale)
10
+ @fields[locale || wanted_locale]
11
11
  end
12
12
 
13
13
  def initialize(object, *)
14
14
  super
15
- @fields = {}
16
- @fields[locale] = extract_from_object object["fields"], :fields
15
+ extract_fields_from_object! object
17
16
  end
18
17
 
19
18
  def inspect(info = nil)
@@ -24,6 +23,26 @@ module Contentful
24
23
  end
25
24
  end
26
25
 
26
+
27
+ private
28
+
29
+ def extract_fields_from_object!(object)
30
+ @fields = {}
31
+
32
+ if nested_locale_fields?
33
+ object["fields"].each do |field_name, nested_child_object|
34
+ nested_child_object.each do |object_locale, real_child_object|
35
+ @fields[object_locale] ||= {}
36
+ @fields[object_locale].merge! extract_from_object(
37
+ { field_name => real_child_object }, :fields
38
+ )
39
+ end
40
+ end
41
+ else
42
+ @fields[locale] = extract_from_object object["fields"], :fields
43
+ end
44
+ end
45
+
27
46
  module ClassMethods
28
47
  # No coercions, since no content type available
29
48
  def fields_coercions
@@ -7,6 +7,8 @@ require_relative 'dynamic_entry'
7
7
  require_relative 'asset'
8
8
  require_relative 'array'
9
9
  require_relative 'link'
10
+ require_relative 'deleted_entry'
11
+ require_relative 'deleted_asset'
10
12
 
11
13
  module Contentful
12
14
  # Transforms a Contentful::Response into a Contentful::Resource or a Contentful::Error
@@ -17,19 +19,23 @@ module Contentful
17
19
  'ContentType' => ContentType,
18
20
  'Entry' => :find_entry_class,
19
21
  'Asset' => Asset,
20
- 'Array' => Array,
22
+ 'Array' => :array_or_sync_page,
21
23
  'Link' => Link,
24
+ 'DeletedEntry' => DeletedEntry,
25
+ 'DeletedAsset' => DeletedAsset,
22
26
  }
23
27
  DEFAULT_ENTRY_MAPPING = {}
24
28
 
25
29
  attr_reader :client, :response, :resource_mapping, :entry_mapping, :resource
26
30
 
27
31
 
28
- def initialize(client, response, resource_mapping = {}, entry_mapping = {})
32
+ def initialize(client, response, resource_mapping = {}, entry_mapping = {}, default_locale = Contentful::Client::DEFAULT_CONFIGURATION[:default_locale])
29
33
  @response = response
30
34
  @client = client
31
35
  @included_resources = {}
32
36
  @known_resources = Hash.new{ |h,k| h[k] = {} }
37
+ @nested_locales = false
38
+ @default_locale = default_locale
33
39
  @resource_mapping = default_resource_mapping.merge(resource_mapping)
34
40
  @entry_mapping = default_entry_mapping.merge(entry_mapping)
35
41
  end
@@ -72,9 +78,12 @@ module Contentful
72
78
  @resource
73
79
  end
74
80
 
75
- # Creates a single resource from the
81
+ # Creates a single resource from the response object
76
82
  def create_resource(object)
77
- res = detect_resource_class(object).new(object, response.request, client)
83
+ res_class = detect_resource_class(object)
84
+ @nested_locales ||= res_class.nested_locale_fields?
85
+ res = res_class.new(object, response.request, client, @nested_locales, @default_locale)
86
+
78
87
  add_to_known_resources res
79
88
  replace_children res, object
80
89
  replace_child_array res.items if res.array?
@@ -99,6 +108,7 @@ module Contentful
99
108
  end
100
109
  end
101
110
 
111
+ # Returns the id of the related ContentType, if there is one
102
112
  def content_type_id_for_entry(object)
103
113
  object["sys"] &&
104
114
  object["sys"]["contentType"] &&
@@ -106,6 +116,15 @@ module Contentful
106
116
  object["sys"]["contentType"]["sys"]["id"]
107
117
  end
108
118
 
119
+ # Detects if a resource is an Contentful::Array or a SyncPage
120
+ def array_or_sync_page(object)
121
+ if object["nextPageUrl"] || object["nextSyncUrl"]
122
+ SyncPage
123
+ else
124
+ Array
125
+ end
126
+ end
127
+
109
128
  # Uses the resource mapping to find the proper Resource class to initialize
110
129
  # for this Response object type
111
130
  #
@@ -122,7 +141,7 @@ module Contentful
122
141
  when Proc
123
142
  res_class[object]
124
143
  when nil
125
- raise UnsparsableResource.new(response)
144
+ raise UnparsableResource.new(response)
126
145
  else
127
146
  res_class
128
147
  end
@@ -0,0 +1,72 @@
1
+ require_relative 'resource_builder'
2
+ require_relative 'deleted_entry'
3
+ require_relative 'deleted_asset'
4
+ require_relative 'sync_page'
5
+
6
+ module Contentful
7
+ class Sync
8
+ attr_reader :next_sync_url
9
+
10
+ def initialize(client, options_or_url)
11
+ @client = client
12
+ @next_sync_url = nil
13
+ @first_page_options_or_url = options_or_url
14
+ end
15
+
16
+ # Iterates over all pages of the current sync
17
+ # Please Keep in Mind: Iterating fires a new request for each page
18
+ def each_page(&block)
19
+ page = first_page
20
+ block.call(page)
21
+
22
+ until completed?
23
+ page = page.next_page
24
+ block.call(page)
25
+ end
26
+ end
27
+
28
+ # Returns the first sync result page
29
+ def first_page
30
+ get(@first_page_options_or_url)
31
+ end
32
+
33
+ # Returns false as long as last sync page has not been reached
34
+ def completed?
35
+ !! next_sync_url
36
+ end
37
+
38
+ # Directly iterates over all resources that have changed
39
+ def each_item(&block)
40
+ each_page do |page|
41
+ page.each_item do |item|
42
+ block.call item
43
+ end
44
+ end
45
+ end
46
+
47
+ def get(options_or_url)
48
+ if options_or_url.is_a? String
49
+ page = Request.new(@client, options_or_url).get
50
+ else
51
+ page = Request.new(@client, '/sync', options_or_url).get
52
+ end
53
+
54
+ link_page_to_sync! page
55
+ update_sync_state_from! page
56
+
57
+ page
58
+ end
59
+
60
+
61
+ private
62
+
63
+ def link_page_to_sync!(page)
64
+ page.instance_variable_set :@sync, self
65
+ end
66
+
67
+ def update_sync_state_from!(page)
68
+ @next_sync_url = page.next_sync_url
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,33 @@
1
+ require_relative 'resource'
2
+ require_relative 'resource/array_like'
3
+
4
+ module Contentful
5
+ class SyncPage
6
+ attr_reader :sync
7
+
8
+ include Contentful::Resource
9
+ include Contentful::Resource::SystemProperties
10
+ include Contentful::Resource::ArrayLike
11
+
12
+ property :items
13
+ property :nextSyncUrl
14
+ property :nextPageUrl
15
+
16
+ def self.nested_locale_fields?
17
+ true
18
+ end
19
+
20
+ def next_page
21
+ sync.get(next_page_url) if next_page?
22
+ end
23
+
24
+ def next_page?
25
+ !! next_page_url
26
+ end
27
+
28
+ def last_page?
29
+ ! next_page_url
30
+ end
31
+
32
+ end
33
+ end
@@ -1,3 +1,3 @@
1
1
  module Contentful
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -56,4 +56,10 @@ describe Contentful::Client do
56
56
  end
57
57
 
58
58
  end
59
+
60
+ describe '#sync' do
61
+ it 'creates a new Sync object' do
62
+ expect( create_client.sync ).to be_a Contentful::Sync
63
+ end
64
+ end
59
65
  end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe 'DeletedAsset' do
5
+ let(:deleted_asset){
6
+ vcr('sync_deleted_asset'){
7
+ create_client.sync(initial: true, type: 'DeletedAsset').first_page.items[0]
8
+ }
9
+ }
10
+
11
+ describe 'SystemProperties' do
12
+ it 'has a #sys getter returning a hash with symbol keys' do
13
+ expect( deleted_asset.sys ).to be_a Hash
14
+ expect( deleted_asset.sys.keys.sample ).to be_a Symbol
15
+ end
16
+
17
+ it 'has #id' do
18
+ expect( deleted_asset.id ).to eq "5c6VY0gWg0gwaIeYkUUiqG"
19
+ end
20
+
21
+ it 'has #type' do
22
+ expect( deleted_asset.type ).to eq "DeletedAsset"
23
+ end
24
+
25
+ it 'has #deleted_at' do
26
+ expect( deleted_asset.created_at ).to be_a DateTime
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe 'DeletedEntry' do
5
+ let(:deleted_entry){
6
+ vcr('sync_deleted_entry'){
7
+ create_client.sync(initial: true, type: 'DeletedEntry').first_page.items[0]
8
+ }
9
+ }
10
+
11
+ describe 'SystemProperties' do
12
+ it 'has a #sys getter returning a hash with symbol keys' do
13
+ expect( deleted_entry.sys ).to be_a Hash
14
+ expect( deleted_entry.sys.keys.sample ).to be_a Symbol
15
+ end
16
+
17
+ it 'has #id' do
18
+ expect( deleted_entry.id ).to eq "CVebBDcQsSsu6yKKIayy"
19
+ end
20
+
21
+ it 'has #type' do
22
+ expect( deleted_entry.type ).to eq "DeletedEntry"
23
+ end
24
+
25
+ it 'has #deleted_at' do
26
+ expect( deleted_entry.created_at ).to be_a DateTime
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,174 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: get
5
+ uri: https://cdn.contentful.com/spaces/cfexampleapi/sync?initial=true&type=DeletedAsset
6
+ body:
7
+ encoding: US-ASCII
8
+ string: ''
9
+ headers:
10
+ User-Agent:
11
+ - RubyContentfulGem/0.1.3
12
+ Authorization:
13
+ - Bearer b4c0n73n7fu1
14
+ Content-Type:
15
+ - application/vnd.contentful.delivery.v1+json
16
+ Host:
17
+ - cdn.contentful.com
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: OK
22
+ headers:
23
+ Date:
24
+ - Mon, 28 Apr 2014 17:19:53 GMT
25
+ Server:
26
+ - nginx/1.1.19
27
+ Access-Control-Allow-Headers:
28
+ - Accept,Accept-Language,Authorization,Cache-Control,Content-Length,Content-Range,Content-Type,DNT,Destination,Expires,If-Match,If-Modified-Since,If-None-Match,Keep-Alive,Last-Modified,Origin,Pragma,Range,User-Agent,X-Http-Method-Override,X-Mx-ReqToken,X-Requested-With,X-Contentful-Version,X-Contentful-Content-Type,X-Contentful-Organization
29
+ Access-Control-Allow-Methods:
30
+ - GET,HEAD,OPTIONS
31
+ Access-Control-Allow-Origin:
32
+ - "*"
33
+ Access-Control-Max-Age:
34
+ - '86400'
35
+ Cache-Control:
36
+ - max-age=0
37
+ Content-Type:
38
+ - application/vnd.contentful.delivery.v1+json
39
+ Etag:
40
+ - '"df9f76f479ba20a61123c025da9126f9"'
41
+ X-Contentful-Request-Id:
42
+ - 71a-635793528
43
+ Content-Length:
44
+ - '2870'
45
+ Accept-Ranges:
46
+ - bytes
47
+ Via:
48
+ - 1.1 varnish
49
+ Age:
50
+ - '0'
51
+ X-Served-By:
52
+ - cache-fra1222-FRA
53
+ X-Cache:
54
+ - MISS
55
+ X-Cache-Hits:
56
+ - '0'
57
+ Vary:
58
+ - Accept-Encoding
59
+ body:
60
+ encoding: UTF-8
61
+ string: |
62
+ {
63
+ "sys": {
64
+ "type": "Array"
65
+ },
66
+ "items": [
67
+ {
68
+ "sys": {
69
+ "type": "DeletedAsset",
70
+ "id": "5c6VY0gWg0gwaIeYkUUiqG",
71
+ "space": {
72
+ "sys": {
73
+ "type": "Link",
74
+ "linkType": "Space",
75
+ "id": "cfexampleapi"
76
+ }
77
+ },
78
+ "revision": 1,
79
+ "createdAt": "2013-09-09T16:17:12.600Z",
80
+ "updatedAt": "2013-09-09T16:17:12.600Z",
81
+ "deletedAt": "2013-09-09T16:17:12.600Z"
82
+ }
83
+ },
84
+ {
85
+ "sys": {
86
+ "type": "DeletedAsset",
87
+ "id": "finn",
88
+ "space": {
89
+ "sys": {
90
+ "type": "Link",
91
+ "linkType": "Space",
92
+ "id": "cfexampleapi"
93
+ }
94
+ },
95
+ "revision": 1,
96
+ "createdAt": "2013-09-02T15:10:33.749Z",
97
+ "updatedAt": "2013-09-02T15:10:33.749Z",
98
+ "deletedAt": "2013-09-02T15:10:33.749Z"
99
+ }
100
+ },
101
+ {
102
+ "sys": {
103
+ "type": "DeletedAsset",
104
+ "id": "3MZPnjZTIskAIIkuuosCss",
105
+ "space": {
106
+ "sys": {
107
+ "type": "Link",
108
+ "linkType": "Space",
109
+ "id": "cfexampleapi"
110
+ }
111
+ },
112
+ "revision": 1,
113
+ "createdAt": "2013-09-02T14:55:34.645Z",
114
+ "updatedAt": "2013-09-02T14:55:34.645Z",
115
+ "deletedAt": "2013-09-02T14:55:34.645Z"
116
+ }
117
+ },
118
+ {
119
+ "sys": {
120
+ "type": "DeletedAsset",
121
+ "id": "4gp6taAwW4CmSgumq2ekUm",
122
+ "space": {
123
+ "sys": {
124
+ "type": "Link",
125
+ "linkType": "Space",
126
+ "id": "cfexampleapi"
127
+ }
128
+ },
129
+ "revision": 2,
130
+ "createdAt": "2013-09-02T14:55:34.623Z",
131
+ "updatedAt": "2013-09-02T14:55:34.623Z",
132
+ "deletedAt": "2013-09-02T14:55:34.623Z"
133
+ }
134
+ },
135
+ {
136
+ "sys": {
137
+ "type": "DeletedAsset",
138
+ "id": "1uf1qqyZuEuiwmigoUYkeu",
139
+ "space": {
140
+ "sys": {
141
+ "type": "Link",
142
+ "linkType": "Space",
143
+ "id": "cfexampleapi"
144
+ }
145
+ },
146
+ "revision": 1,
147
+ "createdAt": "2013-09-02T14:55:34.323Z",
148
+ "updatedAt": "2013-09-02T14:55:34.323Z",
149
+ "deletedAt": "2013-09-02T14:55:34.323Z"
150
+ }
151
+ },
152
+ {
153
+ "sys": {
154
+ "type": "DeletedAsset",
155
+ "id": "4hlteQAXS8iS0YCMU6QMWg",
156
+ "space": {
157
+ "sys": {
158
+ "type": "Link",
159
+ "linkType": "Space",
160
+ "id": "cfexampleapi"
161
+ }
162
+ },
163
+ "revision": 1,
164
+ "createdAt": "2013-09-02T14:55:34.282Z",
165
+ "updatedAt": "2013-09-02T14:55:34.282Z",
166
+ "deletedAt": "2013-09-02T14:55:34.282Z"
167
+ }
168
+ }
169
+ ],
170
+ "nextSyncUrl": "https://cdn.contentful.com/spaces/cfexampleapi/sync?sync_token=w5ZGw6JFwqZmVcKsE8Kow4grw45QdybDscOqw6vDvUcbEzolwqs8UsKNIsOuwozDjFbChi4dLE_DgRYLwpcDw7fCl8KWw4DCgMKDHWZ9IsKSwrd7PCPDnjjCuUNZUMK-wqHCuQkFaTHDtcKBw6ESVMKBB8OY"
171
+ }
172
+ http_version:
173
+ recorded_at: Mon, 28 Apr 2014 17:19:54 GMT
174
+ recorded_with: VCR 2.9.0