redd 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,37 +4,13 @@ module Redd
4
4
  module Models
5
5
  # The base class for all models.
6
6
  class BasicModel
7
- class << self
8
- # @abstract Create an instance from a partial hash containing an id. The difference between
9
- # this and {#initialize} is that from_response is supposed to know how to build the whole
10
- # response from the partial.
11
- # @param client [APIClient] the api client to initialize the object with
12
- # @param hash [Hash] a partial hash
13
- # @return [BasicModel]
14
- def from_response(client, hash)
15
- new(client, hash)
16
- end
17
-
18
- # @abstract Create an instance from a value.
19
- # @param _client [APIClient] the api client to initialize the object with
20
- # @param _value [Object] the object to coerce
21
- # @return [BasicModel]
22
- def from_id(_client, _value)
23
- # TODO: abstract this out?
24
- raise "coercion not implemented for #{name}"
25
- end
26
-
27
- # @return [Hash<Symbol, #from_id>] a mapping of keys to models
28
- def coerced_attributes
29
- @coerced_attributes ||= {}
30
- end
31
-
32
- # Mark an attribute to coerce.
33
- # @param name [Symbol] the attribute to coerce
34
- # @param model [#from_id, nil] a model to coerce it to
35
- def coerce_attribute(name, model = nil)
36
- coerced_attributes[name] = model
37
- end
7
+ # @abstract Create an instance from a value.
8
+ # @param _client [APIClient] the api client to initialize the object with
9
+ # @param _value [Object] the object to coerce
10
+ # @return [BasicModel]
11
+ def self.from_id(_client, _value)
12
+ # TODO: abstract this out?
13
+ raise "coercion not implemented for #{name}"
38
14
  end
39
15
 
40
16
  # @return [APIClient] the client the model was initialized with
@@ -46,13 +22,11 @@ module Redd
46
22
  def initialize(client, attributes = {})
47
23
  @client = client
48
24
  @attributes = attributes
49
- @to_coerce = self.class.coerced_attributes.keys
50
25
  after_initialize
51
26
  end
52
27
 
53
28
  # @return [Hash] a Hash representation of the object
54
29
  def to_h
55
- coerce_all_attributes
56
30
  @attributes
57
31
  end
58
32
 
@@ -61,6 +35,11 @@ module Redd
61
35
  [self]
62
36
  end
63
37
 
38
+ # @return [String] an easily readable representation of the object
39
+ def inspect
40
+ "#{super}\n" + @attributes.map { |a, v| " #{a}: #{v}" }.join("\n")
41
+ end
42
+
64
43
  # Checks whether an attribute is supported by method_missing.
65
44
  # @param method_name [Symbol] the method name or attribute to check
66
45
  # @param include_private [Boolean] whether to also include private methods
@@ -83,25 +62,6 @@ module Redd
83
62
  # @abstract Lets us plug in custom code without making a mess
84
63
  def after_initialize; end
85
64
 
86
- # Coerces an attribute into a class using the {.from_id} method.
87
- # @param attribute [Symbol] the attribute to coerce
88
- def coerce_attribute(attribute)
89
- return unless @to_coerce.include?(attribute) && @attributes.include?(attribute)
90
- klass = self.class.coerced_attributes.fetch(attribute)
91
- @attributes[attribute] =
92
- if klass.nil?
93
- @client.unmarshal(@attributes[attribute])
94
- else
95
- klass.from_id(@client, @attributes[attribute])
96
- end
97
- @to_coerce.delete(attribute)
98
- end
99
-
100
- # Coerce every attribute that can be coerced.
101
- def coerce_all_attributes
102
- @to_coerce.each { |a| coerce_attribute(a) }
103
- end
104
-
105
65
  # Remove a trailing '?' from a symbol name.
106
66
  # @param method_name [Symbol] the symbol to "depredicate"
107
67
  # @return [Symbol] the symbol but with the '?' removed
