Adding a variable to URI - ruby

I'm pulling an integer from a JSON response, and then using that integer as the upper-limit in a loop:
data = response.parsed_response["meta"]
pages = data['total']
(1..pages.to_i).step(1) do |page_num|
response2 = client.get('/item?page=#{page_num}&per_page=1', :headers => {'Authorization' => auth, 'Content-Type' => 'application/json'})
However, I seem to be unable to pass the variable into the URI in a way that is usable with HTTParty:
/opt/chef/embedded/lib/ruby/1.9.1/uri/common.rb:176:in `split': bad URI(is not URI?): /items?page=#{page_num}&per_page=1 (URI::InvalidURIError)
I tried other methods ('/item?page=' + page_num + '&...', for example) that were, likewise unsuccessful. I'm not where what I'm overlooking, but is there another, more correct way of doing this.
The output is being returned as an integer, and is usable, but it seems not to be getting passed into the URI string above.
I came across this thread:
URI with variable
but I was unsure that this applied, and an attempt to adapt the solution was unsuccessful (undefined method `strip' for 10:Fixnum (NoMethodError))

Instead of:
'/item?page=#{page_num}&per_page=1'
Use:
"/item?page=#{page_num}&per_page=1"
Strings with single-quotation marks don't allow interpolation; thus to interpolate, you must use double-quotation marks strings.
And just FYI: When it comes to an integer range, you need not specify "step(1)" as it is the default value.

Related

Ruby string interpolation with substitution

I have a given method that adds keys to urls with:
url % {:key => key}
But for one url I need the key to be escaped with CGI.escape. I cannot change the method, I can only change the url, but substitution does not work:
"https://www.example.com?search=#{CGI.escape(%{key})}"
Is there a way to achieve this only by changing the url string? I cannot use additional variables or change the method, thus I cannot do the escaping in the method and send the escaped key to the url string.
It isn't clear how your given method is supposed to work. Can you give an example where the method works, and one where it doesn't? Ignoring the method part of your question, and focusing on the URL bit,
>> key = "Baby Yoda"
=> "Baby Yoda"
>> %{key}
=> "key"
is the expected result, regardless of whether you have a variable named key, set to any value. See: https://en.wikibooks.org/wiki/Ruby_Programming/Syntax/Literals#The_.25_Notation
Unless you have a method defined which overloads '%' to do something else special for URLs, but that isn't clear in your question.
If you just want to CGI escape the value of 'key' within your URL string, don't use the percent notation:
>> key = 'Baby Yoda'
=> "Baby Yoda"
>> "https://www.example.com?search=#{CGI.escape(key)}"
=> "https://www.example.com?search=Baby+Yoda"
It just seems not possible. I worked around by defining a syntax ${...}
"https://www.example.com?search=${CGI.escape(%{key})}"
Then I first do subtitution of %{key} and then use eval to do CGI.Escape (or any method for that matter) with
gsub(/\${(.+?)}/) { |e| eval($1) }

Ruby to_yaml stringifies my json

