contentful 0.1.0

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