contentful 0.2.0 → 0.3.0

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