contentful 0.1.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 (133) hide show
  1. data/.README.md.swp +0 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +10 -0
  5. data/ChangeLog.md +3 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +193 -0
  9. data/Rakefile +30 -0
  10. data/contentful.gemspec +31 -0
  11. data/do_request.sh +5 -0
  12. data/doc/Contentful.html +131 -0
  13. data/doc/Contentful/AccessDenied.html +158 -0
  14. data/doc/Contentful/Array.html +346 -0
  15. data/doc/Contentful/Asset.html +315 -0
  16. data/doc/Contentful/BadRequest.html +158 -0
  17. data/doc/Contentful/Client.html +1407 -0
  18. data/doc/Contentful/ContentType.html +183 -0
  19. data/doc/Contentful/DynamicEntry.html +333 -0
  20. data/doc/Contentful/Entry.html +198 -0
  21. data/doc/Contentful/Error.html +413 -0
  22. data/doc/Contentful/Field.html +161 -0
  23. data/doc/Contentful/File.html +160 -0
  24. data/doc/Contentful/Link.html +275 -0
  25. data/doc/Contentful/Locale.html +161 -0
  26. data/doc/Contentful/NotFound.html +158 -0
  27. data/doc/Contentful/Request.html +669 -0
  28. data/doc/Contentful/Resource.html +606 -0
  29. data/doc/Contentful/Resource/AssetFields.html +413 -0
  30. data/doc/Contentful/Resource/AssetFields/ClassMethods.html +174 -0
  31. data/doc/Contentful/Resource/ClassMethods.html +271 -0
  32. data/doc/Contentful/Resource/Fields.html +398 -0
  33. data/doc/Contentful/Resource/Fields/ClassMethods.html +187 -0
  34. data/doc/Contentful/Resource/SystemProperties.html +444 -0
  35. data/doc/Contentful/Resource/SystemProperties/ClassMethods.html +174 -0
  36. data/doc/Contentful/ResourceBuilder.html +1400 -0
  37. data/doc/Contentful/Response.html +546 -0
  38. data/doc/Contentful/ServerError.html +158 -0
  39. data/doc/Contentful/Space.html +183 -0
  40. data/doc/Contentful/Support.html +198 -0
  41. data/doc/Contentful/Unauthorized.html +158 -0
  42. data/doc/Contentful/UnparsableJson.html +158 -0
  43. data/doc/Contentful/UnparsableResource.html +158 -0
  44. data/doc/_index.html +410 -0
  45. data/doc/class_list.html +54 -0
  46. data/doc/css/common.css +1 -0
  47. data/doc/css/full_list.css +57 -0
  48. data/doc/css/style.css +338 -0
  49. data/doc/file.README.html +214 -0
  50. data/doc/file_list.html +56 -0
  51. data/doc/frames.html +26 -0
  52. data/doc/index.html +214 -0
  53. data/doc/js/app.js +219 -0
  54. data/doc/js/full_list.js +178 -0
  55. data/doc/js/jquery.js +4 -0
  56. data/doc/method_list.html +533 -0
  57. data/doc/top-level-namespace.html +112 -0
  58. data/examples/custom_classes.rb +43 -0
  59. data/examples/dynamic_entries.rb +126 -0
  60. data/examples/example_queries.rb +27 -0
  61. data/examples/raise_errors.rb +22 -0
  62. data/examples/raw_mode.rb +15 -0
  63. data/examples/resource_mapping.rb +33 -0
  64. data/lib/contentful.rb +3 -0
  65. data/lib/contentful/array.rb +47 -0
  66. data/lib/contentful/asset.rb +34 -0
  67. data/lib/contentful/client.rb +193 -0
  68. data/lib/contentful/content_type.rb +16 -0
  69. data/lib/contentful/dynamic_entry.rb +54 -0
  70. data/lib/contentful/entry.rb +12 -0
  71. data/lib/contentful/error.rb +52 -0
  72. data/lib/contentful/field.rb +16 -0
  73. data/lib/contentful/file.rb +13 -0
  74. data/lib/contentful/link.rb +20 -0
  75. data/lib/contentful/locale.rb +12 -0
  76. data/lib/contentful/location.rb +12 -0
  77. data/lib/contentful/request.rb +37 -0
  78. data/lib/contentful/resource.rb +146 -0
  79. data/lib/contentful/resource/asset_fields.rb +50 -0
  80. data/lib/contentful/resource/fields.rb +39 -0
  81. data/lib/contentful/resource/system_properties.rb +48 -0
  82. data/lib/contentful/resource_builder.rb +197 -0
  83. data/lib/contentful/response.rb +64 -0
  84. data/lib/contentful/space.rb +14 -0
  85. data/lib/contentful/support.rb +18 -0
  86. data/lib/contentful/version.rb +3 -0
  87. data/spec/array_spec.rb +69 -0
  88. data/spec/asset_spec.rb +62 -0
  89. data/spec/auto_includes_spec.rb +12 -0
  90. data/spec/client_class_spec.rb +59 -0
  91. data/spec/client_configuration_spec.rb +197 -0
  92. data/spec/coercions_spec.rb +10 -0
  93. data/spec/content_type_spec.rb +44 -0
  94. data/spec/dynamic_entry_spec.rb +34 -0
  95. data/spec/entry_spec.rb +53 -0
  96. data/spec/error_class_spec.rb +60 -0
  97. data/spec/error_requests_spec.rb +32 -0
  98. data/spec/field_spec.rb +36 -0
  99. data/spec/file_spec.rb +28 -0
  100. data/spec/fixtures/json_responses/content_type.json +83 -0
  101. data/spec/fixtures/json_responses/not_found.json +13 -0
  102. data/spec/fixtures/json_responses/nyancat.json +48 -0
  103. data/spec/fixtures/json_responses/unparsable.json +13 -0
  104. data/spec/fixtures/vcr_cassettes/array.yml +288 -0
  105. data/spec/fixtures/vcr_cassettes/array_page_1.yml +106 -0
  106. data/spec/fixtures/vcr_cassettes/array_page_2.yml +73 -0
  107. data/spec/fixtures/vcr_cassettes/asset.yml +96 -0
  108. data/spec/fixtures/vcr_cassettes/bad_request.yml +76 -0
  109. data/spec/fixtures/vcr_cassettes/content_type.yml +147 -0
  110. data/spec/fixtures/vcr_cassettes/entries.yml +561 -0
  111. data/spec/fixtures/vcr_cassettes/entry.yml +112 -0
  112. data/spec/fixtures/vcr_cassettes/entry_cache.yml +288 -0
  113. data/spec/fixtures/vcr_cassettes/field.yml +147 -0
  114. data/spec/fixtures/vcr_cassettes/locale.yml +81 -0
  115. data/spec/fixtures/vcr_cassettes/location.yml +305 -0
  116. data/spec/fixtures/vcr_cassettes/not_found.yml +71 -0
  117. data/spec/fixtures/vcr_cassettes/nyancat.yml +112 -0
  118. data/spec/fixtures/vcr_cassettes/nyancat_include.yml +112 -0
  119. data/spec/fixtures/vcr_cassettes/reloaded_entry.yml +112 -0
  120. data/spec/fixtures/vcr_cassettes/space.yml +81 -0
  121. data/spec/fixtures/vcr_cassettes/unauthorized.yml +64 -0
  122. data/spec/link_spec.rb +40 -0
  123. data/spec/locale_spec.rb +20 -0
  124. data/spec/location_spec.rb +30 -0
  125. data/spec/request_spec.rb +48 -0
  126. data/spec/resource_spec.rb +52 -0
  127. data/spec/response_spec.rb +50 -0
  128. data/spec/space_spec.rb +36 -0
  129. data/spec/spec_helper.rb +6 -0
  130. data/spec/support/client.rb +6 -0
  131. data/spec/support/json_responses.rb +11 -0
  132. data/spec/support/vcr.rb +16 -0
  133. metadata +374 -0
