I want to receive all Elastic Cloud configuration templates from the Elastic Cloud API with faraday in my Ruby on Rails application.
I've tried to workaround and modify the api call, but it's not possible to not get an array as a response.
This is the repsonse from the api:
[
{
"name": "Small Template",
"id": 1
},
{
"name": "Medium Template",
"id": 2
},
{
"name": "Big Template",
"id": 3
}
]
This is my faraday configuration.
Faraday.new(url: "https://#{ENV['ECE_USERNAME']}:#{ENV['ECE_PASSWORD']}##{ENV['ECE_HOST']}") do |c|
c.response :json, parser_options: {symbolize_names: true}
c.response :logger
c.use JSONParser
c.adapter Faraday.default_adapter
end
And the api call:
#conn.get('/api/v1/platform/configuration/templates/deployments?stack_version').body
This is the error message I receive
undefined method `has_key?' for #<Array:xxxxx>
I don't want to use anything else, but faraday. I'm open to new gems to help faraday, but this client should stay.
How can I setup faraday to handle the array response or how can I workaround my problem?
Related
I'm trying to upload a file to an API with Httparty gem.
Here is the requested format, from the documentation of this API:
Method: POST
Content-Type: application/json
{
"name": "filename.png",
"type": 2,
"buffer": "iVBOR..."
}
My document is stored with ActiveStorage, here is my function to download it and generate the parameters HASH:
def document_params
{
"name": #document.file.filename.to_s,
"type": IDENTIFIERS[#document.document_type],
"buffer": #document.file.download
}
end
Then I send the data with this function:
HTTParty.post(
url,
headers: request_headers,
body: document_params.to_json
)
The problem is that when I do document_params.to_json, I get this error:
UndefinedConversionError ("\xC4" from ASCII-8BIT to UTF-8)
If I don't call to_json, data is not sent as a valid json, but as a hash representation like this: {:key=>"value"}
I would like to just send the file data as binary data, without trying to convert it to UTF-8.
I found a solution: encoding the file content to Base64:
def document_params
{
"name": #document.file.filename.to_s,
"type": IDENTIFIERS[#document.document_type],
"buffer": Base64.encode64(#document.file.download)
}
end
I want to send a transactional mail via Sendgrid when a user registers (I use devise for authentication). I had this working fine in my_mailer.rb using SMTP as follows:
def confirmation_instructions(record, token, opts={})
# SMTP header for Sendgrid - v2
# headers["X-SMTPAPI"]= {
# "sub": {
# "-CONFIRM_TOKEN-": [
# token
# ]
# },
# "filters": {
# "templates": {
# "settings": {
# "enable": 1,
# "template_id": "1111111-1111-1111-1111-111111111111"
# }
# }
# }
# }.to_json
However, prompted by Sendgrid to use v3 syntax to support newer mail templates, I changed code to the following (from the sendgrid help docs, as opposed to a real understanding):
def confirmation_instructions(record, token, opts={})
require 'sendgrid-ruby'
include SendGrid
sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
data = JSON.parse('{
"substitutions": {
"-CONFIRM_TOKEN-": [
token
],
"template_id": "1111111-1111-1111-1111-111111111111"
}')
response = sg.client.mail._("send").post(request_body: data)
puts response.status_code
puts response.body
puts response.parsed_body
puts response.headers
Now I get the error message:
'NoMethodError (undefined method `include' for #<MyMailer:0x0000000003cfa398>):'
If I comment out the 'include' line I get:
'TypeError (no implicit conversion of nil into String):' on the line: "sg = SendGrid..."
I use the Gem: sendgrid-ruby (5.3.0)
Any ideas would be appreciated - I've been trying to hit on the correct syntax by trial-and-error for a while now and finally admit I am stuck.
UPDATE#1:
The first issue was I was using the wrong API_KEY env. variable (copied from 2 different help docs): "SENDGRID_API_KEY" (in code) vs. SENDGRID_APIKEY_GENERAL (set in Heroku). Fixed.
UPDATE #2:
With the "include" line commented out I now seem to be getting a JSON parse error:
JSON::ParserError (416: unexpected token at 'token
So my 2 current issues are now:
(1) I would like 'token' to be the confirmation token variable but it is not being passed
(2) Sending the below simple (1 line) content of 'data' does not throw up an error, but the appropriate template within Sendgrid is not selected:
data = JSON.parse('{
"template_id": "1111111-1111-1111-1111-111111111111"
}')
UPDATE #3:
Here's an update on the status of my issue and exactly where I am now stuck:
This code works fine (using Sendgrid v2 which I am trying to upgrade from):
def confirmation_instructions(record, token, opts={})
#
# SMTP header for Sendgrid - v2
# This works fine
#
headers["X-SMTPAPI"]= {
"sub": {
"-CONFIRM_TOKEN-": [
token
]
},
"filters": {
"templates": {
"settings": {
"enable": 1,
"template_id": "1111111-1111-1111-1111-111111111111"
}
}
}
}.to_json
This Sendgrid v3 code does not work (the email does get sent via Sendgrid but it does not select the template within Sendgrid - it just uses whatever code is in app/views/my_mailer/confirmation_instructions.html.erb):
#
# Sendgrid API v3
# This sends an email alright but it takes content from app/views/my_mailer/confirmation_instructions.html.erb
# It DOES NOT select the template within Sendgrid
#
data = JSON.parse('{
"template_id": "1111111-1111-1111-1111-111111111111",
"personalizations": [
{
"substitutions": {
"-CONFIRM_TOKEN-": "'+token+'"
}
}
]
}')
sg = SendGrid::API.new(api_key: ENV['SENDGRID_APIKEY_GENERAL2'])
response = sg.client.mail._("send").post(request_body: data)
puts response.status_code
puts response.body
puts response.parsed_body
puts response.headers
As always, any insight appreciated.
For anyone trying to get SendGrid v3 API working with Ruby/Devise/Heroku and use SendGrid's dynamic transactional emails these tips may help you. I spent a week getting this to work and these steps (& mistakes I made) were not apparent in the various documentation:
Generating the SendGrid API key (on SendGrid website): when generating the key, the API key only appears once allowing you to copy it, from then on it is invisible. As I could not see the key later I mistakenly used the "API Key ID" in my Heroku environment variables, rather than the true API Key.
Ensure the name you give the key in Heroku (for me: "SENDGRID_APIKEY_GENERAL") matches the code you use to reference it i.e. sg = SendGrid::API.new(api_key: ENV['SENDGRID_APIKEY_GENERAL'])
For sending variables to be substituted in the template use "dynamic_template_data" and not "substitutions". This should be within the "personalizations" section (see code example below).
I found it useful to refer to the Sendgrid dynamic template ID by using an environment variable in Heroku (for me: 'SENDGRID_TRANS_EMAIL_CONFIRM_TEMPLATE_ID') as opposed to hard-coding in Ruby (just allowed me to experiment with different templates rather than changing code).
The correct syntax for using a variable in the JSON string in Ruby is e.g. "CONFIRM_TOKEN": "'+token+'" (see code example below)
Do not use other characters in the name: i.e. "CONFIRM_TOKEN" worked but "-CONFIRM_TOKEN-" did not work
In the HTML of the transactional email template on SendGrid use this syntax for the substitution: {{CONFIRM_TOKEN}}
When creating a transactional template on SendGrid you can only have a 'design' view or a 'code' view not both. You must select at the start when creating the template and cannot switch after.
In the devise confirmations_instructions action refer to the user as a record (e.g. email) as record.email
Gemfile: gem 'rails', '5.2.2' ruby '2.6.1' gem 'devise', '4.6.1' gem 'sendgrid-ruby', '6.0.0'
Here is my successful ruby code that I have in my_mailer.rb:
def confirmation_instructions(record, token, opts={})
data = JSON.parse('{
"personalizations": [
{
"to": [
{
"email": "'+record.email+'"
}
],
"subject": "Some nice subject line content",
"dynamic_template_data": {
"CONFIRM_TOKEN": "'+token+'",
"TEST_DATA": "hello"
}
}
],
"from": {
"email": "aaaa#aaaa.com"
},
"content": [
{
"type": "text/plain",
"value": "and easy to do anywhere, even with Ruby"
}
],
"template_id": "'+ENV['SENDGRID_TRANS_EMAIL_CONFIRM_TEMPLATE_ID']+'"
}')
sg = SendGrid::API.new(api_key: ENV['SENDGRID_APIKEY_GENERAL'])
response = sg.client.mail._("send").post(request_body: data)
puts response.status_code
puts response.body
puts response.headers
You cannot include a module in a method. You have to include it in your class, so outside of the methode, like
class SomethingMailer
require 'sendgrid-ruby'
include SendGrid
def confirmation_instructions(record, token, opts={})
...
end
end
For your third update problem:
You are not sending a JSON but instead you are creating a JSON, then parsing it into a hash, then sending that hash, instead of the JSON.
JSON.parse #parses a JSON into a Hash
You should do the opposite and have a hash that you transform into a JSON
Something like
data = {
template_id: your_template_id # or pass a string
personalizations: [
...
]
}
Then you call
data_json = data.to_json
response = sg.client.mail._("send").post(request_body: data_json)
However this does not explain why your template in app/views/my_mailer/confirmation_instructions.html.erb gets sent. So I think you are either calling a different mailer_method at the same time, or you are not calling your actual confirmation_instructions method at all. Try to confirm that you SendGrid Methods actually is called and see what it returns. It should have returned some kind of error before, when you were sending a hash instead of a string.
I've successfully used rspec_api_documentation gem to generate docs for some other resources in an application.
But when I do the same for a new resource, the JSON file's response_body is empty (though the rest of the JSON exists). And I'm unsure why this is happening.
response_body in generated JSON:
"response_body": "{\n \"data\": {\n \"notes\": [\n\n ]\n },\n \"status\": 200,\n \"message\": \"Ok\"\n}"
Outputed API docs:
{
"data": {
"notes": [
]
},
"status": 200,
"message": "Ok"
}
What I've done:
Tested the endpoint with a curl and can confirm it returns a full response (now I'm just trying to build the documentation)
Created note_factory.rb
Added #notes = create_list(:note, 1,...) in a before(:all) do block at beginning of notes_spec.rb (and printed to console to confirm it creates an object)
Also created below as the test.
get '/api/v1/notes' do
example 'Return all notes' do
explanation 'Return all notes within an account.'
set_jwt_auth_headers(#api_reader)
do_request
expect(status).to eq 200
end
end
I'm confused what I might be missing that causes the JSON response related to RAD to be empty.
Does anyone have any ideas what might cause this?
I am a newbie Rails 4 developer and need some help adding an API to my simple application using rest-client. For simplification, I will just ask about the API's authentication system.
I have a simple app which uses the Devise gem for authentication. I would like for every user that creates an account to have a calendar for scheduling and booking purposes. to achieve this I am using an API called timekit (http://timekit.io/). Their authentication system responds the to following cURL code example:
curl -X POST \
-H 'Timekit-App: docs' \
-d '{
"email": "doc.brown#timekit.io",
"password": "DeLorean"
}' \
https://api.timekit.io/v2/auth
This will then return the following JSON:
{
"data": {
"activated": true,
"email": "doc.brown#timekit.io",
"first_name": "Dr. Emmett",
"img": "http://www.gravatar.com/avatar/7a613e5348d63476276935025",
"last_name": "Brown",
"last_sync": null,
"name": "Dr. Emmett Brown",
"timezone": "America/Los_Angeles",
"token": "UZpl3v3PTP1PRwqIrU0DSVpbJkNKl5gN",
"token_generated_at": null,
"api_token": "FluxCapacitator", // note that this is usually a randomized string and not an actual word
}
}
So now my questions are the following:
1) Where in the Rails framework do I implement this?
2) How do I do so using rest-client instead of cURL?
3) How do I integrate this with Devise?
4)What are good resources to enhance my own understanding of what I am actually doing here?
Awesome to see your using Timekit (I'm one of the core devs) :)
We don't currently have a ruby gem and I'm not a Ruby developer, but here's a quick code example on how to accomplish this with the HTTParty library:
# Global configs for "admin" user
TK_ADMIN_USER = 'my-email#gmail.com'
TK_ADMIN_TOKEN = '12345ABCD'
# Example usage:
# timekit = Timekit.new(TK_ADMIN_USER, TK_ADMIN_TOKEN)
# tk_user = timekit.create_user(account)
# someInternalUser.update(tk_token: tk_user.token)
class Timekit
include HTTParty
base_uri 'https://api.timekit.io'
headers 'Timekit-App' => 'your-app-name'
def initialize(user, password)
#auth = {username: user, password: password}
end
def create_user(account)
options = {
body: {
"email" => account.email,
"first_name" => account.first_name,
"last_name" => account.last_name,
"timezone" => "America/Los_Angeles"
}.to_json,
basic_auth: #auth,
}
self.class.post('/v2/users/', options)
end
def create_calendar(account)
options = {
body: {
name: "Bookings",
description: "Hold bookings for clients."
}.to_json,
basic_auth: #auth
}
self.class.post('/v2/calendars', options)
end
end
https://gist.github.com/laander/83cb7f5dde1f933173c7
In general, the idea is to create a user through our API (you can do it transparently whenever a user signs up in your onboarding) and then save the API Token that the API returns. After that, you can perform API calls impersonating that users (fetch calendars, create events, query availability etc)
Just write us in the chat on timekit.io if you need more hands-on help!
You might consider creating a small library to wrap interactions with the Timekit web API in the /lib directory of your project. I didn't see a gem for this web API, so you might consider extracting this logic into one so the community can benefit.
The rest-client gem appears pretty easy to use:
auth_hash = {
"email" => "doc.brown#timekit.io",
"password" => "DeLorean"
}
RestClient.post "https://api.timekit.io/v2/auth", auth_hash.to_json, content_type: :json, accept: :json
If you are creating a new Timekit account for each user, you might consider adding the credential to the User model or another related model that stores the credential.
I have a very simple Ruby Rack server, like:
app = Proc.new do |env|
req = Rack::Request.new(env).params
p req.inspect
[200, { 'Content-Type' => 'text/plain' }, ['Some body']]
end
Rack::Handler::Thin.run(app, :Port => 4001, :threaded => true)
Whenever I send a POST HTTP request to the server with an JSON object:
{ "session": {
"accountId": String,
"callId": String,
"from": Object,
"headers": Object,
"id": String,
"initialText": String,
"parameters": Object,
"timestamp": String,
"to": Object,
"userType": String } }
I receive nothing. I can detect the request received but can't get the data. The results at my console for puts req.inspect is something like:
"{}"
How am I supposed to get the data?
I tried to change that with something like:
request = Rack::Request.new env
object = JSON.parse request.body
puts JSON.pretty_generate(object)
But I'm getting the following warning:
!! Unexpected error while processing request: can't convert StringIO into String
It seems that I'm supposed to use something like:
msg = JSON.parse env['rack.input'].read
Then just use params in the msg hash.
At least it worked for me this way.
env['rack.input'].gets
This worked for me. I found that using curl or wget to test POST requests against a Rack (v1.4.1) server required using this code as a fallback to get the request body. POST requests out in the wild (e.g. GitHub WebHooks) didn't have this same problem.
One more way to do that:
# ...
request = Rack::Request.new env
object = JSON.parse request.body.gets
# ...
See the documentation: www.rubydoc.info/gems/rack/Rack/Lint/InputWrapper