forem-ruby 0.1.0.beta1

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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/forem-ruby.gemspec +17 -0
  4. data/lib/forem/api_operations/create.rb +47 -0
  5. data/lib/forem/api_operations/delete.rb +88 -0
  6. data/lib/forem/api_operations/list.rb +70 -0
  7. data/lib/forem/api_operations/request.rb +83 -0
  8. data/lib/forem/api_operations/retrieve.rb +43 -0
  9. data/lib/forem/api_operations/save.rb +53 -0
  10. data/lib/forem/api_operations/update.rb +47 -0
  11. data/lib/forem/api_requestor.rb +283 -0
  12. data/lib/forem/api_resource.rb +77 -0
  13. data/lib/forem/client.rb +279 -0
  14. data/lib/forem/configuration.rb +74 -0
  15. data/lib/forem/connection_manager.rb +75 -0
  16. data/lib/forem/errors.rb +118 -0
  17. data/lib/forem/forem_object.rb +264 -0
  18. data/lib/forem/forem_response.rb +50 -0
  19. data/lib/forem/list_object.rb +171 -0
  20. data/lib/forem/resources/admin_concept.rb +169 -0
  21. data/lib/forem/resources/admin_user.rb +152 -0
  22. data/lib/forem/resources/agent_session.rb +110 -0
  23. data/lib/forem/resources/analytics.rb +151 -0
  24. data/lib/forem/resources/article.rb +256 -0
  25. data/lib/forem/resources/billboard.rb +76 -0
  26. data/lib/forem/resources/comment.rb +43 -0
  27. data/lib/forem/resources/concept.rb +192 -0
  28. data/lib/forem/resources/follow.rb +78 -0
  29. data/lib/forem/resources/follower.rb +56 -0
  30. data/lib/forem/resources/health_check.rb +70 -0
  31. data/lib/forem/resources/organization.rb +79 -0
  32. data/lib/forem/resources/page.rb +52 -0
  33. data/lib/forem/resources/podcast_episode.rb +36 -0
  34. data/lib/forem/resources/profile_image.rb +44 -0
  35. data/lib/forem/resources/reaction.rb +61 -0
  36. data/lib/forem/resources/reading_list.rb +29 -0
  37. data/lib/forem/resources/recommended_articles_list.rb +45 -0
  38. data/lib/forem/resources/request_redirect.rb +60 -0
  39. data/lib/forem/resources/segment.rb +103 -0
  40. data/lib/forem/resources/survey.rb +96 -0
  41. data/lib/forem/resources/tag.rb +27 -0
  42. data/lib/forem/resources/trend.rb +80 -0
  43. data/lib/forem/resources/user.rb +229 -0
  44. data/lib/forem/resources/video.rb +28 -0
  45. data/lib/forem/services/admin_concept_service.rb +138 -0
  46. data/lib/forem/services/admin_user_service.rb +114 -0
  47. data/lib/forem/services/agent_session_service.rb +87 -0
  48. data/lib/forem/services/analytics_service.rb +93 -0
  49. data/lib/forem/services/article_service.rb +233 -0
  50. data/lib/forem/services/base_service.rb +45 -0
  51. data/lib/forem/services/billboard_service.rb +91 -0
  52. data/lib/forem/services/comment_service.rb +51 -0
  53. data/lib/forem/services/concept_service.rb +143 -0
  54. data/lib/forem/services/follow_service.rb +61 -0
  55. data/lib/forem/services/follower_service.rb +34 -0
  56. data/lib/forem/services/health_check_service.rb +46 -0
  57. data/lib/forem/services/organization_service.rb +103 -0
  58. data/lib/forem/services/page_service.rb +107 -0
  59. data/lib/forem/services/podcast_episode_service.rb +34 -0
  60. data/lib/forem/services/profile_image_service.rb +32 -0
  61. data/lib/forem/services/reaction_service.rb +72 -0
  62. data/lib/forem/services/reading_list_service.rb +35 -0
  63. data/lib/forem/services/recommended_articles_list_service.rb +87 -0
  64. data/lib/forem/services/request_redirect_service.rb +118 -0
  65. data/lib/forem/services/segment_service.rb +83 -0
  66. data/lib/forem/services/survey_service.rb +48 -0
  67. data/lib/forem/services/tag_service.rb +32 -0
  68. data/lib/forem/services/trend_service.rb +70 -0
  69. data/lib/forem/services/user_service.rb +61 -0
  70. data/lib/forem/services/video_service.rb +33 -0
  71. data/lib/forem/util.rb +43 -0
  72. data/lib/forem/version.rb +4 -0
  73. data/lib/forem.rb +91 -0
  74. metadata +111 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fe5320646c371a3a708ad3f52c847bb9580341799d60e16624bb731c47a70dac