@@ -0,0 +1,193 @@
1
+ require_relative 'request'
2
+ require_relative 'response'
3
+ require_relative 'resource_builder'
4
+ require 'http'
5
+
6
+ module Contentful
7
+ # The client object is initialized with a space and a key and then used
8
+ # for querying resources from this space.
9
+ # See README for details
10
+ class Client
11
+ DEFAULT_CONFIGURATION = {
12
+ secure: true,
13
+ raise_errors: true,
14
+ dynamic_entries: :manual,
15
+ api_url: 'cdn.contentful.com',
16
+ api_version: 1,
17
+ authentication_mechanism: :header,
18
+ resource_builder: ResourceBuilder,
19
+ resource_mapping: {},
20
+ raw_mode: false,
21
+ }
22
+
23
+ attr_reader :configuration, :dynamic_entry_cache
24
+
25
+
26
+ # Wraps the actual HTTP request
27
+ def self.get_http(url, query, headers = {})
28
+ HTTP[headers].get(url, params: query)
29
+ end
30
+
31
+ def initialize(given_configuration = {})
32
+ @configuration = default_configuration.merge(given_configuration)
33
+ normalize_configuration!
34
+ validate_configuration!
35
+
36
+ if configuration[:dynamic_entries] == :auto
37
+ update_dynamic_entry_cache!
38
+ else
39
+ @dynamic_entry_cache = {}
40
+ end
41
+ end
42
+
43
+ # Returns the default configuration
44
+ def default_configuration
45
+ DEFAULT_CONFIGURATION
46
+ end
47
+
48
+ # Gets the client's space
49
+ # Takes an optional hash of query options
50
+ # Returns a Contentful::Space
51
+ def space(query = {})
52
+ Request.new(self, '', query).get
53
+ end
54
+
55
+ # Gets a specific content type
56
+ # Takes an id and an optional hash of query options
57
+ # Returns a Contentful::ContentType
58
+ def content_type(id, query = {})
59
+ Request.new(self, '/content_types', query, id).get
60
+ end
61
+
62
+ # Gets a collection of content types
63
+ # Takes an optional hash of query options
64
+ # Returns a Contentful::Array of Contentful::ContentType
65
+ def content_types(query = {})
66
+ Request.new(self, '/content_types', query).get
67
+ end
68
+
69
+ # Gets a specific entry
70
+ # Takes an id and an optional hash of query options
71
+ # Returns a Contentful::Entry
72
+ def entry(id, query = {})
73
+ Request.new(self, '/entries', query, id).get
74
+ end
75
+
76
+ # Gets a collection of entries
77
+ # Takes an optional hash of query options
78
+ # Returns a Contentful::Array of Contentful::Entry
79
+ def entries(query = {})
80
+ Request.new(self, '/entries', query).get
81
+ end
82
+
83
+ # Gets a specific asset
84
+ # Takes an id and an optional hash of query options
85
+ # Returns a Contentful::Asset
86
+ def asset(id, query = {})
87
+ Request.new(self, '/assets', query, id).get
88
+ end
89
+
90
+ # Gets a collection of assets
91
+ # Takes an optional hash of query options
92
+ # Returns a Contentful::Array of Contentful::Asset
93
+ def assets(query = {})
94
+ Request.new(self, '/assets', query).get
95
+ end
96
+
97
+ # Returns the base url for all of the client's requests
98
+ def base_url
99
+ "http#{configuration[:secure] ? 's' : ''}://#{configuration[:api_url]}/spaces/#{configuration[:space]}"
100
+ end
101
+
102
+ # Returns the headers used for the HTTP requests
103
+ def request_headers
104
+ headers = { "User-Agent" => "RubyContentfulGem/#{Contentful::VERSION}" }
105
+ headers["Authorization"] = "Bearer #{configuration[:access_token]}" if configuration[:authentication_mechanism] == :header
106
+ headers["Content-Type"] = "application/vnd.contentful.delivery.v#{configuration[:api_version].to_i}+json" if configuration[:api_version]
107
+
108
+ headers
109
+ end
110
+
111
+ # Patches a query hash with the client configurations for queries
112
+ def request_query(query)
113
+ if configuration[:authentication_mechanism] == :query_string
114
+ query["access_token"] = configuration[:access_token]
115
+ end
116
+
117
+ query
118
+ end
119
+
120
+ # Get a Contentful::Request object
121
+ # Set second parameter to false to deactivate Resource building and
122
+ # return Response objects instead
123
+ def get(request, build_resource = true)
124
+ response = Response.new(
125
+ self.class.get_http(
126
+ base_url + request.url,
127
+ request_query(request.query),
128
+ request_headers,
129
+ ), request
130
+ )
131
+
132
+ return response if !build_resource || configuration[:raw_mode]
133
+
134
+ result = configuration[:resource_builder].new(self, response, configuration[:resource_mapping]).run
135
+ raise result if result.is_a?(Error) && configuration[:raise_errors]
136
+ result
137
+ end
138
+
139
+ # Use this method together with the client's :dynamic_entries configuration.
140
+ # See README for details.
141
+ def update_dynamic_entry_cache!
142
+ @dynamic_entry_cache = Hash[
143
+ content_types(limit: 0).map{ |ct|
144
+ [
145
+ ct.id.to_sym,
146
+ DynamicEntry.create(ct),
147
+ ]
148
+ }
149
+ ]
150
+ end
151
+
152
+ # Use this method to manually register a dynamic entry
153
+ # See examples/dynamic_entries.rb
154
+ def register_dynamic_entry(key, klass)
155
+ @dynamic_entry_cache[key.to_sym] = klass
156
+ end
157
+
158
+
159
+ private
160
+
161
+ def normalize_configuration!
162
+ [:space, :access_token, :api_url].each{ |s| configuration[s] = configuration[s].to_s }
163
+ configuration[:authentication_mechanism] = configuration[:authentication_mechanism].to_sym
164
+ end
165
+
166
+ def validate_configuration!
167
+ if configuration[:space].empty?
168
+ raise ArgumentError, "You will need to initialize a client with a :space"
169
+ end
170
+
171
+ if configuration[:access_token].empty?
172
+ raise ArgumentError, "You will need to initialize a client with an :access_token"
173
+ end
174
+
175
+ if configuration[:api_url].empty?
176
+ raise ArgumentError, "The client configuration needs to contain an :api_url"
177
+ end
178
+
179
+ unless configuration[:api_version].to_i >= 0
180
+ raise ArgumentError, "The :api_version must be a positive number or nil"
181
+ end
182
+
183
+ unless [:header, :query_string].include? configuration[:authentication_mechanism]
184
+ raise ArgumentError, "The authentication mechanism must be :header or :query_string"
185
+ end
186
+
187
+ unless [:auto, :manual].include? configuration[:dynamic_entries]
188
+ raise ArgumentError, "The :dynamic_entries mode must be :auto or :manual"
189
+ end
190
+ end
191
+ end
192
+ end
193
+
@@ -0,0 +1,16 @@
1
+ require_relative 'resource'
2
+ require_relative 'field'
3
+
4
+ module Contentful
5
+ # Resource Class for Content Types
6
+ # https://www.contentful.com/developers/documentation/content-delivery-api/#content-types
7
+ class ContentType
8
+ include Contentful::Resource
9
+ include Contentful::Resource::SystemProperties
10
+
11
+ property :name, :string
12
+ property :description, :string
13
+ property :fields, Field
14
+ property :displayField, :string
15
+ end
16
+ end
@@ -0,0 +1,54 @@
1
+ require_relative 'resource'
2
+ require_relative 'resource/fields'
3
+ require_relative 'location'
4
+
5
+ module Contentful
6
+ class DynamicEntry < Entry
7
+ KNOWN_TYPES = {
8
+ 'String' => :string,
9
+ 'Text' => :string,
10
+ 'Symbol' => :string,
11
+ 'Integer' => :integer,
12
+ 'Float' => :float,
13
+ 'Boolean' => :boolean,
14
+ 'Date' => :date,
15
+ 'Location' => Location,
16
+ }
17
+
18
+ def self.create(content_type)
19
+ unless content_type.is_a? ContentType
20
+ content_type = ContentType.new(content_type)
21
+ end
22
+
23
+ fields_coercions = Hash[
24
+ content_type.fields.map{ |field|
25
+ [field.id.to_sym, KNOWN_TYPES[field.type]]
26
+ }
27
+ ]
28
+
29
+ Class.new DynamicEntry do
30
+ content_type.fields.each{ |f|
31
+ define_method Support.snakify(f.id).to_sym do
32
+ fields[f.id.to_sym]
33
+ end
34
+ }
35
+
36
+ define_singleton_method :fields_coercions do
37
+ fields_coercions
38
+ end
39
+
40
+ define_singleton_method :content_type do
41
+ content_type
42
+ end
43
+
44
+ define_singleton_method :to_s do
45
+ "Contentful::DynamicEntry[#{content_type.id}]"
46
+ end
47
+
48
+ define_singleton_method :inspect do
49
+ "Contentful::DynamicEntry[#{content_type.id}]"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'resource'
2
+ require_relative 'resource/fields'
3
+
4
+ module Contentful
5
+ # Resource class for Entry.
6
+ # https://www.contentful.com/developers/documentation/content-delivery-api/#entries
7
+ class Entry
8
+ include Contentful::Resource
9
+ include Contentful::Resource::SystemProperties
10
+ include Contentful::Resource::Fields
11
+ end
12
+ end
@@ -0,0 +1,52 @@
1
+ module Contentful
2
+ # All errors raised by the contentful gem are either instances of Contentful::Error
3
+ # or inherit from Contentful::Error
4
+ class Error < StandardError
5
+ attr_reader :response
6
+
7
+ def initialize(response)
8
+ @response = response
9
+ super @response.error_message
10
+ end
11
+
12
+ # Shortcut for creating specialized error classes
13
+ # USAGE rescue Contentful::Error[404]
14
+ def self.[](no)
15
+ case no
16
+ when 404
17
+ NotFound
18
+ when 400
19
+ BadRequest
20
+ when 403
21
+ AccessDenied
22
+ when 401
23
+ Unauthorized
24
+ when 500
25
+ ServerError
26
+ else
27
+ Error
28
+ end
29
+ end
30
+ end
31
+
32
+ # 404
33
+ class NotFound < Error; end
34
+
35
+ # 400
36
+ class BadRequest < Error; end
37
+
38
+ # 403
39
+ class AccessDenied < Error; end
40
+
41
+ # 401
42
+ class Unauthorized < Error; end
43
+
44
+ # 500
45
+ class ServerError < Error; end
46
+
47
+ # Raised when response is no valid json
48
+ class UnparsableJson < Error; end
49
+
50
+ # Raised when response is not parsable as a Contentful::Resource
51
+ class UnparsableResource < Error; end
52
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'resource'
2
+
3
+ module Contentful
4
+ # A ContentType's field schema
5
+ # See https://www.contentful.com/developers/documentation/content-management-api/#resources-content-types-fields
6
+ class Field
7
+ include Contentful::Resource
8
+
9
+ property :id, :string
10
+ property :name, :string
11
+ property :type, :string
12
+ property :items, Field
13
+ property :required, :boolean
14
+ property :localized, :boolean
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'resource'
2
+
3
+ module Contentful
4
+ # An Assets's file info
5
+ class File
6
+ include Contentful::Resource
7
+
8
+ property :fileName, :string
9
+ property :contentType, :string
10
+ property :details
11
+ property :url, :string
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'resource'
2
+
3
+ module Contentful
4
+ # Resource Class for Links
5
+ # https://www.contentful.com/developers/documentation/content-delivery-api/#links
6
+ class Link
7
+ include Contentful::Resource
8
+ include Contentful::Resource::SystemProperties
9
+
10
+ # Queries contentful for the Resource the Link is refering to
11
+ # Takes an optional query hash
12
+ def resolve(query = {})
13
+ id_and_query = [(id unless link_type == "Space")].compact + [query]
14
+ client.public_send(
15
+ Contentful::Support.snakify(link_type).to_sym,
16
+ *id_and_query
17
+ )
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'resource'
2
+
3
+ module Contentful
4
+ # A Locale definition as included in Space
5
+ # Read more about Localization at https://www.contentful.com/developers/documentation/content-delivery-api/#i18n
6
+ class Locale
7
+ include Contentful::Resource
8
+
9
+ property :code, :string
10
+ property :name, :string
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'resource'
2
+
3
+ module Contentful
4
+ # Location Field Type
5
+ # You can directly query for them: https://www.contentful.com/developers/documentation/content-delivery-api/#search-filter-geo
6
+ class Location
7
+ include Contentful::Resource
8
+
9
+ property :lat, :float
10
+ property :lon, :float
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ module Contentful
2
+ # This object represents a request that is to be made. It gets initialized by the client
3
+ # with domain specific logic. The client later uses the Request's #url and #query methods
4
+ # to execute the HTTP request.
5
+ class Request
6
+ attr_reader :client, :type, :query, :id
7
+
8
+ def initialize(client, endpoint, query = {}, id = nil)
9
+ @client = client
10
+ @endpoint = endpoint
11
+ @query = !query || query.empty? ? nil : Support.symbolize_keys(query)
12
+
13
+ if id
14
+ @type = :single
15
+ @id = URI.escape(id)
16
+ else
17
+ @type = :multi
18
+ @id = nil
19
+ end
20
+ end
21
+
22
+ # Returns the final URL, relative to a contentful space
23
+ def url
24
+ "#{@endpoint}#{ @type == :single ? "/#{id}" : '' }"
25
+ end
26
+
27
+ # Delegates the actual HTTP work to the client
28
+ def get
29
+ client.get(self)
30
+ end
31
+
32
+ # Returns a new Request object with the same data
33
+ def copy
34
+ Marshal.load(Marshal.dump(self))
35
+ end
36
+ end
37
+ end