Class: Forem::Concept

Inherits:
APIResource show all
Extended by:
APIOperations::List, APIOperations::Update
Defined in:
lib/forem/resources/concept.rb

Overview

Represents a Forem concept (a semantic, ML-generated tag).

Concepts are semantic categories derived from article embeddings rather than from explicit user tags: each concept carries an anchor embedding generated from its description, and articles whose embeddings fall within the concept's similarity_threshold are classified under it. They are used for advanced semantic categorization, automated feeds, and interest mapping.

The public concepts endpoints are readable by any authenticated user (a super admin sees every concept, other users see the concepts they have been granted access to). Creating and deleting concepts is an admin-only operation exposed separately under /api/admin/concepts.

Available operations (via mixins):

- +List+     — GET /api/concepts
- +Update+   — PUT /api/concepts/:id

Custom class methods:

- +retrieve+ — GET /api/concepts/:id
- +articles+ — GET /api/concepts/:id/articles
- +search+   — GET /api/concepts/search

Concept Fields

  • id (Integer) — Unique concept ID
  • name (String) — Human readable label for the concept
  • slug (String) — URL-friendly identifier
  • description (String, nullable) — Semantic definition used to generate the concept's anchor embedding
  • parent_id (Integer, nullable) — Parent concept when using a hierarchy
  • score (Float) — Concept popularity / curation score
  • similarity_threshold (Float, nullable) — Cosine distance threshold (0.0–1.0) an article embedding must satisfy to be classified under the concept
  • created_at / updated_at (String) — ISO 8601 timestamps
  • daily_metrics (Array) — Nested per-day activity rollups, newest first. Each entry has date, articles_count, comments_count, page_views, reactions_count, and popularity_score
  • top_articles (Array) — Only on retrieve / update responses; the three highest-scoring articles for the concept, each with id, title, slug, score, and published_at

Results from Concept.search carry two extra fields: distance (cosine distance from the query embedding) and similarity (+1.0 - distance+).

Examples:

List concepts with a 30-day metrics window

concepts = client.concepts.list(per_page: 20, days: 30)
concepts.each { |c| puts "#{c.id}: #{c.name} (#{c.score})" }

Retrieve a concept and read its nested daily metrics

concept = client.concepts.retrieve(7)
concept.daily_metrics.each { |m| puts "#{m.date}: #{m.articles_count}" }

Update a concept (params are wrapped in concept: automatically)

client.concepts.update(7, description: "Databases and storage engines")

List the articles classified under a concept

client.concepts.articles(7, sort: "score", per_page: 25).each do |a|
  puts a.title
end

Semantic search across accessible concepts

client.concepts.search(q: "vector databases", per_page: 5).each do |c|
  puts "#{c.name}: #{c.similarity}"
end

See Also:

Constant Summary collapse

OBJECT_NAME =
"concept"
RESOURCE_PATH =
"/api/concepts"

Instance Attribute Summary

Attributes inherited from ForemObject

#requestor

Class Method Summary collapse

Methods included from APIOperations::List

list

Methods included from APIOperations::Update

update

Methods inherited from APIResource

#refresh, resource_path, #resource_url

Methods inherited from ForemObject

#==, #[], #[]=, construct_from, cursor_list, #initialize, #inspect, #method_missing, paginated_list, #respond_to_missing?, #to_hash

Methods included from APIOperations::Request

included, #request

Constructor Details

This class inherits a constructor from Forem::ForemObject

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Forem::ForemObject

Class Method Details

.articles(id, params = {}, opts = {}) ⇒ Forem::ListObject<Forem::Article>

Return the published articles classified under a concept.

Sends a GET request to /api/concepts/:id/articles. Articles are ordered by cosine similarity to the concept (closest first), with the article score as a tiebreaker, unless sort is "score" in which case they are ordered by article score alone.

Examples:

articles = Forem::Concept.articles(7, { sort: "score" }, requestor: requestor)
articles.each { |a| puts a.title }

Parameters:

  • id (Integer, String)

    the concept ID

  • params (Hash) (defaults to: {})

    query parameters

  • opts (Hash) (defaults to: {})

    per-request options (e.g., :api_key)

