redd 0.8.1 → 0.8.2

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.
@@ -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