I am rendering a response to a POST request from a webhook. I just realized that when I render json: thingee and I log thingee it has hash rockets which are not valid json.
I've seen where people puts the hash and it looks fine, but that's not what I'm doing, I'm rendering a hash as JSON in response to a POST..
When rendered, my hash looks like this:
{"rates"=>[{"service_name"=>"Standard", "service_code"=>"f48",
"total_price"=>"390",},{"service_name"=>"Expedited", "service_code"=>"f34",
"total_price"=>"640"}]}
But I need it to be valid JSON and look like this:
{"rates":[{"service_name":"Standard", "service_code":"f48",
"total_price":"390",},{"service_name":"Expedited", "service_code":"f34",
"total_price":"640"}]}
Thanks
Don't worry so much. Its perfectly fine.
In Ruby the hashrocket syntax needs to be used whenever you want to have a hash key that is not a symbol:
irb(main):002:0> { foo: 1, 'baz': 2 }
=> {:foo=>1, :baz=>2} # coerces 'baz' into a symbol
irb(main):003:0> { foo: 1, 'baz' => 2 }
=> {:foo=>1, "baz"=>2}
However when you pass the hash render: { json: #foo } the hash is passed to JSON.generate which converts the ruby hash to valid JSON.
irb(main):006:0> JSON.generate({ "foo" => 1, "baz" => 2 })
=> "{\"foo\":1,\"baz\":2}"
irb(main):007:0> JSON.generate({ foo: 1, baz: 2 })
=> "{\"foo\":1,\"baz\":2}"
irb(main):008:0> { foo: 1, baz: 2 }.to_json
=> "{\"foo\":1,\"baz\":2}"
Related
The docs say this:
{ :name => "Konata Izumi", 'age' => 16, 1 => 2 }.to_json
=> {"name": "Konata Izumi", "1": 2, "age": 16}
But when I actually try it in the rails console it looks like this:
{ :name => "Konata Izumi", 'age' => 16, 1 => 2 }.to_json
=> "{\"name\":\"Konata Izumi\",\"age\":16,\"1\":2}"
How do I get it to return an object/hash instead of a string? I don't want the outer quotes around the curly braces. I want {...} instead of "{...}".
irb(main):012:0> x = { :name => "Konata Izumi", 'age' => 16, 1 => 2 }.to_json
irb(main):013:0> puts x
{"name":"Konata Izumi","age":16,"1":2}
=> nil
If you capture the results in a variable and then print it, you'll see that it is working as expected. The quotes are just the console's way of indicating that the stuff after => is a string.
JSON is a format for serializing data to text. It simply is a String, it cannot be anything else, that would be non-sensical.
If you want to keep a Ruby object instead of serializing to a string, simply don't serialize to a string.
I have a nested hash with unsorted keys:
given = {
"lorem" => {
:AA => "foo",
:GR => "foo",
:BB => "foo"
},
"ipsum" => {
:ZZ => "foo",
:GR => "foo",
}
}
What I'm trying to accomplish is a hash with sorted keys:
goal = {
"ipsum" => {
:GR => "foo",
:ZZ => "foo"
},
"lorem" => {
:AA => "foo",
:BB => "foo",
:GR => "foo"
}
}
I have experimented with .each method and sort_by
given.each { |topic| topic[:key].sort_by { |k, v| k } }
But I'm getting an error message: TypeError: no implicit conversion of Symbol into Integer
Any help is greatly appreciated!
PS: I noticed with gem pry the output is already sorted. But in IRB it's not.
You can use group_by, and transform_values to transform the values inside each hash, also using sort_by plus to_h:
given.transform_values { |value| value.sort.to_h }.sort.to_h
# {"ipsum"=>{:GR=>"foo", :ZZ=>"foo"}, "lorem"=>{:AA=>"foo", :BB=>"foo", :GR=>"foo"}}
You're getting an error because when iterating over a hash, you have to local variables within the block scope to use, the key and its value, you're assigning only one (topic) and trying to get its key, which would be trying to access a key in:
["lorem", {:AA=>"foo", :GR=>"foo", :BB=>"foo"}]
Which isn't possible as is an array. You can update your code to:
given.each do |topic, value|
...
end
But anyway you'll need a way to store the changes or updated and sorted version of that topic values.
given_hash = {"lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}, "ipsum"=>{:ZZ=>"foo", :GR=>"foo"}}
Get keys
given_hash.keys
=> ["lorem", "ipsum"]
New sorted hash
new_hash = {}
given_hash.keys.sort.each do |sorted_key|
new_hash[sorted_key] = given[sorted_key]
end
=> {"ipsum"=>{:ZZ=>"foo", :GR=>"foo"}, "lorem"=>{:AA=>"foo", :GR=>"foo", :BB=>"foo"}}
There can be a better way to do this.
I have the following JSON:
{
my_json: {
id: 1,
name: "John"
}
}
How can I customize key name via parameterized like:
def jsonize(custom_key="id")
{
my_json: {
"#{custom_key}": 1,
name: "John"
}
}
end
To be output with:
Scenario 1:
=> jsonize
OUTPUT:
{
my_json: {
id: 1,
name: "John"
}
}
Scenario 2:
=> jsonize("value")
OUTPUT:
{
my_json: {
value: 1,
name: "John"
}
}
You can use ":" to separate symbolic keys and values, use "=>" in your example:
def jsonize(custom_key="id")
{
my_json: {
"#{custom_key}" => 1,
name: "John"
}
}
end
The hash-rocket syntax has been in Ruby since ancient times:
{ :foo => 1, "bar" => 2 }
Ruby 1.9 (I think) introduced a new colon shortcut syntax just for symbols (while keeping the hash-rocket general for any key type):
{ foo: 1, "bar" => 2 }
Ruby 2.2 (I think) introduced the possibility of symbolizing a string in this syntax:
{ "foo": 1, "bar" => 2 }
All of these do the same thing. What you are doing is perfectly grammatical Ruby code -- in a sufficiently new Ruby. In older Rubies, you will need to use the old reliable hash-rocket syntax:
{ "foo".to_sym => 1, "bar" => 2 }
Now that you actually have a string, you can do normal interpolation:
{ "f#{'o' * 2}".to_sym => 1, "bar" => 2 }
In your case, you could write
{ "#{custom_key}".to_sym => 1 }
However, all of this is completely unnecessary, since you can just write simply this, in any Ruby:
{ custom_key.to_sym => 1 }
Even better, since you're just turning everything into JSON immediately after, you don't even need symbolised keys; so these two expressions will have identical results:
{ custom_key.to_sym => 1 }.to_json
{ custom_key => 1 }.to_json
(Also note that what you state as examples of JSON -- both input and output -- are, in fact, not JSON, nor would .to_json output such. In JSON, as opposed to plain JavaScript object literal, keys must be double-quoted, and that is how to_json would produce it. Your input is a Ruby valid Ruby hash, though.)
You can just convert it to symbol and use hash_rocket syntax you will get the expected result
def jsonize(custom_key = "id")
{
my_json: {
custom_key.to_sym => 1,
name: "John"
}
}
end
#=> jsonize('foo')
#=> {
#=> :my_json => {
#=> :foo => 1,
#=> :name => "John"
#=> }
#=> }
I'm trying to create a workout routine that gets created when a workout gets created by passing this in via ajax:
Parameters: {"utf8"=>"✓", "workout"=>{"name"=>"cool workout", "desc"=>"super cool"}, "exerciseorder"=>["4", "2"], "repssets"=>{"0"=>["40", "4"], "1"=>["60", "6"]}}
Here is what my Create action looks like in my Workout Controller:
exercise_order = params[:exerciseorder]
repssets = params[:repssets]
#workout = Workout.new(workout_params)
if #workout.save
WorkoutRoutine.create(
[
exercise_order.each_with_index.map { |x,i|
{
:exercise_id => x,
:position => i,
:workout_id => #workout.id
}
},
repssets.map { |x| x.last }.each { |y|
{
:reps => y.first,
:sets => y.last
}
}
]
)
render :nothing => true
else
render json: #workout.errors.full_messages, status: :unprocessable_entity
end
If I use an opening and closing '[]' within the WorkoutRoutine.create, it tells me:
ArgumentError: When assigning attributes, you must pass a hash as an argument.
And when I change them to '{}' it tells me:
syntax error, unexpected ',', expecting =>
I've tried a myriad of different combinations and work-arounds but can't seem to figure out why it won't correctly parse the data and save it to the database, any help is very appreciated.
EDIT:
When I remove the initial {} and [] from the WorkoutRoutine.create:
WorkoutRoutine.create(
exercise_order.each_with_index.map { |x,i|
{
:exercise_id => x,
:position => i,
:workout_id => 20
}
},
repssets.map { |x| x.last }.each { |y|
{
:reps => y.first,
:sets => y.last
}
}
)
I get this error message:
ArgumentError: wrong number of arguments (2 for 0..1)
Edit2:
This is the jQuery code that sents to the data field via ajax:
var getId = $(".exercise-list").sortable('toArray');
ary = []
$(getId).each(function () {
id = $(this[0]).selector;
var reps = $("#reps" + id).val();
var sets = $("#sets" + id).val();
ary.push([reps, sets])
});
var orderRepsSets = { exerciseorder: getId, repssets: ary }
var formData = $('#new_workout').serialize();
var data = formData + '&' + $.param(orderRepsSets);
$.ajax({
url: $("#new_workout").attr('action'),
method: 'POST',
data: data,
success: (function() {
....
});
Did I get it correctly that you want to create multiple WorkloadRoutine objects, one for each exercise with the corresponding repetitions, the position, etc. If yes, then in order to do that you will have to pass an array of hashes (one hash for each object) to the WorkoutRoutine.create() function. As engineersmnky correctly stated in his comment, the data structure you are currently passing is more like [[{...},{...},{...}],[{...},{...},{...}]], but instead it should be just [{...},{...},...]. Do achieve that, something like this should do the trick:
WorkoutRoutine.create(
exercise_order.map.with_index { |x,i|
{
:exercise_id => x,
:position => i,
:workout_id => #workout.id,
:reps => repssets[i.to_s].first,
:sets => repssets[i.to_s].last
}
}
)
If you could change repssets to an array like exercise_order you could even remove the string cast for getting the reps and sets, which would simplify the whole think even more.
If it comes for errors they are quite self explanatory. But let's start from beginning..
I assume that WorkoutRoutine is an ActiveRecord::Base model. The WorkoutRoutine.create method gets 0 or 1 argument which should be a Hash or a block.
In the first iteration you were passing an Array instead of Hash, so it looked like:
WorkoutRoutine.create([a, b]) # => ArgumentError: When assigning attributes, you must pass a hash as an argument.
On the second iteration you stripped away the square brackets, what gave you 2 arguments instead of one Hash:
WorkoutRoutine.create(a, b) # => ArgumentError: wrong number of arguments (2 for 0..1)
If you read errors carefully you will start getting the idea what's happening.
About the workout routine itself
From what you specified I would assume that you want something like:
Workout has many Routines
Routine belongs to Workout and Exercise
Routine is composed of fields like
position/order,
number of repetitions,
number of sets
If my assumption is correct, then you want to use nested_attributes and then have parameters and controller like
# given parameters as workout_params
{
workout: {
name: "workout name",
desc: "workout description",
routines_attributes: [
{ position: 1, exercise_id: 4, repetitions_number: 40, sets_number: 4 },
{ position: 2, exercise_id: 2, repetitions_number: 60, sets_number: 6 }
]
}
}
# Controller
def create
#workout = Workout.new workout_params
if #workout.save
redirect_to # ...
else
render :new
end
end
private
def workout_params
params.require(:workout).permit(:name, :desc, routines_attributes: [:position, :exercise_id, :repetitions_number, :sets_number])
end
It should be strait forward how to then create a view with fields_for and update models with proper associations
I have the method:
def self.store(params)
params.each { }
end
It works perfectly, if I pass an Array of Hashes:
params = [ { key: 'value' }, { key: 'value' } ]
However, I might want to pass only a single Hash, instead of an Array of Hashes:
params = { key: 'value' }
What would be the cleanest Ruby way to convert a Hash into an Array of Hashes?
The Array() method a kind of ensures, that an array is always returned, but when the Hash is passed, it is converted into an Array itself.
Array({ key: 'value' }) => [[:key, 'value']]
What I need:
{ key: 'value' } => [ { key: 'value' } ]
Is there any nice way to implement this, or do I have to do a manual type checking with is_a?(Array) ?
For me, the best solution is to change the method to:
def self.store(*hashes)
params = hashes.flatten
puts params.inspect
end
If you pass a single hash, it will be an array
If you pass an array of hashes, it remains the same
If you pases N hashes, it compacts all parameters into a one dimensional array.
You can pass whatever you want.
self.store({:key => 'value'}) # => [{:key => 'value'}]
self.store({:key => 'value'}, {:foo => 'bar'}) # => [{:key => 'value'}, {:foo => 'bar'}]
self.store([{:key => 'value'}, {:foo => 'bar'}]) # => [{:key => 'value'}, {:foo => 'bar'}]
Try this
def self.store(params)
params = [params].flatten
...
end
I would do it like this:
def self.store(params)
(params.is_a?(Array) ? params : [params]).each {|single_hash| }
end