@@ -113,9 +73,6 @@ module Redd
113
73
  # @param name [Symbol] the attribute to check and get
114
74
  # @return [Object] the value of the attribute
115
75
  def get_attribute(name)
116
- # Coerce the attribute if it exists and needs to be coerced.
117
- coerce_attribute(name) if @to_coerce.include?(name) && @attributes.key?(name)
118
- # Fetch the attribute, raising a KeyError if it doesn't exist.
119
76
  @attributes.fetch(name)
120
77
  end
121
78
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'lazy_model'
4
+ require_relative 'gildable'
4
5
  require_relative 'inboxable'
5
6
  require_relative 'moderatable'
6
7
  require_relative 'postable'
@@ -14,49 +15,44 @@ module Redd
14
15
  module Models
15
16
  # A comment.
16
17
  class Comment < LazyModel
18
+ include Gildable
17
19
  include Inboxable
18
20
  include Moderatable
19
21
  include Postable
20
22
  include Replyable
21
23
 
22
- coerce_attribute :author, User
23
- coerce_attribute :subreddit, Subreddit
24
-
25
- # Make a Comment from its id.
26
- # @option hash [String] :name the comment's fullname (e.g. t1_abc123)
27
- # @option hash [String] :id the comment's id (e.g. abc123)
24
+ # Create a Comment from its fullname.
25
+ # @param client [APIClient] the api client to initialize the object with
26
+ # @param id [String] the fullname
28
27
  # @return [Comment]
29
- def self.from_response(client, hash)
30
- # FIXME: listings can be empty... (for some reason)
31
-
32
- # Ensure we have the comment's id.
33
- id = hash.fetch(:id) { hash.fetch(:name).sub('t1_', '') }
34
-
35
- # If we have the link_id, we can load the listing with replies.
36
- if hash.key?(:link_id)
37
- link_id = hash[:link_id].sub('t3_', '')
38
- return new(client, hash) do |c|
39
- # The second half contains a single-item listing containing the comment
40
- c.get("/comments/#{link_id}/_/#{id}").body[1][:data][:children][0][:data]
41
- end
42
- end
43
-
44
- # We can only load the comment in isolation if we don't have the link_id.
45
- new(client, hash) do |c|
46
- # Returns a single-item listing containing the comment
47
- c.get('/api/info', id: "t1_#{id}").body[:data][:children][0][:data]
48
- end
28
+ def self.from_id(client, id)
29
+ new(client, name: id)
49
30
  end
50
31
 
51
32
  private
52
33
 
53
34
  def after_initialize
54
35
  @attributes[:replies] =
55
- if !@attributes.key?(:replies) || @attributes[:replies] == ''
56
- Listing.new(@client, children: [])
36
+ if @attributes[:replies] == ''
37
+ nil
57
38
  else
58
39
  @client.unmarshal(@attributes[:replies])
59
40
  end
41
+ @attributes[:author] = User.from_id(@client, @attributes.fetch(:author))
42
+ @attributes[:subreddit] = Subreddit.from_id(@client, @attributes.fetch(:subreddit))
43
+ end
44
+
45
+ def default_loader
46
+ # Ensure we have the comment's id.
47
+ id = @attributes.fetch(:id) { @attributes.fetch(:name).sub('t1_', '') }
48
+
49
+ # If we have the link_id, we can load the listing with replies.
50
+ if @attributes.key?(:link_id)
51
+ link_id = @attributes[:link_id].sub('t3_', '')
52
+ return @client.get("/comments/#{link_id}/_/#{id}").body[1][:data][:children][0][:data]
53
+ end
54
+ # We can only load the comment in isolation if we don't have the link_id.
55
+ @client.get('/api/info', id: "t1_#{id}").body[:data][:children][0][:data]
60
56
  end
61
57
  end
62
58
  end
@@ -17,7 +17,7 @@ module Redd
17
17
  # @param title [String] the page's title