Options Hash (params):

  • :sort (String)

    "score" to sort by article score; omitted or any other value sorts by semantic distance

  • :page (Integer)

    page number (default: 1)

  • :per_page (Integer)

    number of results per page (default: 10)

Returns:

See Also:



147
148
149
# File 'lib/forem/resources/concept.rb', line 147

def self.articles(id, params = {}, opts = {})
  Forem::Article.paginated_list("#{resource_path}/#{id}/articles", params, opts)
end

.retrieve(id, params = {}, opts = {}) ⇒ Forem::Concept

Retrieve a single concept by its numeric ID.

Sends a GET request to /api/concepts/:id. Unlike the standard APIOperations::Retrieve mixin this accepts query params, because the endpoint takes a days window that controls how many nested daily_metrics entries are returned.

Examples:

Forem::Concept.retrieve(7, { days: 30 }, requestor: requestor)

Parameters:

  • id (Integer, String)

    the concept ID

  • params (Hash) (defaults to: {})

    query parameters

  • opts (Hash) (defaults to: {})

    per-request options (e.g., :api_key)

Options Hash (params):

  • :days (Integer)

    days of activity to include in daily_metrics (default: 7, minimum: 1)

Returns:

See Also:



93
94
95
96
97
# File 'lib/forem/resources/concept.rb', line 93

def self.retrieve(id, params = {}, opts = {})
  requestor = opts[:requestor]
  resp = request(:get, "#{resource_path}/#{id}", params, opts)
  construct_from(resp.parsed_body, requestor: requestor)
end

.search(params = {}, opts = {}) ⇒ Array<Forem::Concept>

Semantically search the concepts accessible to the caller.

Sends a GET request to /api/concepts/search. The query text is embedded and compared against each concept's anchor embedding; results are returned closest-first and are not paginated (only the number of results is configurable). Each returned concept carries distance and similarity in addition to the usual concept fields.

Requires an API key — unlike the other concept endpoints, this action cannot be called with a session user.

Examples:

results = Forem::Concept.search({ q: "databases" }, requestor: requestor)
results.each { |c| puts "#{c.name} #{c.distance}" }

Parameters:

  • params (Hash) (defaults to: {})

    query parameters

  • opts (Hash) (defaults to: {})

    per-request options (e.g., :api_key)

Options Hash (params):

  • :q (String) — default: required

    the search text

  • :per_page (Integer)

    number of concepts to return (default: 10, max: 50)

  • :threshold (Float)

    optional maximum cosine distance (0.0–2.0); concepts further away are filtered out

Returns:

See Also:



174
175
176
177
178
# File 'lib/forem/resources/concept.rb', line 174

def self.search(params = {}, opts = {})
  requestor = opts[:requestor]
  resp = request(:get, "#{resource_path}/search", params, opts)
  (resp.parsed_body || []).map { |item| construct_from(item, requestor: requestor) }
end

.update(id, params = {}, opts = {}) ⇒ Forem::Concept

Update an existing concept.

Sends a PUT request to /api/concepts/:id. The Forem API expects the attributes to be nested under a concept key, so flat params are wrapped automatically; already-wrapped params are passed through untouched.

Only score, description, and similarity_threshold are permitted by the API. Changing the description regenerates the concept's anchor embedding, and changing either the description or the similarity threshold enqueues a background re-classification of existing articles.

Examples:

Forem::Concept.update(7, { similarity_threshold: 0.8 }, requestor: requestor)

Parameters:

  • id (Integer, String)

    the concept ID to update

  • params (Hash) (defaults to: {})

    concept attributes to change

  • opts (Hash) (defaults to: {})

    per-request options (e.g., :api_key)

Options Hash (params):

  • :score (Float)

    new curation score

  • :description (String)

    new semantic description

  • :similarity_threshold (Float)

    new cosine distance threshold (0.0–1.0)

Returns:

See Also:



122
123
124
# File 'lib/forem/resources/concept.rb', line 122

def self.update(id, params = {}, opts = {})
  super(id, wrap_params(params), opts)
end