How do I easily parse a URL with parameters in a Rails test?
CGI::parse(querystring)
will parse a querystring into a hash. Then, CGI::unescape(string)
will undo any URL-encoding in the value.
Alternatively, you can use Rack::Utils.parse_query
and Rack::Utils.unescape
if you're on a recent Rack-based version of Rails, and want to be super-modern.
I'm not aware of any Rails-specific helper methods that wrap these utility functions, but they're pretty simple to use, and CGI or Rack is already loaded in the Rails environment anyway.
How to extract URL parameters from a URL with Ruby or Rails?
I think you want to turn any given URL string into a HASH?
You can try http://www.ruby-doc.org/stdlib/libdoc/cgi/rdoc/classes/CGI.html#M000075
require 'cgi'
CGI::parse('param1=value1¶m2=value2¶m3=value3')
returns
{"param1"=>["value1"], "param2"=>["value2"], "param3"=>["value3"]}
Rails functional test: sending URL query parameters in POST request
A little background first to clarify things: although a request cannot be both GET and POST at the same time, there is nothing stopping you from using both the query string and body form data when using POST. You can even have a POST with all parameters in the query string and an empty body, though this sounds quite unusual.
Rails supports this scenario and indeed you can easily send a form using a POST request and still have query in the form's action. The query will be accessible with request.GET
hash (which is an alias of query_string
), while the POST body params with the request.POST
hash (an alias of request_parameters
). The params
hash is actually constructed from the combined GET
and POST
hashes.
However, from my research it seems that Rails does not support passing query string in POST requests in functional controller tests. Although I could not find anything regarding this in any documentation or among known issues on github, the source code is quite clear. In the following text, I'm assuming that you use Rails 4.
Why it does not work
The problem with functional controller tests is that they don't use real requests / responses but they simulate the HTTP handshake: the request is mocked up, its parameters filled in appropriate places and the given controller action is simply called as a normal ruby method. All of this is done in the action_controller/test_case
classes.
As it turns out, this simulation is not working in your particular case, due to two reasons:
The parameters passed in when running the test are always handed over either to the
request_parameters
, i.e. therequest.POST
hash when using apost
request or to thequery_string
(i.e.request.GET
) forget
test requests. There is no way for both of these hashes to be set during a single test run.This actually makes some sense as the
get
,post
, etc. helpers in functional tests accept only a single hash of params so the internal test code cannot know how to separate them into the two hashes.It is true that one can set up the request before running the test using the
@request
variable, but only to a certain extent, you can set headers, for example. But you cannot set internal attributes of the request, because they are recycled during the test run. The recycling is done here and it resets all internal variables of the request object and the underlying rack request object. So if you try to set up the request GET parameters like this@request.GET[:api_key] = 'my key'
, it won't have any effect as the internal variables representing this hash will get wiped during recycling.
Solutions / workarounds
Give up functional testing and choose integration tests instead. Integration tests allow to set the rack environment variables separately from the main parameters. The following integration test passes the
QUERY_STRING
rack env variable besides the normal post body params and should work flawlessly:class CollectionsTest < ActionDispatch::IntegrationTest
test 'foo' do
post collections_path, { collection: { name: 'New Collection' } },
{ "QUERY_STRING" => "api_key=my_api_key" }
# this proves that the parameters are recognized separately in the controller
# (you can test this in you controller as well as here in the test):
puts request.POST.inspect
# => {"collection"=>{"name"=>"New Collection"}}
puts request.GET.inspect
# => {"api_key"=>"my_api_key"}
end
endYou can still use most of the features from functional tests in your integration tests. E.g. you can test for assigned instance variables in the controller with the
assigns
hash.The transition argument is supported also by the fact that Rails 5 will deprecate functional controller tests in favor of integration testing and since Rails 5.1 these functional tests support will be moved out to a separate gem.
Try Rails 5: although functional tests will be deprecated, its source code seems to have been heavily rewritten in the rails master and e.g. recycling of the request is not used any more. So you might give it a try and try to set the internal variables of the request during test setup. I have not tested it though.
Of course, you can always try to monkey-patch the functional test so that it supports separate params for the
query_string
andrequest_parameters
hashes to be defined in tests.
I'd go the integration tests route :).
Passing attributes in test url - Rspec / Addressable gem
button_to
with nested attributes works; in the view file:
<%= button_to piece_path(@current_piece), method: :patch, params: {piece: {file: file, rank: rank}} do %>
And keep it simple in the controller:
def piece_params
params.require(:piece).permit(:rank, :file)
end
Get URL string parameters?
require 'uri'
require 'cgi'
# use URI.parse to parse the URL into its constituent parts - host, port, query string..
uri = URI.parse(@object.location)
# then use CGI.parse to parse the query string into a hash of names and values
uri_params = CGI.parse(uri.query)
uri_params['v'] # => ["xxxxxxxxxxxxxxxxxxx"]
Note that the return from CGI.parse
is a Hash
of Strings
to Arrays
so that it can handle multiple values for the same parameter name. For your example you would want uri_params['v'][0]
.
Also note that the Hash
returned by CGI.parse
will return []
if the requested key is not found, therefore uri_params['v'][0]
will return either the value or nil
if the URL did not contain a v
parameter.
How to parse a URL and extract the required substring
I'd do it this way:
require 'uri'
uri = URI.parse('http://something.example.com/directory/')
uri.host.split('.').first
=> "something"
URI is built into Ruby. It's not the most full-featured but it's plenty capable of doing this task for most URLs. If you have IRIs then look at Addressable::URI.
Related Topics
Do I Have to Manually Uninstall All Dependent Gems
How to Access Attributes Using Nokogiri
How to Downgrade from Ruby 1.9.2 to Ruby 1.8.7 to Run Rails 2.0.2
Net::Ssh Sudo Command Hangs After Entering Password
Is Everything an Object in Ruby
Exponentiation in Ruby 1.8.7 Returns Wrong Answers
How to Dump an Object's Fields to the Console
Get All Instance Variables Declared in Class
How to Use a Variable as Object Attribute in Rails
Sort a Collection of Objects by Number (Highest First) Then by Letter (Alphabetical)
Ruby Method That Returns Itself
Form_For Error Messages in Ruby on Rails