Where did this Ruby parameter convention come from? - ruby

There's a piece of Ruby middleware used by Rails and other frameworks to the parse the parameters you've sent to the server into a nested Hash object.
If you send these parameters to the server:
person[id] = 1
person[name] = Joe Blow
person[email] = joe.blow#test.com
person[address][street_address] = 123 Somewhere St.
person[address][city] = Chicago
person[address][zip] = 12345
person[other_field][] = 1
person[other_field][] = 2
person[other_field][] = 3
They get parsed to this:
{
:person => {
:id => "1",
:name => "Joe Blow",
:email => "joe.blow#test.com",
:address => {
:street_address => "123 Somewhere St.",
:city => "Chicago",
:state => "IL",
:zip => "12345"
},
:other_field => [ 1, 2, 3 ]
}
}
I believe this is supported by PHP as well. Can anybody tell me what this convention is called, where it came from, and what other languages support it? (Perl, Python, etc.)

Field research
I'm trying to find out if there's a name for this convention, but I can't find it yet.
Ruby
For what it's worth, the piece of middleware that does this in Ruby is Rack::Utils. See the source on Github.
There is some more information on the subject in the Ruby on Rails Guides.
And here is an interesting ticket about the code being moved from Rails to the Rack middleware.
PHP
I've done some digging in the PHP source, and it seems that all the magic there happens in the main/php_variables.c source file. The SAPI code calls the php_std_post_handler method defined here. This eventually calls the php_register_variable_ex method, which is 185 lines of complex string-parsing in C (I must admit that C isn't my forte).
The only name I could find here was the php_std_post_handler, the PHP standard POST handler.
Python
In Python, this format isn't supported by default. See this question here on stackoverflow.com on the subject.
Perl
The CGI library in Perl doesn't support this format either. It does give easy access to single or multiple values, but not nested values as in your example. See the documentation on feching the value or values of a single named parameter and fetching the parameter list as a hash.
Java
Check out the heated debate on the subject of query parameter parsing in this question. Java doesn't parse this 'nested format' of POST parameters into a data structure by default.
Conclusion
I've looked into this, and haven't found a single name for this way of parameter parsing. Of the languages that I've looked into, only Ruby and PHP support this format natively.

It's not called anything, AFAIK, other than "sending parameters". If anything, it's called "parameter [type] conversion", where Rails just "converts" it into a hash.
Other frameworks go further, using the parameter names as expressions used to create typed objects initialized with type-converted parameter values.
All parameters are is a string value with a name. Any/all structure is imposed by the language/framework in use on the server side; what it gets transformed to is 100% dependent on that language/framework, and what that conversion consists of would determine what it would be (reasonable) called.

that would be a JSON object, which is quite standard and supported by most languages/libraries these days.

Related

What options does the asana api expect in tasks.find_by_id

I'm using the asana gem to access the asana api.
The client documentation for the class method find_by_id exposed on the tasks resource (i.e. Asana::Task) says that it will take a hash of options. As far as I can tell looking at the little code snippet, it should be the same options as are listed on https://asana.com/developers/documentation/getting-started/input-output-options#paths
However, when I do client.tasks.find_by_id(123456, :fields => "this.assignee.email"), for example, I get an ArgumentError: unknown keyword: fields.
What am I doing wrong? How should this work?
Also: it's unclear to me from the above page when I should be using this in my field specifications and when it is unnecessary.
EDIT: SOLVED!
The correct syntax is client.tasks.find_by_id(123456, :options => { :fields => "this.assignee.email" })
Both :fields and "fields" work.
Judging from the code in the ruby client library: https://github.com/Asana/ruby-asana/blob/423f76c14792bd4712c099161a14a10ce941b2d9/lib/asana/http_client.rb#L42
Perhaps something like client.tasks.find_by_id(123456, {"fields" => "this.assignee.email"}) might work. Could you try that?

How do I fake uploading a file when testing Ruby Rack without the server?