4
+ data.tar.gz: 28292cfa205edaaa90346520b1b4ae448dea0ee698c67dff48c45cd5cdd8fb4c
5
+ SHA512:
6
+ metadata.gz: 966257cf5cf0b1034a98e227ebdd01801c2fd66667d426c7d07a53bd5d9b0130f3fb82a43b5159126c09afcbbdd83202439504221e499bc7ef62e572457d1be1
7
+ data.tar.gz: '057930af643ab8c4c90a30a44c9e80066626256eadec4917f2e36c565444d00ea0f0dc4456f223b3bbc30cdad9f2288f480e8dd0f3c326da012668ecb51fbc47'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Forem
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,17 @@
1
+ require_relative "lib/forem/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "forem-ruby"
5
+ s.version = Forem::VERSION
6
+ s.required_ruby_version = ">= 3.1.0"
7
+ s.summary = "Ruby client for the Forem API"
8
+ s.description = "A Ruby client library for the Forem API, modeled after stripe-ruby patterns."
9
+ s.authors = ["Forem"]
10
+ s.homepage = "https://github.com/forem/forem-ruby"
11
+ s.license = "MIT"
12
+ s.files = Dir["lib/**/*.rb"] + ["forem-ruby.gemspec", "LICENSE"]
13
+ s.require_paths = ["lib"]
14
+ s.metadata = {
15
+ "rubygems_mfa_required" => "true",
16
+ }
17
+ end
@@ -0,0 +1,47 @@
1
+ module Forem
2
+ module APIOperations
3
+ # Adds a +create+ class method to any resource that extends this module.
4
+ #
5
+ # Sends a POST request to the resource's collection path with the supplied
6
+ # parameters as a JSON body, and returns a new resource instance populated
7
+ # from the API response.
8
+ #
9
+ # @example Extending a resource class
10
+ # class Forem::Article < Forem::APIResource
11
+ # extend APIOperations::Create
12
+ # end
13
+ module Create
14
+ # Create a new resource via the Forem API.
15
+ #
16
+ # Sends a +POST+ request to {APIResource.resource_path} with +params+
17
+ # serialised as a JSON body. The server response is used to construct
18
+ # and return a new resource instance.
19
+ #
20
+ # @param params [Hash] the attributes for the new resource (e.g.
21
+ # <tt>{ article: { title: "Hello", body_markdown: "..." } }</tt>).
22
+ # @param opts [Hash] per-request options.
23
+ # @option opts [String] :api_key override the API key for this request.
24
+ # @option opts [APIRequestor] :requestor a custom requestor to use.
25
+ # @return [ForemObject] the newly-created resource, constructed from the
26
+ # API response body.
27
+ # @raise [InvalidRequestError] on HTTP 422 (validation errors).
28
+ # @raise [AuthenticationError] on HTTP 401.
29
+ # @raise [AuthorizationError] on HTTP 403.
30
+ # @raise [ForemError] on other API or network errors.
31
+ #
32
+ # @example Creating an article
33
+ # article = client.articles.create(
34
+ # article: { title: "Hello, world!", body_markdown: "**hi**", published: true }
35
+ # )
36
+ # article.id #=> 12345
37
+ # article.title #=> "Hello, world!"
38
+ #
39
+ # @see https://developers.forem.com/api/v1#tag/articles/operation/createArticle
40
+ def create(params = {}, opts = {})
41
+ requestor = opts[:requestor]
42
+ resp = request(:post, resource_path, params, opts)
43
+ construct_from(resp.parsed_body, requestor: requestor)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,88 @@
1
+ module Forem
2
+ module APIOperations
3
+ # Adds +delete+ as both a class method and an instance method to any
4
+ # resource that includes this module.
5
+ #
6
+ # The class method deletes by ID; the instance method deletes the receiver
7
+ # using its own {APIResource#resource_url}. Both methods return a resource
8
+ # object if the server responds with a body, otherwise they return +nil+
9
+ # (class method) or +self+ (instance method).
10
+ #
11
+ # The +self.included+ hook extends the including class with {ClassMethods}
12
+ # so the class-level +delete+ is available alongside the instance method.
13
+ #
14
+ # @example Extending a resource class
15
+ # class Forem::Article < Forem::APIResource
16
+ # include APIOperations::Delete
17
+ # end
18
+ module Delete
19
+ # Hook called when {Delete} is included in a class.
20
+ #
21
+ # Extends the including class with {ClassMethods}.
22
+ #
23
+ # @param base [Class] the class including this module.
24
+ # @return [void]
25
+ def self.included(base)
26
+ base.extend(ClassMethods)
27
+ end
28
+
29
+ # Class-level delete helpers.
30
+ module ClassMethods
31
+ # Delete a resource by its ID via the Forem API.
32
+ #
33
+ # Sends a +DELETE+ request to <tt>{resource_path}/{id}</tt>. If the
34
+ # server responds with a non-empty body it is used to construct a
35
+ # resource instance (representing the deleted object); otherwise
36
+ # +nil+ is returned.
37
+ #
38
+ # @param id [Integer, String] the unique identifier of the resource to
39
+ # delete.
40
+ # @param opts [Hash] per-request options.
41
+ # @option opts [String] :api_key override the API key for this request.
42
+ # @option opts [APIRequestor] :requestor a custom requestor to use.
43
+ # @return [ForemObject, nil] the deleted resource (if the API returns
44
+ # a body), or +nil+.
45
+ # @raise [NotFoundError] on HTTP 404.
46
+ # @raise [AuthenticationError] on HTTP 401.
47
+ # @raise [AuthorizationError] on HTTP 403.
48
+ # @raise [ForemError] on other API or network errors.
49
+ #
50
+ # @example Deleting an article by ID
51
+ # client.articles.delete(12345)
52
+ #
53
+ # @see https://developers.forem.com/api/v1
54
+ def delete(id, opts = {})
55
+ requestor = opts[:requestor]
56
+ resp = request(:delete, "#{resource_path}/#{id}", {}, opts)
57
+ resp.parsed_body ? construct_from(resp.parsed_body, requestor: requestor) : nil
58
+ end
59
+ end
60
+
61
+ # Delete this resource instance via the Forem API.
62
+ #
63
+ # Sends a +DELETE+ request to this instance's {APIResource#resource_url}.
64
+ # If the server responds with a non-empty body it is used to construct
65
+ # a new resource instance (representing the deleted state); otherwise
66
+ # +self+ is returned.
67
+ #
68
+ # @param opts [Hash] per-request options.
69
+ # @option opts [String] :api_key override the API key for this request.
70
+ # @option opts [APIRequestor] :requestor a custom requestor to use.
71
+ # @return [ForemObject] a new resource instance built from the response
72
+ # body, or +self+ if the response has no body.
73
+ # @raise [InvalidRequestError] if the instance has no +id+.
74
+ # @raise [NotFoundError] on HTTP 404.
75
+ # @raise [AuthenticationError] on HTTP 401.
76
+ # @raise [AuthorizationError] on HTTP 403.
77
+ # @raise [ForemError] on other API or network errors.
78
+ #
79
+ # @example Deleting the current instance
80
+ # article.delete
81
+ def delete(opts = {})
82
+ requestor = opts[:requestor] || @requestor
83
+ resp = request(:delete, resource_url, {}, opts)
84
+ resp.parsed_body ? self.class.construct_from(resp.parsed_body, requestor: requestor) : self
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,70 @@
1
+ module Forem
2
+ module APIOperations
3
+ # Adds a +list+ class method to any resource that extends this module.
4
+ #
5
+ # Fetches a paginated collection from the resource's path and wraps the
6
+ # result in a {ListObject} that supports manual and automatic pagination.
7
+ #
8
+ # @example Extending a resource class
9
+ # class Forem::Article < Forem::APIResource
10
+ # extend APIOperations::List
11
+ # end
12
+ module List
13
+ # Retrieve a paginated list of resources from the Forem API.
14
+ #
15
+ # Sends a +GET+ request to {APIResource.resource_path} with +params+
16
+ # appended as query-string parameters. The response array is converted
17
+ # into an array of resource instances and wrapped in a {ListObject} that
18
+ # exposes pagination helpers.
19
+ #
20
+ # Pagination defaults: +page+ 1, +per_page+ 30 (matching Forem API
21
+ # defaults). These can be overridden via +params+.
22
+ #
23
+ # @param params [Hash] query parameters for filtering and pagination.
24
+ # @option params [Integer] :page the page number to fetch (default +1+).
25
+ # @option params [Integer] :per_page the number of items per page
26
+ # (default +30+, maximum varies by endpoint).
27
+ # @option params [String] :tag filter articles by tag (articles endpoint).
28
+ # @option params [String] :username filter by username.
29
+ # @option params [String] :state filter by state (e.g. +"fresh"+,
30
+ # +"rising"+, +"all"+).
31
+ # @param opts [Hash] per-request options.
32
+ # @option opts [String] :api_key override the API key for this request.
33
+ # @option opts [APIRequestor] :requestor a custom requestor to use and
34
+ # to forward to subsequent page fetches.
35
+ # @return [ListObject] a paginated list object wrapping the current
36
+ # page's resources.
37
+ # @raise [AuthenticationError] on HTTP 401.
38
+ # @raise [AuthorizationError] on HTTP 403.
39
+ # @raise [ForemError] on other API or network errors.
40
+ #
41
+ # @example Fetching the first page of articles
42
+ # articles = client.articles.list(per_page: 10)
43
+ # articles.map(&:title)
44
+ # #=> ["Article 1", "Article 2", ...]
45
+ #
46
+ # @example Filtering articles by tag
47
+ # client.articles.list(tag: "ruby", per_page: 5).each do |a|
48
+ # puts a.title
49
+ # end
50
+ #
51
+ # @see https://developers.forem.com/api/v1#tag/articles/operation/getArticles
52
+ def list(params = {}, opts = {})
53
+ requestor = opts[:requestor]
54
+ resp = request(:get, resource_path, params, opts)
55
+ data = (resp.parsed_body || []).map { |item| construct_from(item, requestor: requestor) }
56
+ per_page = params[:per_page] || params["per_page"] || 30
57
+ page = params[:page] || params["page"] || 1
58
+
59
+ ListObject.new(
60
+ data: data,
61
+ current_page: page.to_i,
62
+ per_page: per_page.to_i,
63
+ resource_class: self,
64
+ filters: params.reject { |k, _| [:page, :per_page, "page", "per_page"].include?(k) },
65
+ requestor: requestor
66
+ )
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,83 @@
1
+ module Forem
2
+ module APIOperations
3
+ # Provides +request+ as both a class method and an instance method on any
4
+ # class that includes this module.
5
+ #
6
+ # Including {Request} is the only prerequisite for a class to issue
7
+ # authenticated HTTP requests. It is included in {APIResource} and
8
+ # therefore inherited by every concrete resource class.
9
+ #
10
+ # The module uses a +self.included+ hook to also extend the including
11
+ # class with {ClassMethods}, making the class-level +request+ available
12
+ # for module methods such as {APIOperations::Create#create} and
13
+ # {APIOperations::List#list}.
14
+ #
15
+ # The +:requestor+ key in +opts+ is consumed here and never forwarded to
16
+ # {APIRequestor#request}.
17
+ module Request
18
+ # Hook called when {Request} is included in a class.
19
+ #
20
+ # @param base [Class] the class including this module.
21
+ # @return [void]
22
+ def self.included(base)
23
+ base.extend(ClassMethods)
24
+ end
25
+
26
+ # Class-level request helpers mixed into every class that includes
27
+ # {Request}.
28
+ module ClassMethods
29
+ # Issue an authenticated HTTP request using the supplied requestor.
30
+ #
31
+ # The +:requestor+ key in +opts+ is required — there is no global
32
+ # default. In normal use this is supplied automatically by a
33
+ # {Forem::Client} via its service objects.
34
+ #
35
+ # @param method [Symbol] the HTTP verb (+:get+, +:post+, +:put+,
36
+ # +:delete+).
37
+ # @param path [String] the API path (e.g. +"/api/articles"+).
38
+ # @param params [Hash] query parameters or JSON body payload.
39
+ # @param opts [Hash] per-request options.
40
+ # @option opts [APIRequestor] :requestor (required) the requestor
41
+ # that issues this call. Normally injected by a {Forem::Client}.
42
+ # @option opts [String] :api_key override the API key for this request.
43
+ # @option opts [String] :api_base override the base URL for this request.
44
+ # @return [ForemResponse] the response wrapper.
45
+ # @raise [ArgumentError] if no +:requestor+ is supplied.
46
+ # @raise [ForemError] on any API or network error.
47
+ def request(method, path, params = {}, opts = {})
48
+ requestor = opts.delete(:requestor)
49
+ unless requestor
50
+ raise ArgumentError,
51
+ "Forem requires an explicit requestor — call this method " \
52
+ "through a Forem::Client (e.g. client.articles.list)."
53
+ end
54
+ requestor.request(method, path, params, opts)
55
+ end
56
+ end
57
+
58
+ # Issue an authenticated HTTP request on behalf of this resource
59
+ # instance.
60
+ #
61
+ # Falls back to the requestor stored on the instance (set when the
62
+ # object was constructed by a service call) if no explicit
63
+ # +:requestor+ is supplied in +opts+. Delegates to the class-level
64
+ # {ClassMethods#request} for the actual transport.
65
+ #
66
+ # @param method [Symbol] the HTTP verb.
67
+ # @param path [String] the API path.
68
+ # @param params [Hash] query parameters or JSON body payload.
69
+ # @param opts [Hash] per-request options.
70
+ # @option opts [APIRequestor] :requestor override the instance's
71
+ # stored requestor for this call.
72
+ # @return [ForemResponse] the response wrapper.
73
+ # @raise [ArgumentError] if neither the instance nor +opts+ supply a
74
+ # requestor.
75
+ # @raise [ForemError] on any API or network error.
76
+ def request(method, path, params = {}, opts = {})
77
+ opts = opts.dup
78
+ opts[:requestor] ||= @requestor if instance_variable_defined?(:@requestor)
79
+ self.class.request(method, path, params, opts)
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,43 @@
1
+ module Forem
2
+ module APIOperations
3
+ # Adds a +retrieve+ class method to any resource that extends this module.
4
+ #
5
+ # Fetches a single resource by its ID with a GET request and returns a
6
+ # resource instance populated from the API response.
7
+ #
8
+ # @example Extending a resource class
9
+ # class Forem::Article < Forem::APIResource
10
+ # extend APIOperations::Retrieve
11
+ # end
12
+ module Retrieve
13
+ # Retrieve a single resource by its ID from the Forem API.
14
+ #
15
+ # Sends a +GET+ request to <tt>{resource_path}/{id}</tt> and constructs
16
+ # a resource instance from the response body.
17
+ #
18
+ # @param id [Integer, String] the unique identifier of the resource to
19
+ # fetch.
20
+ # @param opts [Hash] per-request options.
21
+ # @option opts [String] :api_key override the API key for this request.
22
+ # @option opts [APIRequestor] :requestor a custom requestor to use.
23
+ # @return [ForemObject] the resource instance populated with data from
24
+ # the API response.
25
+ # @raise [NotFoundError] on HTTP 404 (resource does not exist).
26
+ # @raise [AuthenticationError] on HTTP 401.
27
+ # @raise [AuthorizationError] on HTTP 403.
28
+ # @raise [ForemError] on other API or network errors.
29
+ #
30
+ # @example Fetching an article by ID
31
+ # article = client.articles.retrieve(12345)
32
+ # article.title #=> "Hello, world!"
33
+ # article.id #=> 12345
34
+ #
35
+ # @see https://developers.forem.com/api/v1#tag/articles/operation/getArticleById
36
+ def retrieve(id, opts = {})
37
+ requestor = opts[:requestor]
38
+ resp = request(:get, "#{resource_path}/#{id}", {}, opts)
39
+ construct_from(resp.parsed_body, requestor: requestor)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,53 @@
1
+ module Forem
2
+ module APIOperations
3
+ # Adds a +save+ instance method to any resource that includes this module.
4
+ #
5
+ # +save+ is the instance-level complement to the class-level
6
+ # {APIOperations::Update#update}. It persists the current resource to the
7
+ # API by issuing a PUT request to the instance's own
8
+ # {APIResource#resource_url}, and returns a new resource object reflecting
9
+ # the server's response.
10
+ #
11
+ # @example Including in a resource class
12
+ # class Forem::Article < Forem::APIResource
13
+ # include APIOperations::Save
14
+ # end
15
+ module Save
16
+ # Save (update) this resource instance via the Forem API.
17
+ #
18
+ # Sends a +PUT+ request to {APIResource#resource_url} with +params+
19
+ # serialised as a JSON body. The server response is used to construct
20
+ # and return a new resource instance representing the updated state.
21
+ #
22
+ # Unlike {APIResource#refresh}, which reloads in-place, +save+ returns
23
+ # a *new* instance and does not mutate the receiver.
24
+ #
25
+ # @param params [Hash] the attributes to update (e.g.
26
+ # <tt>{ article: { title: "New title" } }</tt>). Defaults to +{}+.
27
+ # @param opts [Hash] per-request keyword options.
28
+ # @option opts [String] :api_key override the API key for this request.
29
+ # @option opts [APIRequestor] :requestor a custom requestor to use.
30
+ # @return [ForemObject] a new resource instance populated with the
31
+ # updated data returned by the API.
32
+ # @raise [InvalidRequestError] if this instance has no +id+ (via
33
+ # {APIResource#resource_url}).
34
+ # @raise [NotFoundError] on HTTP 404.
35
+ # @raise [InvalidRequestError] on HTTP 422 (validation errors).
36
+ # @raise [AuthenticationError] on HTTP 401.
37
+ # @raise [AuthorizationError] on HTTP 403.
38
+ # @raise [ForemError] on other API or network errors.
39
+ #
40
+ # @example Updating a fetched article
41
+ # article = client.articles.retrieve(12345)
42
+ # updated = article.save(article: { title: "Brand new title" })
43
+ # updated.title #=> "Brand new title"
44
+ #
45
+ # @see https://developers.forem.com/api/v1#tag/articles/operation/updateArticle
46
+ def save(params = {}, **opts)
47
+ requestor = opts[:requestor] || @requestor
48
+ resp = request(:put, resource_url, params, opts)
49
+ self.class.construct_from(resp.parsed_body, requestor: requestor)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,47 @@
1
+ module Forem
2
+ module APIOperations
3
+ # Adds an +update+ class method to any resource that extends this module.
4
+ #
5
+ # Sends a PUT request to a specific resource URL with the supplied
6
+ # parameters as a JSON body, and returns a new resource instance
7
+ # constructed from the API response.
8
+ #
9
+ # @example Extending a resource class
10
+ # class Forem::Article < Forem::APIResource
11
+ # extend APIOperations::Update
12
+ # end
13
+ module Update
14
+ # Update an existing resource via the Forem API.
15
+ #
16
+ # Sends a +PUT+ request to <tt>{resource_path}/{id}</tt> with +params+
17
+ # serialised as a JSON body, and returns a new resource instance
18
+ # reflecting the updated state as returned by the server.
19
+ #
20
+ # @param id [Integer, String] the unique identifier of the resource to
21
+ # update.
22
+ # @param params [Hash] the attributes to update (e.g.
23
+ # <tt>{ article: { title: "New title" } }</tt>).
24
+ # @param opts [Hash] per-request options.
25
+ # @option opts [String] :api_key override the API key for this request.
26
+ # @option opts [APIRequestor] :requestor a custom requestor to use.
27
+ # @return [ForemObject] a resource instance populated with the updated
28
+ # data returned by the API.
29
+ # @raise [NotFoundError] on HTTP 404 (resource does not exist).
30
+ # @raise [InvalidRequestError] on HTTP 422 (validation errors).
31
+ # @raise [AuthenticationError] on HTTP 401.
32
+ # @raise [AuthorizationError] on HTTP 403.
33
+ # @raise [ForemError] on other API or network errors.
34
+ #
35
+ # @example Updating an article's title
36
+ # article = client.articles.update(12345, article: { title: "Updated title" })
37
+ # article.title #=> "Updated title"
38
+ #
39
+ # @see https://developers.forem.com/api/v1#tag/articles/operation/updateArticle
40
+ def update(id, params = {}, opts = {})
41
+ requestor = opts[:requestor]
42
+ resp = request(:put, "#{resource_path}/#{id}", params, opts)
43
+ construct_from(resp.parsed_body, requestor: requestor)
44
+ end
45
+ end
46
+ end
47
+ end