Class: Forem::ForemObject
- Inherits:
-
Object
- Object
- Forem::ForemObject
- Includes:
- APIOperations::Request
- Defined in:
- lib/forem/forem_object.rb
Overview
Base object class for all Forem API objects. Provides dynamic attribute access from API response data.
Attributes are stored internally as a plain Hash with string keys.
They can be read and written using either method-call syntax
(obj.title) or hash-subscript syntax (obj).
Nested hashes are recursively converted to ForemObject instances via
Util.convert_to_forem_object.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#requestor ⇒ APIRequestor?
The requestor that produced this object, used by instance methods (e.g. APIOperations::Save#save) when no explicit
:requestoroption is supplied.
Class Method Summary collapse
-
.construct_from(values, requestor: nil) ⇒ ForemObject
Construct a new ForemObject from an API response hash.
-
.cursor_list(path, params = {}, opts = {}, cursor_param: :after, cursor_from: ->(item) { item["id"] }) ⇒ ListObject
Wrap a cursor-based ("after") GET-array endpoint as a ListObject.
-
.paginated_list(path, params = {}, opts = {}) ⇒ ListObject
Wrap a custom-path GET-array endpoint as a page-based ListObject.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Compare two ForemObject instances by their internal value hash.
-
#[](key) ⇒ Object?
Read an attribute by key.
-
#[]=(key, value) ⇒ Object
Write an attribute by key.
-
#initialize(values = {}) ⇒ ForemObject
constructor
Initialise a new object, optionally pre-populating it with values.
-
#inspect ⇒ String
Return a human-readable string representation of the object.
-
#method_missing(method, *args) ⇒ Object
Dynamic getter/setter for API attributes.
-
#respond_to_missing?(method, include_private = false) ⇒ Boolean
Allow
respond_to?checks for dynamic attribute accessors. -
#to_hash ⇒ Hash{String => Object}
Recursively convert this object to a plain Ruby Hash.
Methods included from APIOperations::Request
Constructor Details
#initialize(values = {}) ⇒ ForemObject
Initialise a new object, optionally pre-populating it with values.
115 116 117 118 119 |
# File 'lib/forem/forem_object.rb', line 115 def initialize(values = {}) @values = {} @requestor = nil update_attributes(values) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args) ⇒ Object
230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/forem/forem_object.rb', line 230 def method_missing(method, *args) name = method.to_s if name.end_with?("=") attr = name.chomp("=") self[attr] = args[0] elsif @values.key?(name) @values[name] else super end end |
Instance Attribute Details
#requestor ⇒ APIRequestor?
Returns the requestor that produced this object,
used by instance methods (e.g. APIOperations::Save#save) when no
explicit :requestor option is supplied.
26 27 28 |
# File 'lib/forem/forem_object.rb', line 26 def requestor @requestor end |
Class Method Details
.construct_from(values, requestor: nil) ⇒ ForemObject
Construct a new Forem::ForemObject from an API response hash.
This is the preferred factory method — it is called by Util and resource class methods rather than #initialize directly.
137 138 139 140 141 142 |
# File 'lib/forem/forem_object.rb', line 137 def self.construct_from(values, requestor: nil) obj = new obj.send(:update_attributes, values) obj.requestor = requestor obj end |
.cursor_list(path, params = {}, opts = {}, cursor_param: :after, cursor_from: ->(item) { item["id"] }) ⇒ ListObject
Wrap a cursor-based ("after") GET-array endpoint as a ListObject.
Used by endpoints (e.g. survey poll votes) that use an +after+-style
cursor — successive pages are fetched by passing the last seen ID as
the cursor. Cursor pagination is forward-only; ListObject#previous_page
returns nil.
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/forem/forem_object.rb', line 84 def self.cursor_list(path, params = {}, opts = {}, cursor_param: :after, cursor_from: ->(item) { item["id"] }) requestor = opts[:requestor] resp = request(:get, path, params, opts) per_page = (params[:per_page] || params["per_page"] || 30).to_i klass = self data = (resp.parsed_body || []).map { |item| klass.construct_from(item, requestor: requestor) } fetcher = lambda do |direction, state, extra| return nil unless direction == :next return nil if state.data.empty? next_cursor = cursor_from.call(state.data.last) new_params = state.filters.merge(cursor_param => next_cursor, per_page: state.per_page).merge(extra) klass.cursor_list(path, new_params, { requestor: state.requestor }, cursor_param: cursor_param, cursor_from: cursor_from) end ListObject.new( data: data, current_page: nil, per_page: per_page, resource_class: klass, filters: params.reject { |k, _| [cursor_param, cursor_param.to_s, :per_page, "per_page"].include?(k) }, requestor: requestor, fetcher: fetcher ) end |
.paginated_list(path, params = {}, opts = {}) ⇒ ListObject
Wrap a custom-path GET-array endpoint as a page-based ListObject.
Used by resource methods that hit a non-standard path (e.g.
/api/articles/me/published) but otherwise behave like
APIOperations::List#list: page-based pagination with
+page+/+per_page+ query params, returning a JSON array of resource
objects.
The returned ListObject re-uses this same helper for #next_page and #previous_page, so pagination works without any further wiring.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/forem/forem_object.rb', line 43 def self.paginated_list(path, params = {}, opts = {}) requestor = opts[:requestor] resp = request(:get, path, params, opts) per_page = (params[:per_page] || params["per_page"] || 30).to_i page = (params[:page] || params["page"] || 1).to_i klass = self data = (resp.parsed_body || []).map { |item| klass.construct_from(item, requestor: requestor) } fetcher = lambda do |direction, state, extra| target = direction == :next ? state.current_page + 1 : state.current_page - 1 return nil if target < 1 new_params = state.filters.merge(page: target, per_page: state.per_page).merge(extra) klass.paginated_list(path, new_params, { requestor: state.requestor }) end ListObject.new( data: data, current_page: page, per_page: per_page, resource_class: klass, filters: params.reject { |k, _| [:page, :per_page, "page", "per_page"].include?(k) }, requestor: requestor, fetcher: fetcher ) end |
Instance Method Details
#==(other) ⇒ Boolean
Compare two Forem::ForemObject instances by their internal value hash.
199 200 201 |
# File 'lib/forem/forem_object.rb', line 199 def ==(other) other.is_a?(ForemObject) && @values == other.instance_variable_get(:@values) end |
#[](key) ⇒ Object?
Read an attribute by key.
153 154 155 |
# File 'lib/forem/forem_object.rb', line 153 def [](key) @values[key.to_s] end |
#[]=(key, value) ⇒ Object
Write an attribute by key.
The value is passed through Util.convert_to_forem_object so nested Hashes become Forem::ForemObject instances automatically.
170 171 172 |
# File 'lib/forem/forem_object.rb', line 170 def []=(key, value) @values[key.to_s] = Util.convert_to_forem_object(value) end |
#inspect ⇒ String
Return a human-readable string representation of the object.
245 246 247 |
# File 'lib/forem/forem_object.rb', line 245 def inspect "#<#{self.class}:0x#{object_id.to_s(16)} #{@values.inspect}>" end |
#respond_to_missing?(method, include_private = false) ⇒ Boolean
Allow respond_to? checks for dynamic attribute accessors.
Returns true for any key currently stored in the internal values hash,
as well as the corresponding setter (e.g. title=).
211 212 213 214 215 |
# File 'lib/forem/forem_object.rb', line 211 def respond_to_missing?(method, include_private = false) name = method.to_s name = name.chomp("=") @values.key?(name) || super end |
#to_hash ⇒ Hash{String => Object}
Recursively convert this object to a plain Ruby Hash.
Nested Forem::ForemObject instances are converted via their own #to_hash;
Arrays whose elements are Forem::ForemObject instances are mapped similarly.
184 185 186 187 188 189 190 191 192 |
# File 'lib/forem/forem_object.rb', line 184 def to_hash @values.transform_values do |v| case v when ForemObject then v.to_hash when Array then v.map { |e| e.is_a?(ForemObject) ? e.to_hash : e } else v end end end |