18
18
  # @return [WikiPage]
19
19
  def wiki_page(title)
20
- WikiPage.from_response(@client, title: title)
20
+ WikiPage.new(@client, title: title)
21
21
  end
22
22
 
23
23
  # Get the appropriate listing.
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Redd
4
+ module Models
5
+ # A model that can be gilded.
6
+ module Gildable
7
+ # Gift a user one month of reddit gold for their link or comment.
8
+ def gild
9
+ @client.post("/api/v1/gold/gild/#{get_attribute(:name)}")
10
+ end
11
+ end
12
+ end
13
+ end
@@ -6,17 +6,17 @@ module Redd
6
6
  module Inboxable
7
7
  # Block the user that sent this item.
8
8
  def block
9
- @client.post('/api/block', id: get_attribute(:fullname))
9
+ @client.post('/api/block', id: get_attribute(:name))
10
10
  end
11
11
 
12
12
  # Mark this thing as read.
13
13
  def mark_as_read
14
- @client.post('/api/read_message', id: get_attribute(:fullname))
14
+ @client.post('/api/read_message', id: get_attribute(:name))
15
15
  end
16
16
 
17
17
  # Mark one or more messages as unread.
18
18
  def mark_as_unread
19
- @client.post('/api/unread_message', id: get_attribute(:fullname))
19
+ @client.post('/api/unread_message', id: get_attribute(:name))
20
20
  end
21
21
  end
22
22
  end
@@ -18,16 +18,10 @@ module Redd
18
18
  @definitely_fully_loaded = false
19
19
  end
20
20
 
21
- # @return [Boolean] whether the model can be to be lazily initialized
22
- def lazy?
23
- !@lazy_loader.nil?
24
- end
25
-
26
21
  # Force the object to make a request to reddit.
27
22
  # @return [self]
28
23
  def force_load
29
- return unless lazy?
30
- @attributes.merge!(@lazy_loader.call(@client))
24
+ @attributes.merge!(@lazy_loader ? @lazy_loader.call(@client) : default_loader)
31
25
  @definitely_fully_loaded = true
32
26
  after_initialize
33
27
  self
@@ -60,9 +54,14 @@ module Redd
60
54
 
61
55
  private
62
56
 
57
+ # @abstract A lazy loader to use when one is not provided.
58
+ def default_loader
59
+ {}
60
+ end
61
+
63
62
  # Make sure the model is loaded at least once.
64
63
  def ensure_fully_loaded
65
- force_load if lazy? && !@definitely_fully_loaded
64
+ force_load unless @definitely_fully_loaded
66
65
  end
67
66
 
68
67
  # Gets the attribute and loads it if it may be available from the response.
@@ -9,13 +9,6 @@ module Redd
9
9
  class Listing < BasicModel
10
10
  include Enumerable
11
11
 
12
- # Make a Listing from a basic Hash.
13
- # @return [Listing]
14
- def self.from_response(client, hash)
15
- hash[:children].map! { |el| client.unmarshal(el) }
16
- new(client, hash)
17
- end
18
-
19
12
  # @return [Array<Comment, Submission, PrivateMessage>] an array representation of self
20
13
  def to_ary
21
14
  get_attribute(:children)
@@ -26,6 +19,12 @@ module Redd
26
19
  get_attribute(:children).public_send(method_name, *args, &block)
27
20
  end
28
21
  end
22
+
23
+ private
24
+
25
+ def after_initialize
26
+ @attributes.fetch(:children).map! { |el| @client.unmarshal(el) }
27
+ end
29
28
  end
30
29
  end
31
30
  end
@@ -10,19 +10,11 @@ module Redd
10
10
  # An update in a live thread.
11
11
  class LiveUpdate < BasicModel; end
12
12
 