For testing, I send a Rack::Request straight to the app, not using the server.
def request_via_API( app, method, path, params={} ) # app should be API
env = Rack::MockRequest.env_for( path, {:method => method, :params=>params} )
app.handle Rack::Request.new(env)
end
works great for testing direct input, but I'm stymied by file upload. My real system works great from the browser with a file upload. But now I want to test it via the API, and don't know how to get the file contents into the request via any of the Rack classes/methods. (I tried making sense of Rack::Test::UploadedFile but didn't succeed).
Thanks, Alistair
You were definitely on the right path. You can even use your function request_via_API without any modifications, e.g.:
request_via_API(app, 'POST', '/', {
:field => "value",
:text_source => Rack::Multipart::UploadedFile.new(PATH_TO_YOUR_FILE, MIME_TYPE_OF_YOUR_FILE)
})
This means you need to have some file somewhere. If you use fixtures, your test upload file should be around them. You can omit MIME time, but it defaults to text/plain.
If you use barebones Rack, you get the following hash after calling Rack::Multipart.parse_multipart:
{
"field" => "value",
"text_source" => {
:filename => File.basename(PATH_TO_YOUR_FILE),
:type => MIME_TYPE_OF_YOUR_FILE,
:name => "text_source",
:tempfile => Tempfile.new("RackMultipart"), # copied from PATH_TO_YOUR_FILE
:head => "Content-Disposition: form-data; name=\"text_source\"; filename=\"#{File.basename(PATH_TO_YOUR_FILE)}\"\r\n" +
"Content-Type: #{MIME_TYPE_OF_YOUR_FILE}\r\n" +
"Content-Length: #{BYTESIZE_OF_YOUR_FILE}\r\n"
}
}
The text_source key can have any other name, of course.
Rack::MockRequest#env_for automatically tries to create multipart form data request if:
HTTP method is not GET
there is no :input option provided
:params option is a Hash
:params option values contain at least one instance of Rack::Multipart::UploadedFile
You can see the details in the source code here and here.
I think relying on multipart request generation by Rack::MockRequest and Rack::Multipart is useful only for mocking HTML forms with file upload and file upload mechanisms that act the same. So, there's no need to use Rack::Multipart#build_multipart or Rack::Multipart::Generator directly.
If you have more complicated multipart scenarios or different file upload mechanism, you must pass opts argument with :input key instead of :params to Rack::MockRequest#env_for. How you generate that value for :input is your problem as far as Rack mocking capabilities are concerned. It only wraps it in StringIO if it is a String as you can see here. Otherwise, it is the same thing that will be passed as rack.input in the Rack environment hash and therefore it must conform to the Rack input stream spec (i.e., be an IO-like object).
Because this was also quite a challenge for me and I used it as an exercise to deepen my knowledge of Rack I created a simple project on GitHub to explore this file upload mocking.
Note: I tried to fix everything to Rack 1.5.2 except for the link to the Rack SPEC (so beware). Links to Ruby StdLib lead to the current version.

Parameters in route are not resolved

I have this configuration in the controller in Padrino
MyProject::App.controllers do
get '/' do
handlebars :index
end
get :file, :with => :tokenId do
tokenId = params[:tokenId]
[extra logic]
end
end
GET / works.
GET /file/abc doesn't.
GET /file/:tokenId works!
It looks like :token is not recognized as a parameter placeholder in the route definition.
I've tried
get "/file/:tokenId"
too but with no luck.
I can't find any information on any similar issue, anybody can help? Happy to add more information if needed.
Okay so I am unsure why the change made a difference but camelCase is generally considered poor syntax for variables in ruby.(Padrino may be calling a method such as underscore on your variable i.e.
"tokenID".underscore.to_sym
#=>:token_id
Using underscored_variables instead. (e.g. :tokenID becomes :token_id. This structure also allows for interacting with databases in a nicer way as well since your columns will have names such as token_id not tokenID.
There are uses for camelCasing in ruby and rails such as class naming and generators but trying keep all local and instance variables in lowercase underscore format.
I don't do much work in padrino so I am not 100% sure why this change helped but I am glad I could help.

How to do atomic update in Mongo/Mongomatic?

I'm having an awfully difficult time figuring out how to update a MongoDB document, using the atomic '$set' operator with Mongomatic. I'm pretty sure it's Mongo's criteria/update language I'm having troubles with, not Mongomatic, but I'm willing to be proven wrong.
The link to a gist with a standalone, runnable script is here: https://gist.github.com/3835672
I'm starting out by creating a document that looks like this:
{"videos":[{"video_id":"video1"},{"video_id":"video2"}],"_id":{"$oid": "506ddd53a114604ce3000001"}}
I can get that document using a model instantiated using Mongomatic:
video_group = VideoGroup.find_one('videos.video_id' => 'video1')
Then I'm trying to set a 'views' field, by doing this:
video_group.update!({ 'videos.video_id' => 'video1' }, '$set' => { 'videos.$.views' => 123 })
That's where Mongo blows up, with the following error:
can't append to array using string field name [$]
I know this is a very common question on StackOverflow. I understand generally that the problem is that the positional operator isn't getting any matches. But even reading through dozens of responses, I still can't figure out how to express this statement in a way that works.
Am I just starting out with the wrong data structure?
It is, in fact, a mongomatic problem. You need to pass the underlying mongo ruby driver the option {:multi => true}, as well as including your criteria with the specific _id for the update sent to mongodb instead of as part of the optional parameters. Looks like a bug in mongomatic. Here is the ruby debugger transcript that I used to find it: https://gist.github.com/3836797
Note that I made a change to the file you posted, adding the line debugger before line #41, and changing line #42-44 to this:
video_group.update!({ 'videos.video_id' => 'video1', :multi => true }, '$set' => {
'videos.$.views' => 123,
})

ruby Openid compliant lib

I'm using ruby openid compliant library so I can be an openid consumer, I got the sample and when I try to start-up the service, it show errors like
/var/lib/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/session/cookie_store.rb:163:in `ensure_session_key': A key is required to write a cookie containing the session data. Use config.action_controller.session = { :key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb (ArgumentError)
from /var/lib/gems/1.8/gems/actionpack-2.3.2/lib/action_controller/session/cookie_store.rb:74:in `initialize'
any idea would be appreciated, thanks
I don't know anything about ruby but I strongly suspect you need to change these two things.
"_myapp_session"
"some secret phrase"
1 should probably be a session id (How to get this in I have no idea). 2 could in theory be left alone but it's not very secret then.
Just faced the same error. Although error message (as it often happens with ruby) is a bit messy, it says you exactly what to do: put that piece of code inside config/environment.rb.
To be specific, put it inside Rails::Initializer.run do |config| block.

Resources