Class: McpToolkit::Serializer::Base
- Inherits:
-
Object
- Object
- McpToolkit::Serializer::Base
- Defined in:
- lib/mcp_toolkit/serializer/base.rb
Overview
The DEFAULT serializer base shipped by the toolkit. A self-contained
implementation of the subset of an AMS-style serializer the MCP wire format
depends on, with NO dependency on active_model_serializers / fast_jsonapi.
The injection contract
The executors (ListExecutor / GetExecutor) only ever call two class
methods on a resource's serializer:
serializer.serialize_one(record, scope:)
# => Hash (a single record's shape), or nil for a nil record
serializer.serialize_collection(records, scope:, total_count:, limit:, offset:)
# => { <root_key> => [ <record_hash>, ... ],
# meta: { total_count:, limit:, offset: } }
ANY class implementing those two methods can be registered as a resource's
serializer — that is the seam that lets an app's existing serializers slot in
unchanged alongside this base. The resource_schema tool additionally reads
declared_attributes and
declared_associations off the serializer (for shape discovery); a custom
serializer that wants to power resource_schema should expose those too, but
they are not required for get / list.
Sparse fieldsets (optional)
Both entry points also accept an OPTIONAL fields: keyword — an array of the
attribute and/or relationship link-key names to include (JSON:API's sparse
fieldset; the two share one flat namespace). fields: nil (the default) means
"everything", so the contract above is unchanged for callers that omit it. When
a subset is given, only those members are emitted AND only the selected
relationships are loaded (unselected has_many links are never queried). A
serializer that does NOT declare a fields: keyword still supports sparse
fieldsets — McpToolkit::Serialization prunes its output instead — so honoring
fields: natively is a performance optimization, not a contract requirement.
scope is whatever the serializer needs (typically the account); it may be
nil for models without translations.
Output shape
A single record serializes to:
{ <attr> => <value>, ..., "links" => { "<assoc>" => <id|[ids]|{id:,type:}|nil> } }
- Declared
attributesare emitted as symbol keys, in declaration order (an instance method named after the attribute overrides the column value). -
"links"is a string key whose value is a Hash with string keys, one per declared association, sorted alphabetically.- has_one / belongs_to whose FK lives on the record => the raw id (or nil)
- polymorphic has_one / belongs_to => { id:
, type: } - has_many => a sorted Array of associated ids ([] when none)
- created_at / updated_at, when present, are rendered as iso8601(6).
A collection serializes to:
{ <plural_resource_name>: [ <record_hash>, ... ],
meta: { total_count:, limit:, offset: } }
Defined Under Namespace
Classes: Association
Constant Summary collapse
- TIMESTAMP_COLUMNS =
%i[created_at updated_at].freeze
- HIGH_PRECISION_FOR_TIMESTAMPS =
6
Instance Attribute Summary collapse
-
#object ⇒ Object
readonly
---- instance API ----------------------------------------------------.
-
#scope ⇒ Object
readonly
---- instance API ----------------------------------------------------.
Class Method Summary collapse
- .attributes(*names) ⇒ Object
- .declared_associations ⇒ Object
- .declared_attributes ⇒ Object
-
.has_many(name, key: nil, root: nil, serializer: nil) ⇒ Object
has_many / has_and_belongs_to_many - sorted array of ids.
-
.has_one(name, key: nil, root: nil, serializer: nil, polymorphic: false, foreign_key: nil) ⇒ Object
belongs_to / has_one - single id (or id:,type: when polymorphic).
-
.model_class ⇒ Object
Infer the serialized model from the serializer class name by stripping a trailing "Serializer" and the host namespace, e.g.
-
.model_class=(klass) ⇒ Object
Lets subclasses point at a model whose name doesn't follow the convention (e.g. namespacing differences).
-
.root_key ⇒ Object
Pluralized resource name used as the collection root key, derived from the serialized model (
model.model_name.plural). -
.serialize_collection(records, scope: nil, total_count: nil, limit: nil, offset: nil, fields: nil) ⇒ Object
Serialize an array of records to the index wrapper, keyed by the pluralized resource name, with a
metapagination block. -
.serialize_one(record, scope: nil, fields: nil) ⇒ Object
Serialize a single record to its attributes+links hash.
-
.translates(*names) ⇒ Object
Declares attributes whose value is a
{ locale => translation }hash.
Instance Method Summary collapse
-
#initialize(object, scope: nil) ⇒ Base
constructor
A new instance of Base.
-
#serializable_hash(fields: nil) ⇒ Object
(also: #as_json)
fields:(optional) is a sparse fieldset — an array of attribute and/or relationship link-key names to include.
Constructor Details
#initialize(object, scope: nil) ⇒ Base
Returns a new instance of Base.
172 173 174 175 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 172 def initialize(object, scope: nil) @object = object @scope = scope end |
Instance Attribute Details
#object ⇒ Object (readonly)
---- instance API ----------------------------------------------------
170 171 172 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 170 def object @object end |
#scope ⇒ Object (readonly)
---- instance API ----------------------------------------------------
170 171 172 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 170 def scope @scope end |
Class Method Details
.attributes(*names) ⇒ Object
74 75 76 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 74 def self.attributes(*names) names.each { |name| declared_attributes << name.to_sym } end |
.declared_associations ⇒ Object
113 114 115 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 113 def self.declared_associations @declared_associations ||= [] end |
.declared_attributes ⇒ Object
109 110 111 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 109 def self.declared_attributes @declared_attributes ||= [] end |
.has_many(name, key: nil, root: nil, serializer: nil) ⇒ Object
has_many / has_and_belongs_to_many - sorted array of ids.
92 93 94 95 96 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 92 def self.has_many(name, key: nil, root: nil, serializer: nil) declared_associations << Association.new( name: name.to_sym, type: :has_many, key: key || root, serializer:, polymorphic: false ) end |
.has_one(name, key: nil, root: nil, serializer: nil, polymorphic: false, foreign_key: nil) ⇒ Object
belongs_to / has_one - single id (or id:,type: when polymorphic).
foreign_key: overrides the FK method read for the id (defaults to
<name>_id). Use it when the model's FK column doesn't follow the
<name>_id convention - e.g.
has_one :account, foreign_key: :synced_account_id so the link reports
the central account id straight off the already-loaded column.
85 86 87 88 89 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 85 def self.has_one(name, key: nil, root: nil, serializer: nil, polymorphic: false, foreign_key: nil) declared_associations << Association.new( name: name.to_sym, type: :has_one, key: key || root, serializer:, polymorphic:, foreign_key: ) end |
.model_class ⇒ Object
Infer the serialized model from the serializer class name by stripping a
trailing "Serializer" and the host namespace, e.g.
Mcp::NotificationSerializer -> Notification
Mcp::PushNotifications::FilterSerializer -> PushNotifications::Filter
Subclasses whose name doesn't follow the convention set model_class.
150 151 152 153 154 155 156 157 158 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 150 def self.model_class @model_class ||= begin without_suffix = name.delete_suffix("Serializer") # Drop the leading serializer namespace segment (e.g. "Mcp::") so the # remainder names the model. If there is no namespace, use as-is. without_namespace = without_suffix.sub(/\A[^:]+::/, "") (without_namespace.empty? ? without_suffix : without_namespace).constantize end end |
.model_class=(klass) ⇒ Object
Lets subclasses point at a model whose name doesn't follow the
convention (e.g. namespacing differences). Written as an explicit class
method (not attr_writer, which would define an instance writer) to set the
class-level @model_class the convention-inference memoizes.
164 165 166 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 164 def self.model_class=(klass) # rubocop:disable Style/TrivialAccessors @model_class = klass end |
.root_key ⇒ Object
Pluralized resource name used as the collection root key, derived from
the serialized model (model.model_name.plural).
141 142 143 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 141 def self.root_key model_class.model_name.plural.to_sym end |
.serialize_collection(records, scope: nil, total_count: nil, limit: nil, offset: nil, fields: nil) ⇒ Object
Serialize an array of records to the index wrapper, keyed by the
pluralized resource name, with a meta pagination block. fields: (optional)
restricts every row to a sparse fieldset — see the class-level docs.
131 132 133 134 135 136 137 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 131 def self.serialize_collection(records, scope: nil, total_count: nil, limit: nil, offset: nil, fields: nil) rows = Array(records).map { |record| new(record, scope:).serializable_hash(fields:) } { root_key => rows, meta: { total_count: total_count.nil? ? rows.size : total_count, limit:, offset: } } end |
.serialize_one(record, scope: nil, fields: nil) ⇒ Object
Serialize a single record to its attributes+links hash. nil-safe.
fields: (optional) restricts the output to a sparse fieldset — see the
class-level docs.
122 123 124 125 126 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 122 def self.serialize_one(record, scope: nil, fields: nil) return nil if record.nil? new(record, scope:).serializable_hash(fields:) end |
.translates(*names) ⇒ Object
Declares attributes whose value is a { locale => translation } hash.
An instance method is defined for each attribute that delegates to
#translate. Only meaningful for Globalize models; harmless otherwise
(returns {}).
102 103 104 105 106 107 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 102 def self.translates(*names) names.each do |name| declared_attributes << name.to_sym unless declared_attributes.include?(name.to_sym) define_method(name) { translate(name) } end end |
Instance Method Details
#serializable_hash(fields: nil) ⇒ Object Also known as: as_json
fields: (optional) is a sparse fieldset — an array of attribute and/or
relationship link-key names to include. nil (default) emits everything.
179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/mcp_toolkit/serializer/base.rb', line 179 def serializable_hash(fields: nil) selected = fields&.map(&:to_sym) hash = {} self.class.declared_attributes.each do |attr| next if selected && !selected.include?(attr) hash[attr] = read_attribute(attr) end (hash) link_hash = links(selected) hash["links"] = link_hash unless link_hash.nil? hash end |