13
- # Get a Conversation from its id.
14
- # @option hash [String] :id the base36 id (e.g. abc123)
15
- # @return [Conversation]
16
- def self.from_response(client, hash)
17
- id = hash.fetch(:id)
18
- new(client, hash) { |c| c.get("/live/#{id}/about").body[:data] }
19
- end
20
-
21
13
  # Get a LiveThread from its id.
22
14
  # @param id [String] the id
23
15
  # @return [LiveThread]
24
16
  def self.from_id(client, id)
25
- from_response(client, id: id)
17
+ new(client, id: id)
26
18
  end
27
19
 
28
20
  # Get the updates from the thread.
@@ -55,14 +47,14 @@ module Redd
55
47
  # @return [Array<User>] the contributors to this thread
56
48
  def contributors
57
49
  @client.get("/live/#{get_attribute(:id)}/contributors").body[0][:data].map do |user|
58
- User.from_response(@client, user)
50
+ User.new(@client, user)
59
51
  end
60
52
  end
61
53
 
62
54
  # @return [Array<User>] users invited to contribute to this thread
63
55
  def invited_contributors
64
56
  @client.get("/live/#{get_attribute(:id)}/contributors").body[1][:data].map do |user|
65
- User.from_response(@client, user)
57
+ User.new(@client, user)
66
58
  end
67
59
  end
68
60
 
@@ -77,6 +69,12 @@ module Redd
77
69
  def discussions(**params)
78
70
  @client.model(:get, "/live/#{get_attribute(:id)}/discussions", params)
79
71
  end
72
+
73
+ private
74
+
75
+ def default_loader
76
+ @client.get("/live/#{@attributes.fetch(:id)}/about").body[:data]
77
+ end
80
78
  end
81
79
  end
82
80
  end
@@ -11,26 +11,11 @@ module Redd
11
11
  # Represents a conversation in the new modmail.
12
12
  # TODO: add modmail-specific user type
13
13
  class Conversation < LazyModel
14
- # Get a Conversation from its id.
15
- # @option hash [String] :id the base36 id (e.g. abc123)
16
- # @return [Conversation]
17
- def self.from_response(client, hash)
18
- id = hash.fetch(:id)
19
- new(client, hash) do |c|
20
- response = c.get("/api/mod/conversations/#{id}").body
21
- response[:conversation].merge(
22
- messages: response[:messages].values.map { |m| Message.from_response(c, m) },
23
- user: response[:user],
24
- mod_actions: response[:modActions]
25
- )
26
- end
27
- end
28
-
29
14
  # Get a Conversation from its id.
30
15
  # @param id [String] the base36 id (e.g. abc123)
31
16
  # @return [Conversation]
32
17
  def self.from_id(client, id)
33
- from_response(client, id: id)
18
+ new(client, id: id)
34
19
  end
35
20
 
36
21
  # Add a reply to the ongoing conversation.
@@ -84,6 +69,15 @@ module Redd
84
69
 
85
70
  private
86
71
 
72
+ def default_loader
73
+ response = @client.get("/api/mod/conversations/#{@attributes[:id]}").body
74
+ response[:conversation].merge(
75
+ messages: response[:messages].values.map { |m| Message.new(@client, m) },
76
+ user: response[:user],
77
+ mod_actions: response[:modActions]
78
+ )
79
+ end
80
+
87
81
  # Perform an action on a conversation.
88
82
  # @param method [:post, :delete] the method to use
89
83
  # @param action [String] the name of the action
@@ -104,21 +98,22 @@ module Redd
104
98
  # @return [Array<Subreddit>] moderated subreddits that are enrolled in the new modmail
105
99
  def enrolled
106
100
  @client.get('/api/mod/conversations/subreddits').body[:subreddits].map do |_, s|
107
- Subreddit.from_response(@client, s.merge(last_updated: s.delete(:lastUpdated)))
101
+ Subreddit.new(@client, s.merge(last_updated: s.delete(:lastUpdated)))
108
102
  end
109
103
  end