I am trying to convert a ruby hash to yaml. I'd like part of the hash be valid json; however, when I try to serialize the json string, it is converted to yaml in quotes.
For example, when I just have a simple string, the ouput is as follows (note foo is not in quotations):
request = {}
request['body'] = 'foo'
request.to_yaml # outputs: body: foo
However, when I add something to the beginning of the string, like { foo the output for body gets quoted:
request['body'] = '{ foo'
request.to_yaml # outputs: body: '{ foo'
How can I get around this? I've tried JSON.parse and, though that make work, I can't be guaranteed that this input will actually be json (could be xml, etc...) -- I just want to give back whatever was given to me but not "stringified".
Basically, I want to give an object that looks like:
{ 'request' => {
'url' => '/posts',
'method' => 'GET',
'headers' => [
'Content-Type' => 'application/json'
]
},
'response' => {
'code' => 200,
'body' => '[{"id":"ef4b3a","title":"this is the title"},{"id":"a98c4f","title":"title of the second post"}]'
}
}
Which returns:
request:
url: /posts
method: GET
headers:
- Content-Type: application/json
response:
code: 200
body:
[{"id":"ef4b3a","title":"this is the title"},{"id":"a98c4f","title":"title of the second post"}]
The reason being: right now, I can go from yaml to the correct ruby hash but I can't go the other way.
The method my_hash.to_yaml() just takes a hash and converts it to YAML without doing anything special to the values. The method does not care whether your string is JSON or XML, it just treats it as a string.
So why is my JSON being put into quotes when other strings aren't?
Good question! The reason is simple: curly braces are a valid part of YAML syntax.
This:
my_key: { sub: 1, keys: 2}
Is called flow mapping syntax in YAML, and it allows you make nested mappings in one line. To escape strings which have curly braces in them, YAML uses quotes:
my_key: "{ sub: 1, keys: 2}" # this is just a string
Of course, the quotes are optional for all strings:
my_key: "foo" #same as my_key: foo
Okay, but I want to_yaml() to find my JSON string and convert it to YAML mappings like the rest of the hash.
Well then, you need to convert your JSON string to a hash like the rest of your hash. to_yaml() converts a hash to YAML. It doesn't convert strings to YAML. The proper method for doing this is to use JSON.parse, as you mentioned:
request['body'] = JSON.parse( '{"id":"ef4b3a"}' )
But the string might not be JSON! It might be XML or some other smelly string.
This is exactly why to_yaml() doesn't convert strings. A wise programmer once told me: "Strings are strings. Strings are not data structures. Strings are strings."
If you want to convert a string into a data structure, you need to validate it and parse it. Because there's no guarantee that a string will be valid, it's your responsibility as a programmer to determine whether your data is JSON or XML or just bad, and to decide how you want to respond to each bit of data.
Since it looks like you're parsing web pages, you might want to consider using the same bit of data other web clients use to parse these things:
{ 'request' => {
'url' => '/posts',
'method' => 'GET',
'headers' => [
'Content-Type' => 'application/json' #<== this guy, right here!
]
},
'response' => {
'code' => 200,
'body' => '[{"id":"ef4b3a","title":"this is the title"},{"id":"a98c4f","title":"title of the second post"}]'
}
}
If the content-type doesn't agree with the body then you should throw an error because your input data is bad.
The reason '{ foo' requires quote is because this is part of the YAML specification 7.3.3 Plain Style.
Excerpt
Plain scalars must never contain the “: ” and “#” character combinations. Such combinations would cause ambiguity with mapping key: value pairs and comments. In addition, inside flow collections, or when used as implicit keys, plain scalars must
not contain the “[”, “]”, “{”, “}” and “,” characters. These characters would cause ambiguity with flow collection structures.
Based on the above even your stated "return" value is incorrect and the body is probably enclosed in single quotes e.g.
response:
code: 200
body: '[{"id":"ef4b3a","title":"this is the title"},{"id":"a98c4f","title":"title of the second post"}]'
Otherwise it would create ambiguity with "Flow Sequences" ([,]) and "Flow Mappings" ({,}).
If you would like result of the JSON, XML or other notation language to be represented appropriately (read objectively) then you will need to determine the correct parser (may be from the "Content-Type") and parse it before converting it YAML

Chef attributes "no implicit conversion of String into Integer"

I'm writing a chef recipe which simply creates a database config file, but I'm stumped simply access the attributes. I have a few PHP applications being deployed to each instance, and OpsWorks uses the same recipes for everyone, so I have a few different settings in the attributes file.
attributes/database-settings.rb
# API
default[:api][:path] = 'app/config/database.php';
default[:api][:host] = 'test';
default[:api][:database] = 'test';
default[:api][:username] = 'test';
default[:api][:password] = 'test';
recipes/database-settings.rb
Chef::Log.info("Database settings!");
node[:deploy].each do |application, deploy|
if node.has_key?(application)
Chef::Log.info("Application: #{application}");
path = node["api"]["path"]; # ERROR HAPPENING HERE
Chef::Log.info("Path: #{path}");
template path do
source "database.erb"
mode 0440
variables({
:host => node["api"]["host"],
:database => node["api"]["database"],
:username => node["api"]["username"],
:password => node["api"]["password"]
})
end
end
end
The error I'm getting is no implicit conversion of String into Integer. I've tried creating and accessing the settings in every way I can think of, such as...
node[:api][:path] # no implicit conversion of Symbol into Integer
node['api']['path'] # no implicit conversion of String into Integer
node[:api].path # undefined method `path' for #<Chef::Node::ImmutableArray:0x007fa4a71086e8>
node[application][:path] # no implicit conversion of Symbol into Integer
I'm sure there's something very obvious I'm doing wrong here, but I've tried everything I can think of an I just can't seem to find any way of getting this to work?! Ideally I'd like to use a variable where I can "api", but using an if/else wouldn't be too terrible for 3 apps...
That is a common error seen when you try to access an object thinking it is a hash, but is actually an array. In fact, from one of your errors, it can be read that node["api"] is a Chef::Node::ImmutableArray.
Ok so the problem wasn't really that I was accessing the config wrongly, it was that the different attribute files were all being merged into a single config and I didn't realise this.
I had these config files...
attributes/database_settings.rb
default[:api][:path] = 'app/config/database.php';
default[:api][:username] = 'example';
attributes/writable_directories.rb
default[:api] = ['public/uploads', 'storage/cache'];
When I tried to access default[:api][:path] I was actually accessing the array of directories when seemed to override the database settings attributes. Moving these into default[:directories][:api] and default[:database][:api][:path] etc fixed this.
Note that you will also get this error if you accidentally enter a space between "node" and the items indexing it:
node[:foo][:bar]
will work, while
node [:foo][:bar]
will throw this exception. It can be hard to spot.

Stuck with HTTP GET authentication in Ruby

I am trying to write short script to do HTTP authentication using GET request
This makes GET request.
def try_login(u, p)
path1 = '/index.php'
path2 = '?uuser=#{myuser}&ppass=#{mypass}'
r = send_request_raw({
'URI' => "#{path1}#{path2}",
'method' => 'GET'
})
...continued...
But this code does not work because error says:
undefined local variable or method `myuser'
--> Basically I am trying to send one (1) GET request with login parameters, and the app responds with a specific data. And I do not know how to put placeholders for user and pass in this GET request.
...
Next, I am checking the HTTP response. Response comes in as JSON mime like this:
Success response
{"param1":1,"param2"="Auth Success","menu":0,"userdesc":"My User","user":"uuser","pass":"ppass","check":"success"}
Fail response
{"param1":-1,"param2"="Auth Fail","check":"fail"}
--> How can I check the response body for this kind of data.
I have been trying all day now, but stuck totally. Please advice.
Edit:
I do not understand why some one down voted this question saying little to no research on my part. Until before yesterday morning, I had absolutely zero idea about ruby code & working with it. And then I spent numerous hours looking at many different examples, making my script and testing it out. When it still didn't work, I asked my question here. Please, if you still want to down vote, do it but please, at least share some pointers to solve this as well.
def try_login(u, p)
path1 = '/index.php'
path2 = '?uuser=#{myuser}&ppass=#{mypass}'
r = send_request_raw({
'URI' => "#{path1}#{path2}",
'method' => 'GET'
})
...continued...
Should be:
def try_login(u, p)
path1 = '/index.php'
path2 = "?uuser=#{u}&ppass=#{p}"
r = send_request_raw({
'URI' => "#{path1}#{path2}",
'method' => 'GET'
})
...continued...
For parsing JSON in Ruby, I would recommend you take a look at this answer to another question.
Edit: The reason try_login(u, p) isn't working as you would expect is because Ruby does not do string interpolation for single quoted (') strings. Additionally, myuser and mypass do not appear to be the correct variables.

Is it possible to pass a JavaScript Object as an argument to the Locals Hash in a render: partial call?

I think this is easily explained by looking at code, so I'm posting a couple of snippets that show how I usually do Ajax things in Rails 3.2,
The following will work:
create.js.coffee
$("<%= raw j(render(partial: #comment, locals: { str: 'string' })) %>")
.appendTo("ul#comments")
_comment.html.haml
-puts "The variable's value is #{str}"
%li= comment.what
Now, I'd like to do the same as above, but instead of passing a JS string literal, I'd like to pass an object literal, like this:
create.js.coffee
obj =
'v1': 'value'
'v2': 'value'
$("<%= raw j(render(partial: #comment, locals: { o: obj })) %>")
.appendTo("ul#comments")
Which of course fails with the following reason:
ActionView::Template::Error (undefined local variable or method `obj'...
Does anybody know how to accomplish what I'm trying to do? Is it possible at all?
Let me know if what I'm trying to do is insane, and what would be the proper way if that's the case..
Thanks!
Well I guess clients objects are not shared with server object, unless untill we are doing remoting. So in this case you have to serialize JS object into JSON or any other string format (XML or any other structure) and then you have to de-serialize in Ruby object or you can manually parse the string and use the information provided.

Resources