Class: ShipReal::Client
- Inherits:
-
Object
- Object
- ShipReal::Client
- Defined in:
- lib/shipreal.rb
Overview
Talks to the ShipReal API.
Instance Attribute Summary collapse
-
#base_url ⇒ Object
readonly
Returns the value of attribute base_url.
-
#sandbox ⇒ Object
readonly
Returns the value of attribute sandbox.
-
#timeout ⇒ Object
readonly
Returns the value of attribute timeout.
Instance Method Summary collapse
-
#ask(query) ⇒ Object
Ask in natural language (NLWeb).
-
#ask_stream(query) ⇒ Object
The same question, streamed.
-
#batch(requests) ⇒ Object
Several reads in one round trip, up to MAX_BATCH.
-
#course ⇒ Object
Totals, language and the subtitle languages.
-
#initialize(base_url: DEFAULT_BASE_URL, sandbox: false, timeout: 30) ⇒ Client
constructor
sandbox: routes reads at the frozen fixture data.
-
#module_by(slug_or_title) ⇒ Object
One module by slug, or by an exact or partial title match.
-
#modules(query = nil) ⇒ Object
Every matching module, following pagination for you.
-
#pricing(region: nil) ⇒ Object
Current plans and prices.
-
#search(query: nil, page: nil, limit: nil, cursor: nil) ⇒ Object
Search the curriculum.
Constructor Details
#initialize(base_url: DEFAULT_BASE_URL, sandbox: false, timeout: 30) ⇒ Client
sandbox: routes reads at the frozen fixture data. Same code path and same shapes over contents that never change, so a test written against it stays green when the curriculum moves. Fixture prices are 1 unit and fixture links point at example.invalid, so sandbox data leaking into real output is obvious. See https://shipreal.dev/sandbox
63 64 65 66 67 |
# File 'lib/shipreal.rb', line 63 def initialize(base_url: DEFAULT_BASE_URL, sandbox: false, timeout: 30) @base_url = base_url.chomp("/") @sandbox = sandbox @timeout = timeout end |
Instance Attribute Details
#base_url ⇒ Object (readonly)
Returns the value of attribute base_url.
56 57 58 |
# File 'lib/shipreal.rb', line 56 def base_url @base_url end |
#sandbox ⇒ Object (readonly)
Returns the value of attribute sandbox.
56 57 58 |
# File 'lib/shipreal.rb', line 56 def sandbox @sandbox end |
#timeout ⇒ Object (readonly)
Returns the value of attribute timeout.
56 57 58 |
# File 'lib/shipreal.rb', line 56 def timeout @timeout end |
Instance Method Details
#ask(query) ⇒ Object
Ask in natural language (NLWeb).
There is no model behind this: it runs the same keyword search, which means it says so when nothing matches instead of inventing a module.
134 135 136 137 138 |
# File 'lib/shipreal.rb', line 134 def ask(query) raise ArgumentError, "ask needs a question" if query.to_s.empty? request(:post, "#{@base_url}/ask", body: { query: query }) end |
#ask_stream(query) ⇒ Object
The same question, streamed. Yields NLWeb events as they arrive: "start", then one "result" per hit, then "complete".
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 |
# File 'lib/shipreal.rb', line 142 def ask_stream(query) raise ArgumentError, "ask_stream needs a question" if query.to_s.empty? return enum_for(:ask_stream, query) unless block_given? uri = URI("#{@base_url}/ask") req = Net::HTTP::Post.new(uri) req["accept"] = "text/event-stream" req["content-type"] = "application/json" req["user-agent"] = USER_AGENT req.body = JSON.generate({ query: query }) http(uri).request(req) do |res| raise Error.new(res.code.to_i, safe_json(res.body), uri.to_s) unless res.is_a?(Net::HTTPSuccess) event = "message" data = +"" res.read_body do |chunk| chunk.each_line do |raw| line = raw.chomp if line.empty? # A blank line closes an SSE frame. Anything short of one is a # partial frame and waits for the next line. unless data.empty? parsed = safe_json(data) yield({ "event" => event, "data" => parsed }) if parsed end event = "message" data = +"" elsif line.start_with?("event:") event = line[6..].strip elsif line.start_with?("data:") data << line[5..].strip end end end end end |
#batch(requests) ⇒ Object
Several reads in one round trip, up to MAX_BATCH.
Each item comes back with its own status, so check per item rather than assuming the whole batch succeeded.
124 125 126 127 128 |
# File 'lib/shipreal.rb', line 124 def batch(requests) raise ArgumentError, "batch takes at most #{MAX_BATCH} requests" if requests.length > MAX_BATCH request(:post, "#{@base_url}/api/#{API_VERSION}/batch", body: { requests: requests }) end |
#course ⇒ Object
Totals, language and the subtitle languages.
116 117 118 |
# File 'lib/shipreal.rb', line 116 def course get("/course") end |
#module_by(slug_or_title) ⇒ Object
One module by slug, or by an exact or partial title match.
92 93 94 95 96 |
# File 'lib/shipreal.rb', line 92 def module_by(slug_or_title) raise ArgumentError, "module_by needs a slug or title" if slug_or_title.to_s.empty? get("/modules/#{ERB_ESCAPE.call(slug_or_title.to_s)}") end |
#modules(query = nil) ⇒ Object
Every matching module, following pagination for you.
79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/shipreal.rb', line 79 def modules(query = nil) out = [] cursor = nil loop do page = search(query: query, limit: 100, cursor: cursor) out.concat(page["data"] || []) cursor = page.dig("pagination", "nextCursor") break if cursor.nil? || cursor.empty? end out end |
#pricing(region: nil) ⇒ Object
Current plans and prices.
Two regional prices are live at once, so quoting one without naming its region is misleading. Pass region ("intl" or "il") when you know which applies and the response comes back flattened to it.
103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/shipreal.rb', line 103 def pricing(region: nil) every = get("/pricing") return every unless %w[intl il].include?(region) complete = every["complete"][region].dup complete["url"] = every["complete"]["url"] teams = every["teams"][region].dup teams["minSeats"] = every["teams"]["minSeats"] teams["perSeat"] = true { "region" => region, "free" => every["free"], "complete" => complete, "teams" => teams } end |
#search(query: nil, page: nil, limit: nil, cursor: nil) ⇒ Object
Search the curriculum. Without a query, every module in course order.
Matching is a case-insensitive substring over title, description and part name, so an empty result means the course does not cover that topic under that name, rather than that the search was too clever.
74 75 76 |
# File 'lib/shipreal.rb', line 74 def search(query: nil, page: nil, limit: nil, cursor: nil) get("/modules", q: query, page: page, limit: limit, cursor: cursor) end |