I upload some files through Uppy, which saves the related JSON to a hidden form element. My create action submits this as a param called uppyResult. The input element looks like this once uploads are complete:
<input
name="uppyResult"
type="hidden"
value="[
{
"successful":[
{"source":"Dashboard","id":"uppy-e5vuacxwuamsw4u/png-1e-image/png-56448-1625714886789","name":"E5vUACXWUAMsW4u.png","extension":"png","meta":{"relativePath":null,"name":"E5vUACXWUAMsW4u.png","type":"image/png","key":"cache/663ecb0c8cac3ea40327f625f46cecdd.png","Content-Disposition":"inline; filename=\"E5vUACXWUAMsW4u.png\"; filename*=UTF-8''E5vUACXWUAMsW4u.png","Content-Type":"image/png","policy":"eyJleHBpcmF0aW9uIjoiMjAyMS0wNy0xNVQyMjozNzoxN1oiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJmcmFjdGlvbmNsdWItZGV2MiJ9LHsia2V5IjoiY2FjaGUvNjYzZWNiMGM4Y2FjM2VhNDAzMjdmNjI1ZjQ2Y2VjZGQucG5nIn0seyJDb250ZW50LURpc3Bvc2l0aW9uIjoiaW5saW5lOyBmaWxlbmFtZT1cIkU1dlVBQ1hXVUFNc1c0dS5wbmdcIjsgZmlsZW5hbWUqPVVURi04JydFNXZVQUNYV1VBTXNXNHUucG5nIn0seyJDb250ZW50LVR5cGUiOiJpbWFnZS9wbmcifSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwwLDEwNDg1NzYwXSx7IngtYW16LWNyZWRlbnRpYWwiOiJBS0lBM0pFWlpGQU5PM0I2UjRUTy8yMDIxMDcxNS91cy13ZXN0LTEvczMvYXdzNF9yZXF1ZXN0In0seyJ4LWFtei1hbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In0seyJ4LWFtei1kYXRlIjoiMjAyMTA3MTVUMjEzNzE3WiJ9XX0=","x-amz-credential":"AKIA3JEZZFANO3B6R4TO/20210715/region/s3/aws4_request","x-amz-algorithm":"AWS4-HMAC-SHA256","x-amz-date":"20210715T213717Z","x-amz-signature":"d95631770c5ad9aad4e21c59966463b7b7e29f55946a52145ae934d8cf2d8237"},"type":"image/png","data":{},"progress":{"uploadStarted":1626385018193,"uploadComplete":true,"percentage":100,"bytesUploaded":58322,"bytesTotal":58322,"postprocess":null},"size":56448,"isRemote":false,"remote":"","preview":"blob:https://example.com/ae686b0e-1cae-4b43-9589-b7c01446b7f0","xhrUpload":{"method":"post","formData":true,"endpoint":"https://bucket.s3.region.amazonaws.com","metaFields":["key","Content-Disposition","Content-Type","policy","x-amz-credential","x-amz-algorithm","x-amz-date","x-amz-signature"],"headers":{}},"response":{"status":204,"body":{"location":null},"uploadURL":null},"uploadURL":null,"isPaused":false},
{"source":"Dashboard","id":"uppy-91920f5bd03f91282b38941096ec4ffc/jpg-1e-image/jpeg-44471-1626247994037","name":"91920f5bd03f91282b38941096ec4ffc.jpg","extension":"jpg","meta":{"relativePath":null,"name":"91920f5bd03f91282b38941096ec4ffc.jpg","type":"image/jpeg","key":"cache/7ed930a77fb05ed135624e3c74187c84.jpg","Content-Disposition":"inline; filename=\"91920f5bd03f91282b38941096ec4ffc.jpg\"; filename*=UTF-8''91920f5bd03f91282b38941096ec4ffc.jpg","Content-Type":"image/jpeg","policy":"eyJleHBpcmF0aW9uIjoiMjAyMS0wNy0xNVQyMjozNzoxN1oiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJmcmFjdGlvbmNsdWItZGV2MiJ9LHsia2V5IjoiY2FjaGUvN2VkOTMwYTc3ZmIwNWVkMTM1NjI0ZTNjNzQxODdjODQuanBnIn0seyJDb250ZW50LURpc3Bvc2l0aW9uIjoiaW5saW5lOyBmaWxlbmFtZT1cIjkxOTIwZjViZDAzZjkxMjgyYjM4OTQxMDk2ZWM0ZmZjLmpwZ1wiOyBmaWxlbmFtZSo9VVRGLTgnJzkxOTIwZjViZDAzZjkxMjgyYjM4OTQxMDk2ZWM0ZmZjLmpwZyJ9LHsiQ29udGVudC1UeXBlIjoiaW1hZ2UvanBlZyJ9LFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjBdLHsieC1hbXotY3JlZGVudGlhbCI6IkFLSUEzSkVaWkZBTk8zQjZSNFRPLzIwMjEwNzE1L3VzLXdlc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IngtYW16LWFsZ29yaXRobSI6IkFXUzQtSE1BQy1TSEEyNTYifSx7IngtYW16LWRhdGUiOiIyMDIxMDcxNVQyMTM3MTdaIn1dfQ==","x-amz-credential":"AKIA3JEZZFANO3B6R4TO/20210715/region/s3/aws4_request","x-amz-algorithm":"AWS4-HMAC-SHA256","x-amz-date":"20210715T213717Z","x-amz-signature":"c9566b00a78bc465de654768745f39275c1a51038b98095dc9eb1e2f51d24cd2"},"type":"image/jpeg","data":{},"progress":{"uploadStarted":1626385018193,"uploadComplete":true,"percentage":100,"bytesUploaded":46446,"bytesTotal":46446,"postprocess":null},"size":44471,"isRemote":false,"remote":"","preview":"blob:https://example.com/99a4ad8f-0112-442f-ab57-fdf89eeed805","xhrUpload":{"method":"post","formData":true,"endpoint":"https://bucket.s3.region.amazonaws.com","metaFields":["key","Content-Disposition","Content-Type","policy","x-amz-credential","x-amz-algorithm","x-amz-date","x-amz-signature"],"headers":{}},"response":{"status":204,"body":{"location":null},"uploadURL":null},"uploadURL":null,"isPaused":false}
],
"failed":[],
"uploadID":"ckr5fnbkf00013g631ll6q1yk"
}
]"
>
My create action then does this:
def create
#item = current_user.owned_items.create(item_params)
successful_results.each do |file|
media_file_params = [{
fileable_type: "Item",
fileable_id: #item.id,
file_data: {
id: file["meta"]["key"].sub!("cache/", "store/"),
storage: "store",
metadata: {
filename: file["name"],
size: file["size"],
mime_type: file["type"]
}
}
}]
MediaFile.create(media_file_params)
end
end
private
def successful_results
uppy_result[0]['successful']
end
def uppy_result
JSON.parse(params["uppyResult"])
end
def item_params
params.require(:item).permit(:info)
end
This is throwing an error: JSON::ParserError (784: unexpected token at '{:id=>"store/8116adea58af1759f3865d315695ed8e.png", :storage=>"store", :metadata=>{:filename=>"E5vUACXWUAMsW4u.png", :size=>56448, :mime_type=>"image/png"}}'):
I thought that maybe the symbols were supposed to be strings, but that's definitely not it.
Related
I am currently working on a project using react and ruby on rails. My goal right now is to send a post request using fetch to create and store my user in my backend api on submission of my react form. My problem is, the backend isn't receiving my data correctly, resulting in a 406 error. I feel like i've tried everything, i'm going crazy, help.
REACT CODE:
form-
<form onSubmit={handleSubmit}>
<label>Name:</label>
<input type="text" required value={name} onChange={handleNameChange} name="name" placeholder="name"/>
<label>Password:</label>
<input type="password" required value={password} onChange={handlePasswordChange} name="password" placeholder="password"/>
<input type="submit" value="Create Account"/>
</form>
methods -
const [name, setName] = useState("")
const [password, setPassword] = useState("")
const handleNameChange = (e) => {
setName(e.target.value);
}
const handlePasswordChange = (e) => {
setPassword(e.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
const data = {name, password}
fetch("http://localhost:3000/users", {
method: 'POST',
body: JSON.stringify(data),
headers: {
Content_Type: "application/json",
}
})
RAILS CODE:
users controller-
def create
user = User.create(user_params)
if user.valid?
payload = {user_id: user.id}
token = encode_token(payload)
render json: { user: user, jwt: token }
else
render json: { error: 'failed to create user' }, status: :not_acceptable
end
end
private
def user_params
params.permit(:name, :password)
end
error -
backend error
It looks like user.valid? returns false, so your else statement kicks in:
render json: { error: 'failed to create user' }, status: :not_acceptable
The status: :not_acceptable generates the 406 error.
You should probably include the reason why user is not valid, and return a bad request response instead:
render json: { error: user.errors.full_messages}, status: :bad_request
My application sends data to a metadata repository through a REST API. I choosed Faraday to handle the HTTP requests. I basically setup some headers, a json dataset, and POST to the webservice. The following code takes place in the skills_controller, and is triggered when the user decides to publish the definition of a variable:
### Create the variable for the BusinessArea, get the location header in return
connection = Faraday.new("https://sis-sms-r.application.opendataquality.ch", :ssl => {:verify => false})
request_body = {
definedVariableType: #skill.skill_type.property,
description: {
en: #skill.description_translations.where(language: :en).take!,
de: #skill.description_translations.where(language: :de_OFS).take!,
fr: #skill.description_translations.where(language: :fr_OFS).take!
},
identifier: "Variable TEST 10",
name: {
en: #skill.name_translations.where(language: :en).take!,
de: #skill.name_translations.where(language: :de_OFS).take!,
fr: #skill.name_translations.where(language: :fr_OFS).take!
},
pattern: nil,
pseudonymized: true,
validFrom: Time.now,
validTo: Time.now + 1.year,
version: "1",
responsibleDeputy: {
identifier: #skill.deputy.email,
name: #skill.deputy.external_directory_id
},
responsibleOrgUnit: {
identifier: #skill.organisation.code,
name: #skill.organisation.external_reference
},
responsiblePerson: {
identifier: #skill.responsible.email,
name: #skill.responsible.external_directory_id
}
}
puts "--- body"
puts request_body
response = connection.post("/api/InformationFields/#{business_area.uuid}/definedVariables") do |req|
req.body = request_body.to_json
req.headers['Content-Type'] = 'application/json'
req.headers['Accept'] = 'application/json'
req.headers['Authorization'] = "Bearer #{token}"
end
puts "--- response"
puts response.status # Status 201 => successful request
puts response.body # Message
puts response.headers["location"] # uuid of new object
The method then renders an updated partial of the Show view of the skill, with its updated status.
This works fine as long as the request body is quite simple. But I'd like to handle a variable number of translations, and in some cases also send child records to the web service: i.e. implement loops, nested json objects, and probably partials.
I read about Jbuilder features to create complex json for views. Is there something similar I could use in a controller? Or is there a way to create a json view (and partials) and render it into Faraday' request body? Which would be a good architecture to build this feature? Do you know any article that would describe this?
Thanks a lot for showing me the way.
Start by creating an object that touches your application boundry:
class JSONClient
attr_reader :connection
def initialize(base_uri, **opts, &block)
#connection = Faraday.new(
base_uri,
**opts
) do |f|
f.request :json # encode req bodies as JSON
f.response :json # decode response bodies as JSON
yield f if block_given?
end
end
end
class BusinessAreaClient < JSONClient
def initialize(**opts)
super(
"https://sis-sms-r.application.opendataquality.ch",
ssl: { verify: false},
**opts
)
end
def defined_variables(skill:, uiid: token:)
response = connection.post(
"/api/InformationFields/#{uuid}/definedVariables"
SkillSerializer.serialize(skill),
{
'Authorization' => "Bearer #{token}"
}
)
if response.success?
response
else
# handle errors
end
end
end
response = BusinessAreaClient.new
.defined_variables(
skill: skill,
uuid: business_area.uuid,
token: token
)
This gives you object that can be tested in isolation and stubbed out. Its also the only object that should know about the quirks and particularities of the API thus limiting the impact on your application if it should change.
While using a view sounds like a good idea intially you're basically using a very awkward DSL to generate basic data structures like arrays and hashes that map 1-1 to JSON. jBuilder is also very slow.
As a first step to refactoring you could just extract turning a Skill into JSON into its own PORO:
class SkillSerializer < SimpleDelegator
LANG_MAPPING = {
en: :en,
de: :de_OFS,
fr: :fr_OFS
}.freeze
def serialize
{
definedVariableType: skill_type.property,
description: translate(description_translations),
identifier: "Variable TEST 10",
name: translate(name_translations),
pattern: nil,
pseudonymized: true,
validFrom: Time.now,
validTo: Time.now + 1.year,
version: "1",
responsibleDeputy: {
identifier: deputy.email,
name: deputy.external_directory_id
},
responsibleOrgUnit: {
identifier: organisation.code,
name: organisation.external_reference
},
responsiblePerson: {
identifier: responsible.email,
name: responsible.external_directory_id
}
}
end
def self.serialize(object)
new(object).serialize
end
private
# should probally be refactored to not cause an excessive amount of queries
def translate(relation)
LANG_MAPPING.dup.transform_values do |lang|
relation.where(language: lang).take!
end
end
end
ActiveModel::Serializers is also an option.
I am using Rails 6 API and React. I'm trying to build a Rich Text Editor with ActionText. When I send the RTE content from the Trix editor on the front end, it just doesn't set the ActionText body to the body I sent through with Axios.
I am sure that the body has come correctly to the controller because I used byebug and printed out the param value.
For example, it looked like this: <div><!--block-->test</div>
But whenever I try to view what it actually is by running announcement.details.to_s it returns " " for some reason.
I set the details field like this: has_rich_text :details in the Announcement model.
My controller which handles this looks like this:
module V1
class AnnouncementsController < ApplicationController
def create
announcement = Announcement.new(announcement_params)
announcement.author = #current_user
authorize announcement
if announcement.valid? && announcement.save
render json: { message: "Announcement successfully created! You can view it here." }, status: 201
else
render json: { messages: announcement.errors.full_messages }, status: 400
end
end
private
def announcement_params
params.require(:announcement).permit(:title, :details)
end
end
end
If it helps in any way, this is the React code:
const RTE = (props) => {
let trixInput = React.createRef()
useEffect(() => {
trixInput.current.addEventListener("trix-change", event => {
console.log("fired")
props.onChange(event.target.innerHTML)
})
}, [])
return (
<div>
<input
type="hidden"
id="trix"
value={props.value}
/>
<trix-editor
input="trix"
data-direct-upload-url={`${bURL}/rails/active_storage/direct_uploads`}
data-blob-url-template={`${bURL}/rails/active_storage/blobs/:signed_id/*filename`}
ref={trixInput}
className="trix-content"
></trix-editor>
</div>
);
}
And then I just normally pass it with Axios:
axios.post(`${bURL}/v1/announcements/create`, {
"announcement": {
"title": title,
"details": value
}
}, {
headers: {
'Authorization': `token goes here`
}
}).then(res => {
// success
}).catch(err => {
// error
})
If you need any more code snippets or information please comment.
I've inherited a static website, with a typical contact form on the main page which takes a name, email, phone and message and should send an email to a set email address on submit. The JS and Slim partial below were given to me. I'm now trying to hook up to Rails using ActionMailer, but I'm doing something wrong with the form params and/or building of the email object. I'm not looking to store it in the database (tried with and without a model).
Routes:
resources :contacts, only: [:new, :create]
_contact_form.slim
(all I've changed is the action value)
form#contactform.form-horizontal action="/contacts" method="post" name="contactform" role="form"
/! Field 1
.row
.col-md-6
h3 name:
.input-text.form-group.left-field
input.input-name.form-control name="contact_name" type="text" /
.col-md-6
h3 Phone:
.input-text.form-group
input.input-name.form-control name="contact_phone" type="text" /
/! Field 2
h3 Email:
.input-email.form-group
input.input-email.form-control name="contact_email" type="email" /
/! Field 3
h3 Message:
.textarea-message.form-group
textarea.textarea-message.form-control name="contact_message" rows="7"
/! Button
button.btn.btn-default type="submit"
| Submit
i.flaticon-arrow209
Javascript:
(I've set the url and added the beforeSend)
/* --------------------------------------------
Contact Form
-------------------------------------------- */
$(function($) {
$('#contactform').bootstrapValidator({
message: '',
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
contact_name: {
validators: {
notEmpty: {
message: ''
}
}
},
contact_email: {
validators: {
notEmpty: {
message: ''
},
emailAddress: {
message: ''
}
}
},
contact_message: {
validators: {
notEmpty: {
message: ''
}
}
}
},
submitHandler: function(validator, form, submitButton) {
$('.contact-form').addClass('ajax-loader');
var data = $('#contactform').serialize();
$.ajax({
type: "POST",
url: "/contacts",
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: $('#contactform').serialize(),
success: function(msg) {
$('.contact-form').removeClass('ajax-loader');
$('.form-message').html(msg);
$('.form-message').show();
submitButton.removeAttr("disabled");
resetForm($('#contactform'));
},
error: function(msg) {
$('.contact-form').removeClass('ajax-loader');
$('.form-message').html(msg);
$('.form-message').show();
submitButton.removeAttr("disabled");
resetForm($('#contactform'));
}
});
return false;
},
});
function resetForm($form) {
$form.find('input:text, input:password, input, input:file, select, textarea').val('');
$form.find('input:radio, input:checkbox').removeAttr('checked').removeAttr('selected');
}
});
contact.rb
class Contact
include ActiveModel::Model
include ActiveModel::Conversion
include ActiveModel::Validations
attr_accessor :contact_name, :contact_phone, :contact_email, :contact_message
validates :contact_name, :contact_email, :contact_message, presence: true
end
contacts_controller.rb:
class ContactsController < ApplicationController
def create
#contact = Contact.new(contact_params)
if #contact.valid?
ContactMailer.contact_email(#contact).deliver
redirect_to new_contact_path, notice: "Thank you for your message."
else
flash[:alert] = "An error occurred while delivering this message."
render :new
end
end
private
def contact_params
params.require(:contact).permit(:contact_name, :contact_phone, :contact_email, :contact_message)
end
end
contact_mailer.rb
class ContactMailer < ActionMailer::Base
default to: "me#myemailaddress.com"
def contact_email(contact)
#contact = contact
mail(from: email: #contact.contact_email, body: #contact.contact_message)
end
end
contact_email.text.slim
| New contact
= #contact.contact_name
= #contact.contact_email
| wrote:
= #contact.contact_message
Current output from Rails Server
I'm currently getting a syntax error in contact_mailer.rb on the #contact object, but prior to that the Rails server logs were telling me that the Contact object was uninitialized. I've tried including the form_for/form_tag Rails helpers but this hasn't seemed to help. I've also added an index action for the contact to the HomeController having read this suggestion elsewhere.
I can keep playing 'change the error message' but I imagine I'm getting something fundamental wrong (still a Rails newbie) so would really appreciate any pointers. I've read through the ActionMailer docs and everything else I can find.
You need to have .deliver after your contact_mailer.rb call don't you?
Also, in your contact.rb try adding
after_create :send_email
private
def send_email
ContactMailer.contact_email(self).deliver
end
I have a POST request from a javascript file below:
this.submitQuoteButton = $("<button />")
.text("Download PDF")
.addClass("submitQuoteButton button-success pure-button")
.click(function() {
$.ajax({
type: "POST",
url: "../quotes/create",
data: {
name : "John ",
email: "john#john.com",
json: "data",
uid: "uid",
},
dataType:'text',
success: function(data,status,xhr){
console.log(status);
alert("SUCCESS!");
},
error: function(xhr,status,error){
console.log(status,error);
alert("ERROR!");
}
});
})
This POST calls to my quotes_controller create method where I have this
def create
#quote = Quote.new(quote_params)
if #quote.save
redirect_to root_url
else
redirect_to blog_path
end
end
private
def quote_params
params.require(:quotes).permit(:uid, :name, :email, :json)
end
The aim is to get the data passed in the POST request and save it to my database with the create method. Am I doing this right? I am getting a:
param is missing or the value is empty for: quotes
Does this mean there is a problem with my database set up or the create method?
quote_params require the quotes key in your params. So your ajax call data should look like this:
data: {
quotes: {
name : "John ",
email: "john#john.com",
json: "data",
uid: "uid",
}
}