apple-news 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +44 -20
  3. data/lib/apple-news.rb +2 -2
  4. data/lib/apple-news/addition.rb +21 -0
  5. data/lib/apple-news/additions/base.rb +1 -1
  6. data/lib/apple-news/animation.rb +21 -0
  7. data/lib/apple-news/animations/base.rb +1 -1
  8. data/lib/apple-news/article.rb +29 -17
  9. data/lib/apple-news/{document → article}/attachments.rb +1 -1
  10. data/lib/apple-news/article/persistence.rb +63 -0
  11. data/lib/apple-news/behavior.rb +21 -0
  12. data/lib/apple-news/behaviors/base.rb +1 -1
  13. data/lib/apple-news/channel.rb +4 -4
  14. data/lib/apple-news/component.rb +4 -0
  15. data/lib/apple-news/components/base.rb +4 -2
  16. data/lib/apple-news/components/chapter.rb +1 -1
  17. data/lib/apple-news/components/container.rb +1 -1
  18. data/lib/apple-news/components/divider.rb +1 -1
  19. data/lib/apple-news/components/gallery.rb +1 -1
  20. data/lib/apple-news/components/mosaic.rb +1 -1
  21. data/lib/apple-news/components/section.rb +1 -1
  22. data/lib/apple-news/components/text.rb +7 -2
  23. data/lib/apple-news/document.rb +17 -14
  24. data/lib/apple-news/properties.rb +26 -9
  25. data/lib/apple-news/properties/advertising_settings.rb +4 -1
  26. data/lib/apple-news/properties/caption_descriptor.rb +3 -1
  27. data/lib/apple-news/requests/delete.rb +28 -0
  28. data/lib/apple-news/requests/post.rb +1 -0
  29. data/lib/apple-news/resource.rb +16 -5
  30. data/lib/apple-news/scene.rb +21 -0
  31. data/lib/apple-news/scenes/base.rb +1 -1
  32. data/lib/apple-news/section.rb +4 -4
  33. data/lib/apple-news/version.rb +1 -1
  34. metadata +5 -5
  35. data/lib/apple-news/document/metadata.rb +0 -43
  36. data/lib/apple-news/document/persistence.rb +0 -42
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 78e84e4d9bb33e84d5830ed625262332b71aeb11
4
- data.tar.gz: 14b6d402c7fdc66f2c1d4c2912f112f26fdcd0a1
3
+ metadata.gz: 1d4c2accd3a83c76997106e65005ca440498773b
4
+ data.tar.gz: f10b56d9b207b1f495b8cecd7dbc8921e9301077
5
5
  SHA512:
6
- metadata.gz: 2636c8c7ea2451cfcde19a482237522a481f1af933d425812ac7d82b99bdd8bbda6720bf7ab44166b56bbbe4b6b17feb58287de9076f0d47e27ace65e4d7ed76
7
- data.tar.gz: 8630518537f2a847d5809ef5346e59e4c285e6ae875f2737ae1d2ff67a02d7ef847f33407abc1f4890477e0d120e05c363a1e8387825224cd5cd4fb116457a4f
6
+ metadata.gz: 5ab67e0d195a10ed907963461796342f0d2cbee21a41afc230d7077e6422502d162e7bf0c169c05aecb9cc570f026c725a8557bb776abdfec8ef78dcc14ddd00
7
+ data.tar.gz: 856cc6fab4feae7ea0cafc6555be23406f3315a61eada4fa3434af9a340ea18cc38add94ddd4b324a6d7daeb1a8068455362d79c17aeee196bf9c82d83700de0
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A fully featured gem for building Apple News documents and interfacing with the Apple News API.
4
4
 
5
- **NOTE:** this is very much a work in progress. There are a lot of things that don't work yet, and some things will probbaly change (even drastically), but the basics are here. You can create and fetch articles, and fetch information about channels and sections.
5
+ **NOTE:** this is very much a work in progress. There will probably be bugs, but for the most part, everything should be working.
6
6
 
7
7
  ## Installation
8
8
 
@@ -70,27 +70,27 @@ channel.default_section.articles # all articles in the default section
70
70
 
71
71
  ## Submitting Articles
72
72
 
