Class: SecApi::Query
- Inherits:
-
Object
- Object
- SecApi::Query
- Defined in:
- lib/sec_api/query.rb
Overview
International forms (20-F, 40-F, 6-K) are supported as first-class citizens. No special handling required - they work identically to domestic forms (10-K, 10-Q, 8-K).
Fluent query builder for SEC filing searches using Lucene query syntax.
Builder Pattern Design: Uses the fluent builder pattern (like ActiveRecord) for query construction. Key aspects:
-
Intermediate methods (.ticker, .form_type, .date_range) return
selffor chaining -
Terminal method (.search) executes the query and returns results
-
Each
client.querycall returns a NEW instance (stateless between calls) -
Instance variables accumulate query parts until terminal method is called
Why fluent builder? More readable than positional args for complex queries, allows optional filters, and familiar to Ruby developers from ActiveRecord.
Provides a chainable, ActiveRecord-style interface for building and executing SEC filing queries. Each method returns self for chaining, with .search as the terminal method that executes the query.
Constant Summary collapse
- DOMESTIC_FORM_TYPES =
Note:
This is not an exhaustive list. The API accepts any form type string.
Common domestic SEC form types for reference.
%w[10-K 10-Q 8-K S-1 S-3 4 13F DEF\ 14A].freeze
- INTERNATIONAL_FORM_TYPES =
International SEC form types for foreign private issuers.
%w[20-F 40-F 6-K].freeze
- ALL_FORM_TYPES =
Note:
This is not an exhaustive list. The API accepts any form type string.
Combined list of common domestic and international form types.
(DOMESTIC_FORM_TYPES + INTERNATIONAL_FORM_TYPES).freeze
Instance Method Summary collapse
-
#auto_paginate ⇒ Enumerator::Lazy
Executes the query and returns a lazy enumerator for automatic pagination.
-
#cik(cik_number) ⇒ self
Filter filings by Central Index Key (CIK).
-
#date_range(from:, to:) ⇒ self
Filter filings by date range.
-
#form_type(*types) ⇒ self
Filter filings by form type(s).
-
#fulltext(query, options = {}) ⇒ SecApi::Collections::FulltextResults
Execute a full-text search across SEC filings.
-
#initialize(client) ⇒ SecApi::Query
constructor
private
Creates a new Query builder instance.
-
#limit(count) ⇒ self
Limit the number of results returned.
-
#search(query = nil, options = {}) ⇒ Object
Execute the query and return filings.
-
#search_text(keywords) ⇒ self
Execute full-text search across filing content.
-
#ticker(*tickers) ⇒ self
Filter filings by ticker symbol(s).
-
#to_lucene ⇒ String
Returns the assembled Lucene query string for debugging/logging.
Constructor Details
#initialize(client) ⇒ SecApi::Query
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Creates a new Query builder instance.
Query instances are typically obtained via Client#query rather than direct instantiation. Each call to client.query returns a fresh instance to ensure query chains start with clean state.
106 107 108 109 110 111 112 |
# File 'lib/sec_api/query.rb', line 106 def initialize(client) @_client = client @query_parts = [] @from_offset = 0 @page_size = 50 @sort_config = [{"filedAt" => {"order" => "desc"}}] end |
Instance Method Details
#auto_paginate ⇒ Enumerator::Lazy
Executes the query and returns a lazy enumerator for automatic pagination.
Convenience method that chains #search with Collections::Filings#auto_paginate. Useful for backfill operations where you want to process all matching filings across multiple pages.
301 302 303 |
# File 'lib/sec_api/query.rb', line 301 def auto_paginate search.auto_paginate end |
#cik(cik_number) ⇒ self
The SEC API requires CIK values WITHOUT leading zeros. This method automatically normalizes the input.
Filter filings by Central Index Key (CIK).
152 153 154 155 156 157 |
# File 'lib/sec_api/query.rb', line 152 def cik(cik_number) normalized_cik = cik_number.to_s.gsub(/^0+/, "") raise ArgumentError, "CIK cannot be empty or zero" if normalized_cik.empty? @query_parts << "cik:#{normalized_cik}" self end |
#date_range(from:, to:) ⇒ self
Filter filings by date range.
268 269 270 271 272 273 274 275 276 277 |
# File 'lib/sec_api/query.rb', line 268 def date_range(from:, to:) raise ArgumentError, "from: is required" if from.nil? raise ArgumentError, "to: is required" if to.nil? from_date = coerce_date(from) to_date = coerce_date(to) @query_parts << "filedAt:[#{from_date} TO #{to_date}]" self end |
#form_type(*types) ⇒ self
Form types are case-sensitive. “10-K” and “10-k” are different.
International forms work identically to domestic forms - no special API handling.
Filter filings by form type(s).
Supports both domestic and international SEC form types. International forms (20-F, 40-F, 6-K) are treated as first-class citizens - no special handling required.
186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/sec_api/query.rb', line 186 def form_type(*types) types = types.flatten.map(&:to_s) raise ArgumentError, "At least one form type is required" if types.empty? @query_parts << if types.size == 1 "formType:\"#{types.first}\"" else quoted_types = types.map { |t| "\"#{t}\"" }.join(" OR ") "formType:(#{quoted_types})" end self end |
#fulltext(query, options = {}) ⇒ SecApi::Collections::FulltextResults
Execute a full-text search across SEC filings.
384 385 386 387 |
# File 'lib/sec_api/query.rb', line 384 def fulltext(query, = {}) response = @_client.connection.post("/full-text-search", {query: query}.merge()) Collections::FulltextResults.new(response.body) end |
#limit(count) ⇒ self
Limit the number of results returned.
Sets the maximum number of filings to return in the response. When not specified, defaults to 50 results.
242 243 244 245 246 247 248 |
# File 'lib/sec_api/query.rb', line 242 def limit(count) count = count.to_i raise ArgumentError, "Limit must be a positive integer" if count <= 0 @page_size = count self end |
#search ⇒ SecApi::Collections::Filings #search(query, options = {}) ⇒ SecApi::Collections::Filings
Execute the query and return filings.
This is the terminal method that builds the Lucene query from accumulated filters and sends it to the sec-api.io API.
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/sec_api/query.rb', line 332 def search(query = nil, = {}) if query.is_a?(String) # Backward-compatible: raw query string passed directly warn "[DEPRECATION] Passing raw Lucene query strings to #search is deprecated. " \ "Use the fluent builder instead: client.query.ticker('AAPL').form_type('10-K').search" payload = {query: query}.merge() response = @_client.connection.post("/", payload) Collections::Filings.new(response.body) else # Fluent builder: build from accumulated query parts lucene_query = to_lucene payload = { query: lucene_query, from: @from_offset.to_s, size: @page_size.to_s, sort: @sort_config } # Store query context for pagination (excludes 'from' which changes per page) query_context = { query: lucene_query, size: @page_size.to_s, sort: @sort_config } response = @_client.connection.post("/", payload) Collections::Filings.new(response.body, client: @_client, query_context: query_context) end end |
#search_text(keywords) ⇒ self
Execute full-text search across filing content.
Adds a full-text search clause to the query. The search terms are quoted to match the exact phrase. Combines with other filters using AND.
217 218 219 220 221 222 223 224 225 |
# File 'lib/sec_api/query.rb', line 217 def search_text(keywords) raise ArgumentError, "Search keywords are required" if keywords.nil? || keywords.to_s.strip.empty? # Escape backslashes first, then quotes for valid Lucene phrase syntax # In gsub replacement, \\\\ (4 backslashes) produces \\ (2 actual backslashes) escaped = keywords.to_s.strip.gsub("\\") { "\\\\" }.gsub('"', '\\"') @query_parts << "\"#{escaped}\"" self end |
#ticker(*tickers) ⇒ self
Filter filings by ticker symbol(s).
125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/sec_api/query.rb', line 125 def ticker(*tickers) tickers = tickers.flatten.map(&:to_s).map(&:upcase) @query_parts << if tickers.size == 1 "ticker:#{tickers.first}" else "ticker:(#{tickers.join(", ")})" end self end |
#to_lucene ⇒ String
Returns the assembled Lucene query string for debugging/logging.
370 371 372 |
# File 'lib/sec_api/query.rb', line 370 def to_lucene @query_parts.join(" AND ") end |