undefined method `symbolize_keys!' for #<Array rails 4 - ruby-on-rails

In mcs_mailer.rb:
def invite(email,subject,body,attachment_urls)
#attachment_urls= attachment_urls
mandrill_mail(
template: 'group-invite',
subject: subject,
to: email,
html: body,
attachments: [ #attachment_urls.each do |url|
{
content: File.read(url),
name: 'offer.pdf',
type: 'application/pdf'
}
end
])
I am using mandrill_mail in my rails application.I want to send mail with multiple attachments. But getting error in the each loop undefined methodsymbolize_keys!' for #

Try
# [...]
attachments: #attachment_urls.map do |url|
{
# [...]
}
end
Use map to return an array of each block return value.
You get this error because it tries to symbolize_keys! on the first value of :attachments array, which is also an array (Array#each returns the object itself when a block is provided).

Related

Validation incoming parameters (in: :body) in rswag specs (Rails)

I have spent a lot of time trying to emplement ability of validation incoming params is rswag specs, my code:
# incoming-parameter
params = {
login: 'www',
id: 15
}
# test rswag-spec
path '/controller/hello' do
post('Say Hello!') do
tags 'users'
consumes 'application/json'
produces 'application/json'
parameter name: :my_params, in: :body, schema: {
type: :object,
required: %i[id name],
properties: {
id: { type: :string },
name: { type: :string }
}
}
response(200, 'successful') do
# schema '$ref' => '#/components/schemas/UserRegistrationResponse'
describe 'new user with valid reg_params' do
let(:my_params) { params }
run_test! do |response|
data = JSON.parse(response.body)
puts "data = #{data}"
end
end
end
end
end
You expecting that incoming params won't pass validation, because id - is an integer, and name field is absent. But that's doesn't matter and test is compliting with success.
Can you say what's wrong with my code an why don't work validation of incoming parameters those declarated in rswag docs?

cause of JSON::ParserError (784: unexpected token at '{

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.

How to build complex json to POST to a web service with Rails 5.2 and Faraday gem?

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.

How to write if statement in Rails 4 as_json method?

I'm using Rails 4.I'm creating API databse where users can sign up from Facebook Graph API.
If user has no profile picture then the image_url is null.
After reading answers in SO I thought this is the correct way how to build custom json for my response.
I have created method as_json to render response when user is created with only parameters who should get returned.
This is the method how I'm creating json response:
def as_json(options={}){
id: self.id,
first_name: self.first_name,
last_name: self.last_name,
auth_token: self.auth_token,
image: {
thumb: "http://domain.com" + self.profile_image.thumb.url
}
}
end
This method above gives me an error: no implicit conversion of nil into String.
I need to give absolute image url path if the image exists in my db, but i don't need to give this parameter in response if image url is null in my database.
How can I write if statement inside this as_json method?
I've tried this, but it doesn't work.
def as_json(options={}){
id: self.id,
first_name: self.first_name,
last_name: self.last_name,
auth_token: self.auth_token,
if !self.profile_image.thumb.url == nil
image: {
thumb: "http://domain.com" + self.profile_image.thumb.url
}
end
}
end
With the help from Jorge de los Santos I've managed to make it pass no implicit conversion of nil into String error with this code:
def as_json(options={})
response = { id: self.id,
first_name: self.first_name,
last_name: self.last_name,
auth_token: self.auth_token }
if !self.profile_image.thumb.url == nil
image = "http://domain.com" + self.profile_image.thumb.url
response.merge(image: {thumb: image })
end
response
end
But now all the users are returned without image parameter even when he has a image url.
Your code seems fine except when you try to merge image key, merge function is not working as you expect, check the followingt to understand:
hash = {a: 1, b:2 }
hash.merge(b: 3)
puts hash #{a: 1, b:2 }
hash = hash.merge(b: 3)
puts hash #{a: 1, b:2, c: 3 }
so you will need to modify your code by changing this line:
response.merge(image: {thumb: image })
to
response = response.merge(image: {thumb: image })
Using Jbuilder
When you are building a complex json object, it's better to use jbuilder, I'll assume the model is called 'User'
Create a template called show.json.jbuilder
json.id #user.id
json.first_name #user.first_name
json.last_name #user.last_name
json.auth_token #user.auth_token
unless #user.profile_image.thumb.url.nil?
json.image do |image|
image.thumb "http://domain.com#{#user.profile_image.thumb.url}"
end
end
I would recommend creating a helper for the image url, so we could for example call something like
json.image #user.full_profile_image_url
Using as_json
As for your own method (using as_json) you could create a method that returns the full image hash
class User < ActiveRecord::Base
def image
{ thumb: "http://domain.com#{profile_image.thumb.url}" }
end
end
Then in the as json call the method
#user.to_json(
only: %i(id first_name last_name auth_key),
methods: :image
)
This will call the image method and set it's value inside a key called 'image'
You can't use logic inside a hash key, you can declare the variable before returning the hash, or you can use the full statement inside the value of the hash. But I think this is more readable.
def as_json(options={})
response = { id: self.id,
first_name: self.first_name,
last_name: self.last_name,
auth_token: self.auth_token }
image = "http://domain.com" + self.profile_image.thumb.url
response.merge!({image: {thumb: image }}) unless self.profile_image.thumb.url
response
end

calling a method with signature

I have a method with a signautre in rails:
def my_function(some_variable)
end
I call the method from jquery get function like this:
$.get('/controller/my_function', {data: mydata}, function(){
});
But I get an error because I need to send the argument also.
How can I do that?
You need to define an action in your controller and call your function from that action
def my_action
my_function(params[:data])
end
and your jquery script will be calling my_action
$.get('/controller/my_action', {data: mydata}, function(){
});
as #Henry pointed out - in your javascript code data: xxxx is HTTP parameters being sent from the browser to your rails controller action on the server, rails puts all HTTP parameters into the params hash, so if you had
var data = {
first_name: "Joe",
last_name: "Smith"
}
$.get('/controller/some_action', data, function() {
// ...
access those in the params hash on the server
def some_action
logger.debug params.inspect
# => { :first_name => "Joe", :last_name => "Smith", :action => "some_action" }
user.first_name = params[:first_name]
# ...
end

Resources