73
- Apple News articles are submitted as "bundles", with the article content and attached files together in one request. Because of this, we have the concept of a Document. The Document is the high-level abstraction that contains all of the contents for submission to the Apple News API, as well as the extra metadata that lets us control things like setting whether the article is just a preview or if it's sponsored.
73
+ Apple News articles are submitted as "bundles", with the article content and attached files together in one request. Because of this, we have the concept of a Document. The Document is what contains the actual article content in the Apple News JSON format. The Article encapsulates this and also includes all of the files that will be submitted along with the document.
74
74
 
75
- ### Building Articles
75
+ ### Building Documents
76
76
 
77
- Articles are built using the `AppleNews::Article` class. All articles must have an identifier (a string generated by you to determine article uniqueness, not the ID returned from the Apple News API), title, layout, components, and a default component text style.
77
+ Documents are built using the `AppleNews::Document` class. All documents must have an identifier (a string generated by you to determine article uniqueness, not the ID returned from the Apple News API), title, layout, components, and a default component text style.
78
78
 
79
79
  In order to maintain idiomatic Ruby, all properties are accessed/set via the underscore version of the actual Apple News API property. For example, `componentTextStyles` becomes `component_text_styles`.
80
80
 
81
81
  ``` ruby
82
- article = AppleNews::Article.new
83
- article.identifier = "1234"
84
- article.title = "Test Article"
85
- article.layout = AppleNews::Layout.new(columns: 1, width: 1024)
86
- article.component_text_styles[:default] = AppleNews::Style::ComponentText.new(
82
+ document = AppleNews::Document.new
83
+ document.identifier = "1234"
84
+ document.title = "Test Article"
85
+ document.layout = AppleNews::Layout.new(columns: 1, width: 1024)
86
+ document.component_text_styles[:default] = AppleNews::Style::ComponentText.new(
87
87
  font_name: 'Georgia',
88
88
  font_size: 14,
89
89
  text_color: '#000000'
90
90
  )
91
91
 
92
- article.components << AppleNews::Component::Heading.new(text: "Test Article")
93
- article.components << AppleNews::Component::Body.new(text: "Just testing out this Ruby gem!")
92
+ document.components << AppleNews::Component::Heading.new(text: "Test Article")
93
+ document.components << AppleNews::Component::Body.new(text: "Just testing out this Ruby gem!")
94
94
  ```
95
95
 