110
104
 
111
105
  # Get the conversations
112
- # @param after [String] base36 modmail conversation id
113
106
  # @param subreddits [Subreddit, Array<Subreddit>] the subreddits to limit to
114
- # @param limit [Integer] an integer (default: 25)
115
- # @param sort [:recent, :mod, :user, :unread] the sort order
116
- # @param state [:new, :inprogress, :mod, :notifications, :archived, :highlighted, :all] the
117
- # state to limit the conversations by
107
+ # @param params [Hash] additional request parameters
108
+ # @option params [String] :after base36 modmail conversation id
109
+ # @option params [Integer] :limit an integer (default: 25)
110
+ # @option params [:recent, :mod, :user, :unread] :sort the sort order
111
+ # @option params [:new, :inprogress, :mod, :notifications, :archived, :highlighted, :all]
112
+ # :state the state to limit the conversations by
118
113
  def conversations(subreddits: nil, **params)
119
114
  params[:entity] = Array(subreddits).map(&:display_name).join(',') if subreddits
120
115
  @client.get('/api/mod/conversations', **params).body[:conversations].map do |_, conv|
121
- Conversation.from_response(@client, conv)
116
+ Conversation.new(@client, conv)
122
117
  end
123
118
  end
124
119
 
@@ -129,7 +124,7 @@ module Redd
129
124
  # @param body [String] the message body
130
125
  # @return [Conversation] the created conversation
131
126
  def create(from:, to:, subject:, body:, hidden: false)
132
- Conversation.from_response(@client, @client.post(
127
+ Conversation.new(@client, @client.post(
133
128
  '/api/mod/conversations',
134
129
  srName: from.display_name, to: to.name,
135
130
  subject: subject, body: body, isAuthorHidden: hidden
@@ -24,7 +24,7 @@ module Redd
24
24
  # @param sort [String] the sort order of the returned comments
25
25
  # @param lookup [Hash] a hash of comments to add future replies to
26
26
  # @param depth [Number] the maximum recursion depth
27
- # @return [Array<Comment, MoreComments>] the expanded comments or {self} if past depth
27
+ # @return [Array<Comment, MoreComments>] the expanded comments or self if past depth
28
28
  def recursive_expand(link:, sort: 'best', lookup: {}, depth: 10)
29
29
  return [self] if depth == 0
30
30
 
@@ -6,20 +6,12 @@ module Redd
6
6
  module Models
7
7
  # A multi.
8
8
  class Multireddit < LazyModel
9
- # Make a Multireddit from its path.
10
- # @option hash [String] :path the multi's path
11
- # @return [Multireddit]
12
- def self.from_response(client, hash)
13
- path = hash.fetch(:path)
14
- new(client, hash) { |c| c.get("/api/multi#{path}").body[:data] }
15
- end
16
-
17
9
  # Create a Multireddit from its path.
18
10
  # @param client [APIClient] the api client to initialize the object with
19
- # @param id [String] the multi's path (prepended by a /)
11
+ # @param id [String] the multi's path (with a leading and trailing slash)
20
12
  # @return [Multireddit]
21
13
  def self.from_id(client, id)
22
- from_response(client, path: id)
14
+ new(client, path: id)
23
15
  end
24
16
 
25
17
  # Get the appropriate listing.
@@ -56,9 +48,13 @@ module Redd
56
48
  private
57
49
 
58
50
  def after_initialize
59
- # @attributes[:subreddits].map! do |subreddit|
60
- # Subreddit.from_response(client, display_name: subreddit[:name])
61
- # end
51
+ @attributes[:subreddits].map! do |subreddit|
52
+ Subreddit.new(client, display_name: subreddit[:name])
53
+ end
54
+ end
55
+
56
+ def default_loader
57
+ @client.get("/api/multi#{@attributes.fetch(:path)}").body[:data]
62
58
  end
63
59
  end
64
60
  end