class Attio::Note

Represents a note attached to a record in Attio

def self.resource_path

Returns:
  • (String) - The API path
def self.resource_path
  "notes"
end

def content

Convenience method to get content based on format
def content
  case format
  when "plaintext"
    content_plaintext
  when "html", "markdown"
    content_markdown
  else
    content_plaintext
  end
end

def create(**kwargs)

Override create to handle validation and parameter mapping
def create(**kwargs)
  # Extract options from kwargs
  opts = {}
  opts[:api_key] = kwargs.delete(:api_key) if kwargs.key?(:api_key)
  # Map object/record_id to parent_object/parent_record_id
  normalized_params = {
    parent_object: kwargs[:object] || kwargs[:parent_object],
    parent_record_id: kwargs[:record_id] || kwargs[:parent_record_id],
    title: kwargs[:title] || kwargs[:content] || "Note",
    content: kwargs[:content],
    format: kwargs[:format]
  }
  prepared_params = prepare_params_for_create(normalized_params)
  response = execute_request(:POST, resource_path, prepared_params, opts)
  new(response["data"] || response, opts)
end

def destroy(**opts)

Override destroy to handle nested ID
def destroy(**opts)
  raise InvalidRequestError, "Cannot destroy a note without an ID" unless persisted?
  note_id = id.is_a?(Hash) ? (id[:note_id] || id["note_id"]) : id
  self.class.delete(note_id, **opts)
  freeze
  true
end

def for_record(params = {}, object:, record_id:, **)

Get notes for a record
def for_record(params = {}, object:, record_id:, **)
  list(
    params.merge(
      parent_object: object,
      parent_record_id: record_id
    ),
    **
  )
end

def html?

Check if note is in HTML format
def html?
  format == "html"
end

def initialize(attributes = {}, opts = {})

def initialize(attributes = {}, opts = {})
  super
  normalized_attrs = normalize_attributes(attributes)
  @parent_object = normalized_attrs[:parent_object]
  @parent_record_id = normalized_attrs[:parent_record_id]
  @title = normalized_attrs[:title]
  @content_plaintext = normalized_attrs[:content_plaintext]
  @content_markdown = normalized_attrs[:content_markdown]
  @tags = normalized_attrs[:tags] || []
  @metadata = normalized_attrs[:metadata] || {}
  @format = normalized_attrs[:format] || "plaintext"
  @created_by_actor = normalized_attrs[:created_by_actor]
end

def parent_record(**)

Get the parent record
def parent_record(**)
  return nil unless parent_object && parent_record_id
  Internal::Record.retrieve(
    object: parent_object,
    record_id: parent_record_id,
    **
  )
end

def plaintext?

Check if note is in plaintext format
def plaintext?
  format == "plaintext"
end

def prepare_params_for_create(params)

Override create to handle validation
def prepare_params_for_create(params)
  validate_parent!(params[:parent_object], params[:parent_record_id])
  validate_content!(params[:content])
  validate_format!(params[:format]) if params[:format]
  {
    data: {
      title: params[:title],
      parent_object: params[:parent_object],
      parent_record_id: params[:parent_record_id],
      content: params[:content],
      format: params[:format] || "plaintext"
    }
  }
end

def resource_path

def resource_path
  raise InvalidRequestError, "Cannot generate path without an ID" unless persisted?
  note_id = id.is_a?(Hash) ? (id[:note_id] || id["note_id"]) : id
  "#{self.class.resource_path}/#{note_id}"
end

def retrieve(id, **opts)

Override retrieve to handle nested ID
def retrieve(id, **opts)
  note_id = id.is_a?(Hash) ? (id[:note_id] || id["note_id"]) : id
  validate_id!(note_id)
  response = execute_request(:GET, "#{resource_path}/#{note_id}", {}, opts)
  new(response["data"] || response, opts)
end

def save(*)

Notes cannot be updated
def save(*)
  raise NotImplementedError, "Notes cannot be updated. Create a new note instead."
end

def strip_html(html)

def strip_html(html)
  return html unless html.is_a?(String)
  # Basic HTML stripping (production apps should use a proper HTML parser)
  html.gsub(/<[^>]+>/, " ")
    .gsub(/\s+/, " ")
    .strip
end

def to_h

Returns:
  • (Hash) - Note data as a hash
def to_h
  super.merge(
    parent_object: parent_object,
    parent_record_id: parent_record_id,
    content: content,
    format: format,
    created_by_actor: created_by_actor,
    content_plaintext: content_plaintext
  ).compact
end

def to_plaintext

Get plaintext version of content
def to_plaintext
  return content_plaintext if content_plaintext
  # If no plaintext, try to get markdown/html content and strip HTML
  html_content = content_markdown || content
  return nil unless html_content
  strip_html(html_content)
end

def update(*)

def update(*)
  raise NotImplementedError, "Notes cannot be updated. Create a new note instead."
end

def validate_content!(content)

def validate_content!(content)
  if content.nil? || content.to_s.strip.empty?
    raise ArgumentError, "content cannot be empty"
  end
end

def validate_format!(format)

def validate_format!(format)
  valid_formats = %w[plaintext html]
  unless valid_formats.include?(format.to_s)
    raise ArgumentError, "Invalid format: #{format}. Valid formats: #{valid_formats.join(", ")}"
  end
end

def validate_parent!(parent_object, parent_record_id)

def validate_parent!(parent_object, parent_record_id)
  if parent_object.nil? || parent_object.to_s.empty?
    raise ArgumentError, "parent_object is required"
  end
  if parent_record_id.nil? || parent_record_id.to_s.empty?
    raise ArgumentError, "parent_record_id is required"
  end
end