96
96
  Every component, style, property, etc. as defined by the [API documentation](https://developer.apple.com/library/ios/documentation/General/Conceptual/Apple_News_Format_Ref/index.html) has its own class. Each property can be set either by the constructor, or by calling the accessor method on the object. For example:
@@ -104,28 +104,52 @@ component = AppleNews::Component::Instagram.new
104
104
  component.url = "https://www.instagram.com/p/BB7mr0hsS4U/"
105
105
  ```
106
106
 
107
- ### Creating a Document
107
+ ### Creating an Article
108
108
 
109
- A document must have an article. Once it's created, you can set metadata flags and add files to the document bundle.
109
+ An article must have a document. Once it's created, you can add files to the article bundle.
110
110
 
111
111
  ``` ruby
112
- document = AppleNews::Document.new(article)
113
- document.is_preview = true
112
+ article = AppleNews::Article.new(nil, document: document)
113
+ article.is_preview = true
114
114
 
115
115
  # There are 3 different ways you can add a file to the document
116
- document.add_file(File.new("/path/to/image.jpg"))
117
- document.add_file_at_path("/path/to/image.jpg")
118
- document.add_string_as_file("image.jpg", image_contents, "image/jpeg")
116
+ article.add_file(File.new("/path/to/image.jpg"))
117
+ article.add_file_at_path("/path/to/image.jpg")
118
+ article.add_string_as_file("image.jpg", image_contents, "image/jpeg")
119
119
  ```
120
120
 
121
- ### Saving a Document
121
+ ### Saving an Article
122
122
 
123
123
  Once you have your document built, you can submit it to the API.
124
124
 
125
125
  ```
126
- document.save!
126
+ article.save!
127
127
  ```
128
128
 
129
+ It will return `true` if saving succeeds, otherwise it will return an array of errors as provided by the API.
130
+
131
+ ### Updating an Article
132
+
133
+ Updating an article works the same as creating an article.
134
+
135
+ ``` ruby
136
+ article = AppleNews::Article.new("25c4666a-26d9-48c0-88c1-d8c84fa94ecd")
137
+ article.document.title = "New Title!"
138
+ article.is_preview = false
139
+ article.save!
140
+ ```
141
+
142
+ ## Deleting Articles
143
+
144
+ You can easily delete articles once they're fetched.
145
+
146
+ ``` ruby
147
+ article = AppleNews::Article.new("25c4666a-26d9-48c0-88c1-d8c84fa94ecd")
148
+ article.delete!
149
+ ```
150
+
151
+ This will return true when successful, otherwise it will return an array of errors as provided by the API.
152
+
129
153
  ## Development
130
154
 
131
155
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/lib/apple-news.rb CHANGED
@@ -14,19 +14,19 @@ require "apple-news/properties"
14
14
  require "apple-news/addition"
15
15
  require "apple-news/animation"
16
16
  require "apple-news/behavior"
17
- require "apple-news/component"
18
17
  require "apple-news/component_layout"
19
18
  require "apple-news/layout"
20
19
  require "apple-news/metadata"
21
20
  require "apple-news/property"
22
21
  require "apple-news/scene"
23
22
  require "apple-news/style"
23
+ require "apple-news/component"
24
24
 
25
25
  require "apple-news/resource"
26
26
  require "apple-news/links"
27
27
  require "apple-news/configuration"
28
- require "apple-news/document"
29
28
  require "apple-news/article"
29
+ require "apple-news/document"
30
30
  require "apple-news/channel"
31
31
  require "apple-news/request"
32
32
  require "apple-news/section"
@@ -3,5 +3,26 @@ Dir["#{File.dirname(__FILE__)}/additions/*.rb"].each { |path| require path }
3
3
 
4
4
  module AppleNews
5
5
  module Addition
6
+ extend self
7
+
8
+ def factory(data)
9
+ return if data.nil?
10
+
11
+ additions.each do |addition|
12
+ if addition.type == data[:type]
13
+ return addition.new(data)
14
+ end
15
+ end
16
+
17
+ nil
18
+ end
19
+
20
+ private
21
+
22
+ def additions
23
+ @additions ||= self.constants.
24
+ map { |const| self.const_get(const) }.
25
+ select { |const| const.name.demodulize != "Base" && const.is_a?(Class) }
26
+ end
6
27
  end
7
28
  end
@@ -4,7 +4,7 @@ module AppleNews
4
4
  include Properties
5
5
 
6
6
  def self.type(val = nil)
7
- val.nil? ? _required_property_map[:type] : required_property(:type, val)
7
+ val.nil? ? _required_property_map[:type][:default] : required_property(:type, val)
8
8
  end
9
9
 
10
10
  def type
@@ -3,5 +3,26 @@ Dir["#{File.dirname(__FILE__)}/animations/*.rb"].each { |path| require path }
3
3
 
4
4
  module AppleNews
5
5
  module Animation
6
+ extend self
7
+
8
+ def factory(data)
9
+ return if data.nil?
10
+
11
+ animations.each do |animation|
12
+ if animation.type == data[:type]
13
+ return animation.new(data)
14
+ end
15
+ end
16
+
17
+ nil
18
+ end
19
+
20
+ private
21
+
22
+ def animations
23
+ @animations ||= self.constants.
24
+ map { |const| self.const_get(const) }.
25
+ select { |const| const.name.demodulize != "Base" && const.is_a?(Class) }
26
+ end
6
27
  end
7
28
  end
@@ -6,7 +6,7 @@ module AppleNews
6
6
  optional_property :user_controllable
7
7
 
8
8
  def self.type(val = nil)
9
- val.nil? ? _required_property_map[:type] : required_property(:type, val)
9
+ val.nil? ? _required_property_map[:type][:default] : required_property(:type, val)
10
10
  end
11
11
 
12
12
  def type
@@ -1,33 +1,45 @@
1
+ require 'apple-news/article/attachments'
2
+ require 'apple-news/article/persistence'
3
+
1
4
  module AppleNews
2
5
  class Article
6
+ extend Forwardable
7
+
8
+ include Attachments
9
+ include Persistence
3
10
  include Resource
4
11
  include Properties
5
12
 
6
- attr_reader :id
13
+ optional_properties :is_sponsored, :is_preview, :accessory_text, :revision
14
+ optional_property :links, {}
7
15
 
8
- required_properties :identifier, :title, :layout
9
- required_property :components, []
10
- required_property :component_text_styles, {}
16
+ attr_reader :id
17
+ attr_accessor :document
18
+ def_delegator :@document, :title
11
19
 
12
- required_property :version, "1.1"
13
- required_property :language, "en"
20
+ def initialize(id = nil, data = {})
21
+ super(data)
14
22
 
15
- optional_properties :advertising_settings, :subtitle, :metadata, :document_style,
16
- :text_styles, :component_layouts, :component_styles
23
+ @resource_path = "/articles"
24
+ @id = id
17
25
 
18
- def initialize(id = nil, opts = nil)
19
- super(opts)
26
+ document = (data[:document] || data['document'])
27
+ @document = document.is_a?(AppleNews::Document) ? document : Document.new(document)
28
+ @files = {}
20
29
 
21
- @id = id
22
- @url = "/articles/#{id}"
23
- @metadata = Metadata.new((opts || {}).fetch(:metadata, {}))
30
+ # These are read-only properties that are not submitted to the API
31
+ @share_url = data['shareUrl']
32
+ @state = data['state']
24
33
 
25
- hydrate! if !id.nil? && opts.nil?
34
+ hydrate! if !id.nil? && data.keys.size == 0
26
35
  end
27
36
 
28
- def persisted?
29
- !@id.nil?
37
+ private
38
+
39
+ def hydrate!
40
+ data = fetch_data['data']
41
+ @document = Document.new(data.delete('document'))
42
+ load_properties(data)
30
43
  end
31
- alias_method :saved?, :persisted?
32
44
  end
33
45
  end
@@ -1,5 +1,5 @@
1
1
  module AppleNews
2
- class Document
2
+ class Article
3
3
  module Attachments
4
4
  extend ActiveSupport::Concern
5
5
 
@@ -0,0 +1,63 @@
1
+ module AppleNews
2
+ class Article
3
+ module Persistence
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ def save!
8
+ request = Request::Post.new(endpoint_url)
9
+ request.fields = {
10
+ 'metadata' => metadata_field,
11
+ 'article.json' => document_json
12
+ }.merge(@files)
13
+
14
+ resp = request.call
15
+
16
+ return resp['errors'] if resp.has_key?('errors')
17
+
18
+ @id = resp['data']['id']
19
+ load_properties(resp['data'])
20
+
21
+ true
22
+ end
23
+
24
+ def persisted?
25
+ !@id.nil?
26
+ end
27
+ alias_method :saved?, :persisted?
28
+
29
+ def delete!
30
+ request = Request::Delete.new(endpoint_url)
31
+ resp = request.call
32
+
33
+ return resp['errors'] if resp.is_a?(Hash) && resp.has_key?('errors')
34
+ @id = nil
35
+
36
+ true
37
+ end
38
+
39
+ private
40
+
41
+ def endpoint_url
42
+ if persisted?
43
+ "/articles/#{id}"
44
+ else
45
+ "/channels/#{AppleNews.config.channel_id}/articles"
46
+ end
47
+ end
48
+
49
+ def metadata_field
50
+ JSON.dump({ data: self.as_json })
51
+ end
52
+
53
+ def document_json
54
+ UploadIO.new(
55
+ StringIO.new(JSON.dump(document.as_json)),
56
+ "application/json",
57
+ "article.json"
58
+ )
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -3,5 +3,26 @@ Dir["#{File.dirname(__FILE__)}/behaviors/*.rb"].each { |path| require path }
3
3
 
4
4
  module AppleNews
5
5
  module Behavior
6
+ extend self
7
+
8
+ def factory(data)
9
+ return if data.nil?
10
+
11
+ behaviors.each do |behavior|
12
+ if behavior.type == data[:type]
13
+ return behavior.new(data)
14
+ end
15
+ end
16
+
17
+ nil
18
+ end
19
+
20
+ private
21
+
22
+ def behaviors
23
+ @behaviors ||= self.constants.
24
+ map { |const| self.const_get(const) }.
25
+ select { |const| const.name.demodulize != "Base" && const.is_a?(Class) }
26
+ end
6
27
  end
7
28
  end
@@ -4,7 +4,7 @@ module AppleNews
4
4
  include Properties
5
5
 
6
6
  def self.type(val = nil)
7
- val.nil? ? _required_property_map[:type] : required_property(:type, val)
7
+ val.nil? ? _required_property_map[:type][:default] : required_property(:type, val)
8
8
  end
9
9
 
10
10
  def type
@@ -4,7 +4,7 @@ module AppleNews
4
4
  include Links
5
5
 
6
6
  attr_reader :id, :type, :name, :website, :links, :created_at, :modified_at,
7
- :default_section
7
+ :default_section, :share_url
8
8
 
9
9
  def self.current
10
10
  self.new(AppleNews.config.channel_id)
@@ -12,9 +12,9 @@ module AppleNews
12
12
 
13
13
  def initialize(id, data = nil)
14
14
  @id = id
15
- @url = "/channels/#{id}"
15
+ @resource_path = "/channels"
16
16
 
17
- data.nil? ? hydrate! : process_data(data)
17
+ data.nil? ? hydrate! : set_read_only_properties(data)
18
18
  end
19
19
 
20
20
  def default_section
@@ -33,7 +33,7 @@ module AppleNews
33
33
  request = Request::Get.new("/channels/#{id}/articles")
34
34
  resp = request.call(params)
35
35
  resp['data'].map do |article|
36
- Article.new(article['id'], article)
36
+ Article.new(article['id'])
37
37
  end
38
38
  end
39
39
  end
@@ -10,11 +10,15 @@ module AppleNews
10
10
  extend self
11
11
 
12
12
  def factory(data)
13
+ return if data.nil?
14
+
13
15
  components.each do |component|
14
16
  if component.role == data[:role]
15
17
  return component.new(data)
16
18
  end
17
19
  end
20
+
21
+ nil
18
22
  end
19
23
 
20
24
  private
@@ -3,10 +3,12 @@ module AppleNews
3
3
  class Base
4
4
  include Properties
5
5
 
6
- optional_properties :identifier, :layout, :style
6
+ optional_property :identifier
7
+ optional_property :layout, nil, ComponentLayout
8
+ optional_property :style, nil, Style::Component
7
9
 
8
10
  def self.role(val = nil)
9
- val.nil? ? _required_property_map[:role] : required_property(:role, val)
11
+ val.nil? ? _required_property_map[:role][:default] : required_property(:role, val)
10
12
  end
11
13
 
12
14
  def role
@@ -4,7 +4,7 @@ module AppleNews
4
4
  module Component
5
5
  class Chapter < Container
6
6
  role "chapter"
7
- optional_property :scene
7
+ optional_property :scene, nil, Scene, :factory
8
8
  end
9
9
  end
10
10
  end
@@ -2,7 +2,7 @@ module AppleNews
2
2
  module Component
3
3
  class Container < Base
4
4
  role "container"
5
- optional_property :components, []
5
+ optional_property :components, [], Component, :factory
6
6
  end
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module AppleNews
2
2
  module Component
3
3
  class Divider < Base
4
4
  role "divider"
5
- optional_property :stroke
5
+ optional_property :stroke, nil, Style::Stroke
6
6
  end
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module AppleNews
2
2
  module Component
3
3
  class Gallery < Base
4
4
  role "gallery"
5
- required_property :items, []
5
+ required_property :items, [], Property::GalleryItem
6
6
  end
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module AppleNews
2
2
  module Component
3
3
  class Mosaic < Base
4
4
  role "mosaic"
5
- required_property :items, []
5
+ required_property :items, [], Property::GalleryItem
6
6
  end
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@ module AppleNews
2
2
  module Component
3
3
  class Section < Container
4
4
  role "section"
5
- optional_property :scene
5
+ optional_property :scene, nil, Scene, :factory
6
6
  end
7
7
  end
8
8
  end
@@ -5,8 +5,13 @@ module AppleNews
5
5
 
6
6
  included do
7
7
  required_property :text
8
- optional_properties :anchor, :animation, :behavior, :format,
9
- :inlineTextStyles, :textStyle
8
+ optional_property :format
9
+
10
+ optional_property :anchor, nil, Property::Anchor
11
+ optional_property :animation, nil, Animation, :factory
12
+ optional_property :behavior, nil, Behavior, :factory
13
+ optional_property :inline_text_styles, [], Style::InlineText
14
+ optional_property :text_style, nil, Style::Text
10
15
  end
11
16
  end
12
17
  end
@@ -1,20 +1,23 @@
1
- require 'apple-news/document/attachments'
2
- require 'apple-news/document/metadata'
3
- require 'apple-news/document/persistence'
4
-
5
1
  module AppleNews
6
2
  class Document
7
- include Attachments
8
- include Metadata
9
- include Persistence
10
- include Resource
3
+ include Properties
4
+
5
+ attr_accessor :id
6
+
7
+ required_properties :identifier, :title
8
+ required_property :layout, nil, Layout
9
+ required_property :components, [], Component, :factory
10
+ required_property :component_text_styles, {}, Style::ComponentText
11
11
 
12
- attr_accessor :article, :metadata
12
+ required_property :version, "1.1"
13
+ required_property :language, "en"
13
14
 
14
- def initialize(article = nil, metadata = {})
15
- @article = article
16
- @metadata = {}
17
- @files = {}
18
- end
15
+ optional_property :advertising_settings, nil, Property::AdvertisingSettings
16
+ optional_property :metadata, nil, Metadata
17
+ optional_property :document_style, nil, Style::Document
18
+ optional_property :text_styles, {}, Style::Text
19
+ optional_property :component_layouts, {}, ComponentLayout
20
+ optional_property :component_styles, {}, Style::Component
21
+ optional_property :subtitle
19
22
  end
20
23
  end
@@ -3,11 +3,26 @@ module AppleNews
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  def initialize(opts = nil)
6
- if !opts.nil?
7
- opts = ActiveSupport::HashWithIndifferentAccess.new(opts)
8
- self.class.properties.each do |prop, default|
9
- instance_variable_set "@#{prop}", opts.fetch(prop, default)
6
+ load_properties(opts) if !opts.nil?
7
+ end
8
+
9
+ def load_properties(opts)
10
+ opts = ActiveSupport::HashWithIndifferentAccess.new(opts)
11
+ self.class.properties.each do |prop, settings|
12
+ val = if !settings[:klass].nil?
13
+ assigned_val = opts.fetch(prop, settings[:default])
14
+ if settings[:default].is_a?(Array)
15
+ assigned_val.map { |v| settings[:klass].send(settings[:init_method], v) }
16
+ elsif settings[:default].is_a?(Hash)
17
+ Hash[assigned_val.map { |k, v| [k, settings[:klass].send(settings[:init_method], v)]}]
18
+ else
19
+ assigned_val.nil? ? nil : settings[:klass].send(settings[:init_method], assigned_val)
20
+ end
21
+ else
22
+ opts.fetch(prop, settings[:default])
10
23
  end
24
+
25
+ instance_variable_set "@#{prop}", val
11
26
  end
12
27
  end
13
28
 
@@ -40,7 +55,9 @@ module AppleNews
40
55
  elsif send(key).is_a?(Array)
41
56
  send(key).map(&:as_json)
42
57
  elsif send(key).is_a?(Hash)
43
- Hash[send(key).map {|k, v| [k.to_s, v.as_json]}]
58
+ Hash[send(key).map {|k, v|
59
+ [k.to_s, v.respond_to?(:as_json) ? v.as_json : v]
60
+ }]
44
61
  else
45
62
  send(key)
46
63
  end
@@ -61,8 +78,8 @@ module AppleNews
61
78
  args.each { |arg| required_property(arg) }
62
79
  end
63
80
 
64
- def required_property(name, default = nil)
65
- _required_property_map[name] = default
81
+ def required_property(name, default = nil, klass = nil, init_method = :new)
82
+ _required_property_map[name] = { default: default, klass: klass, init_method: init_method }
66
83
  attr_accessor name
67
84
  end
68
85
 
@@ -70,8 +87,8 @@ module AppleNews
70
87
  args.each { |arg| optional_property(arg) }
71
88
  end
72
89
 
73
- def optional_property(name, default = nil)
74
- _optional_property_map[name] = default
90
+ def optional_property(name, default = nil, klass = nil, init_method = :new)
91
+ _optional_property_map[name] = { default: default, klass: klass, init_method: init_method }
75
92
  attr_accessor name
76
93
  end
77
94
 
@@ -1,7 +1,10 @@
1
+ require 'apple-news/properties/advertising_layout'
2
+
1
3
  module AppleNews
2
4
  module Property
3
5
  class AdvertisingSettings < Base
4
- optional_properties :frequency, :layout
6
+ optional_property :frequency
7
+ optional_property :layout, nil, Property::AdvertisingLayout
5
8
  end
6
9
  end
7
10
  end
@@ -2,7 +2,9 @@ module AppleNews
2
2
  module Property
3
3
  class CaptionDescriptor < Base
4
4
  required_property :text
5
- optional_properties :text_style, :format, :inline_text_styles, :additions
5
+ optional_properties :text_style, :format
6
+ optional_property :inline_text_styles, []
7
+ optional_property :additions, []
6
8
  end
7
9
  end
8
10
  end
@@ -0,0 +1,28 @@
1
+ module AppleNews
2
+ module Request
3
+ class Delete
4
+ attr_reader :url
5
+
6
+ def initialize(url)
7
+ @config = AppleNews.config
8
+ @url = URI::parse(File.join(@config.api_base, url))
9
+ end
10
+
11
+ def call(params = {})
12
+ http = Net::HTTP.new(@url.hostname, @url.port)
13
+ http.use_ssl = true
14
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
15
+
16
+ res = http.delete(@url, headers)
17
+ res.code == '204' ? true : JSON.parse(res.body)
18
+ end
19
+
20
+ private
21
+
22
+ def headers
23
+ security = AppleNews::Security.new('DELETE', @url.to_s)
24
+ { 'Authorization' => security.authorization }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -17,6 +17,7 @@ module AppleNews
17
17
  # http.set_debug_output $stderr
18
18
 
19
19
  res = http.post(@url, content_body, headers)
20
+ # puts JSON.parse(res.body)
20
21
  JSON.parse(res.body)
21
22
  end
22
23
 
@@ -4,18 +4,29 @@ module AppleNews
4
4
 
5
5
  included do
6
6
  def update_with_data(data)
7
- process_data(data)
7
+ load_properties(data)
8
+ end
9
+
10
+ def resource_url
11
+ File.join(@resource_path, id)
8
12
  end
9
13
 
10
14
  private
11
15
 
12
16
  def hydrate!
13
- request = AppleNews::Request::Get.new(@url)
14
- resp = request.call
15
- process_data(resp['data'])
17
+ if respond_to?(:load_properties)
18
+ load_properties(fetch_data['data'])
19
+ else
20
+ set_read_only_properties(fetch_data['data'])
21
+ end
22
+ end
23
+
24
+ def fetch_data
25
+ request = AppleNews::Request::Get.new(resource_url)
26
+ request.call
16
27
  end
17
28
 
18
- def process_data(data)
29
+ def set_read_only_properties(data)
19
30
  data.each do |k, v|
20
31
  instance_variable_set("@#{k.underscore}", v)
21
32
  end
@@ -3,5 +3,26 @@ Dir["#{File.dirname(__FILE__)}/scenes/*.rb"].each { |path| require path }
3
3
 
4
4
  module AppleNews
5
5
  module Scene
6
+ extend self
7
+
8
+ def factory(data)
9
+ return if data.nil?
10
+
11
+ scenes.each do |scene|
12
+ if scene.type == data[:type]
13
+ return scene.new(data)
14
+ end
15
+ end
16
+
17
+ nil
18
+ end
19
+
20
+ private
21
+
22
+ def scenes
23
+ @scenes ||= self.constants.
24
+ map { |const| self.const_get(const) }.
25
+ select { |const| const.name.demodulize != "Base" && const.is_a?(Class) }
26
+ end
6
27
  end
7
28
  end
@@ -4,7 +4,7 @@ module AppleNews
4
4
  include Properties
5
5
 
6
6
  def self.type(val = nil)
7
- val.nil? ? _required_property_map[:type] : required_property(:type, val)
7
+ val.nil? ? _required_property_map[:type][:default] : required_property(:type, val)
8
8
  end
9
9
 
10
10
  def type
@@ -3,13 +3,13 @@ module AppleNews
3
3
  include Resource
4
4
  include Links
5
5
 
6
- attr_reader :id, :type, :name, :is_default, :links, :created_at, :modified_at
6
+ attr_reader :id, :type, :name, :is_default, :links, :created_at, :modified_at, :share_url
7
7
 
8
8
  def initialize(id, data = nil)
9
9
  @id = id
10
- @url = "/sections/#{id}"
10
+ @resource_path = "/sections"
11
11
 
12
- data.nil? ? hydrate! : process_data(data)
12
+ data.nil? ? hydrate! : set_read_only_properties(data)
13
13
  end
14
14
 
15
15
  def channel
@@ -20,7 +20,7 @@ module AppleNews
20
20
  request = Request::Get.new("/sections/#{id}/articles")
21
21
  resp = request.call(params)
22
22
  resp['data'].map do |article|
23
- Article.new(article['id'], article)
23
+ Article.new(article['id'])
24
24
  end
25
25
  end
26
26
  end
@@ -1,3 +1,3 @@
1
1
  module AppleNews
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apple-news
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan LeFevre
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-04-28 00:00:00.000000000 Z
11
+ date: 2016-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,6 +108,8 @@ files:
108
108
  - lib/apple-news/animations/move_in.rb
109
109
  - lib/apple-news/animations/scale_fade.rb
110
110
  - lib/apple-news/article.rb
111
+ - lib/apple-news/article/attachments.rb
112
+ - lib/apple-news/article/persistence.rb
111
113
  - lib/apple-news/behavior.rb
112
114
  - lib/apple-news/behaviors/background_motion.rb
113
115
  - lib/apple-news/behaviors/background_parallax.rb
@@ -154,9 +156,6 @@ files:
154
156
  - lib/apple-news/components/video.rb
155
157
  - lib/apple-news/configuration.rb
156
158
  - lib/apple-news/document.rb
157
- - lib/apple-news/document/attachments.rb
158
- - lib/apple-news/document/metadata.rb
159
- - lib/apple-news/document/persistence.rb
160
159
  - lib/apple-news/layout.rb
161
160
  - lib/apple-news/links.rb
162
161
  - lib/apple-news/metadata.rb
@@ -174,6 +173,7 @@ files:
174
173
  - lib/apple-news/properties/offset.rb
175
174
  - lib/apple-news/property.rb
176
175
  - lib/apple-news/request.rb
176
+ - lib/apple-news/requests/delete.rb
177
177
  - lib/apple-news/requests/get.rb
178
178
  - lib/apple-news/requests/post.rb
179
179
  - lib/apple-news/resource.rb
@@ -1,43 +0,0 @@
1
- module AppleNews
2
- class Document
3
- module Metadata
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- def is_sponsored?
8
- @metadata['isSponsored']
9
- end
10
- alias_method :sponsored?, :is_sponsored?
11
-
12
- def is_sponsored=(val)
13
- @metadata['isSponsored'] = val
14
- end
15
-
16
- def is_preview?
17
- @metadata['isPreview']
18
- end
19
- alias_method :preview?, :is_preview?
20
-
21
- def is_preview=(val)
22
- @metadata['isPreview'] = val
23
- end
24
-
25
- def accessory_text
26
- @metadata['accessoryText']
27
- end
28
-
29
- def accessory_text=(val)
30
- @metadata['accessoryText'] = val
31
- end
32
-
33
- def links
34
- @metadata['links']
35
- end
36
-
37
- def links=(val)
38
- @metadata['links'] = val
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,42 +0,0 @@
1
- module AppleNews
2
- class Document
3
- module Persistence
4
- extend ActiveSupport::Concern
5
-
6
- included do
7
- def save!
8
- request = AppleNews::Request::Post.new(endpoint_url)
9
- request.fields = {
10
- 'metadata' => metadata_field,
11
- 'article.json' => article_json
12
- }.merge(@files)
13
-
14
- resp = request.call
15
- article.update_with_data(resp['data'].delete('document'))
16
- end
17
-
18
- private
19
-
20
- def endpoint_url
21
- if article.persisted?
22
- "/articles/#{id}"
23
- else
24
- "/channels/#{AppleNews.config.channel_id}/articles"
25
- end
26
- end
27
-
28
- def metadata_field
29
- JSON.dump({ data: @metadata })
30
- end
31
-
32
- def article_json
33
- UploadIO.new(
34
- StringIO.new(JSON.dump(article.as_json)),
35
- "application/json",
36
- "article.json"
37
- )
38
- end
39
- end
40
- end
41
- end
42
- end