I am a bit confused with the frozen string and utilizing them with test cases.
I just added the following line at the top of my test cases :
# frozen_string_literal: true
And i have the following two test cases:
test "Create upload invoice invalid invoice id" do
post :upload, params = {invoices_data: [{invoice_id: 987654, unit_id: 1321}]}
assert_response :not_found
end
test "Create upload invoice request to fortnox with non array request parameter" do
request = {invoices_data: {invoice_id: "invoice.id", unit_id: 321}}
post :upload_invoices, params = request
assert_response :bad_request
end
All of a sudden my second test failed with
RuntimeError: can't modify frozen String
at this line
post :upload_invoices, params = request
however, if I change the string to some symbol for instance :invoice_id then it works just fine.
Can someone guide why about the following two things:
Why does sending a string value fails in this case reporting that I
am trying to modify a String and which string value I am trying to
modify?
Why does it fail on post request, if it has to fail then it
should fail when creating the request i.e request = {invoices_data: {invoice_id: "invoice.id", unit_id: 321}}
What i can do to send string value instead of Symbol in the hash?
1a) Sending a string value fails in this case because your upload_invoices controller action attempts to modify the invoice_id parameter itself. (Or you're running an old version of Rails where the #post method itself attempts to modify the invoice_id parameter by converting it to UTF-8 encoding.)
1b) The string value you're trying to modify is "invoice.id".
2 ) It fails on the post request and not the assignment to the request variable because the assignment to the request variable is not where the attempted modification happens. The frozen string literal is attempted to be modified by the call to #post. See answer 1a above.
3 ) You can send a non-frozen string value in the hash a few different ways. You could remove the # frozen_string_literal: true magic comment, but I feel you don't want to do that. Otherwise, the simplest thing to do is to send along a duplicate of the string with either +'invoice.id' or the less esoteric 'invoice.id'.dup. Or you can create a non-literal string with something silly like ['invoice', 'id'].join('.') or :invoice.to_s. No doubt there are other ways.
However, it seems EXTREMELY unlikely you want to pass a string here at all. The invoice_id parameter is almost assuredly an integer, and passing a string to it makes little sense unless I guess you're trying to test that the controller action can handle that kind of erroneous input. If so, one of the string duplication techniques +'string_literal'/'string_literal'.dup would be your best option.
I would wager by the name of the test that you're actually trying to send along a real invoice_id which means you don't want to pass along a string, but instead an integer. Maybe the ID of an Invoice fixture you have setup?
And on another slightly unrelated note, you're not passing params to the #post method properly. It should be params: ... not params = ....
Related
A series of parameters are being submitted to a Rails controller:
"25"=>"true", "30"=>"false", "35"=>"true", "40"=>"true", "45"=>"true", "50"=>"true", "55"=>"true", "60"=>"true", "65"=>"false", "70"=>"true", "75"=>"true", "80"=>"false", "100"=>"true"
The goal is to loop through:
#age_limits = [25,30,35,40,45,50,55,60,65,70,75,80,100]
and process based on each param's value.
The problem is that the syntax for referring to the param is evading me:
#age_limits.each do |limit|
puts params[:"#{limit}"]
if params[:"#{limit}"] == "true"
session[:demographic_agegroups] << params[:"#{limit}"]
puts session[:demographic_agegroups]
end
end
Both params[:"#{limit}"] and params[":#{limit}"] do not allow to access the parameter. The former will return the value by putting into console (editor BBEdit does not consider it properly formed Ruby), but will not allow to process in its basis.
How can the controller properly access the param and process based on its value?
You'r missing to convert int to string.
Since your params keys are string, you can use params[limit.to_s].
Also, if its key based "keyX".to_sym would be :keyX
I'm trying to get the value of a variable, prop, in a query string from a passed URL similar to what the gentleman in this thread did.
It works if there's a query string in the URL but if there isn't I get
CGI.parse undefined method 'split' for nil:NilClass error. I did research and the problem is because there is no query to split.
So my question is how can I test for the presence of any query string and then run it through CGI.parse to see if the prop query string is one of them? I assume I could probably do it through Regex but I was hoping there was a Rails solution.
Any help would be appreciated.
Thanx
Code:
I'm also trying to get the domain name of the URL referrer. That is why I have that variable in there.
url = request.env['HTTP_REFERER']
encoded_url = URI.encode(url.to_s)
parse = URI.parse(encoded_url)
domain = parse.host
puts domain
params = CGI.parse(parse.query)
puts params['prop'].first
UPDATE:
I got the error to go away by adding the attached code. But I'm still wondering if there's a better Rails solution. I'm still fairly new to Rails, so I want to make sure I'm doing it correctly.
if encoded_url.include? "?prop"
params = CGI.parse(parse.query)
puts params['prop'].first
end
The keys in query string in a URL can be accessed using params[] hash, even if the URL is not present inside the app itself. This is how it is convenient for rails to communicate with the outside world as well. Note that rails treats all the HTTP verbs equally when it comes to usage of params[] hash.
Usage: puts params[:your_key]
I have the following code to test file uploading:
test 'when a user adds an attachment to an existing candidate, the attachment shows up on the candidates page' do
user = login_user
opportunity = opportunities(:with_candidates)
candidate = candidates(:first)
upload = fixture_file_upload(ActionController::TestCase.fixture_path + 'files/file_upload_support_image.jpeg', 'image/jpeg')
attributes = { attachments: [upload] }
user.put opportunity_candidate_path(opportunity, candidate, request: attributes)
user.follow_redirect!
assert_match /#{upload.original_filename}/, fixer.response.body, 'The filename of the attachment should appear on the opportunity candidates page'
end
The test fails because the file does not get attached.
The code works when run through the browser – it turns out I had written the assert incorrectly and was getting a false pass. Now the assert is correct, the test fails.
When debugging, I find that the upload variable in the test is something like #<Rack::Test::UploadedFile:0x007f85ca141c50>. However, when I debug the controller, the value in the params hash is "#<Rack::Test::UploadedFile:0x007f85ca141c50>".
Note the quote marks. Rails has turned the file upload into a string!
Since there’s none of my code between the test and the controller, and this works when the app is run in the browser, I guess something may be wrong with how I am constructing the params hash or the tempfile itself in the test.
Any ideas what I am doing wrong?
Okay, turns out I was doing something incorrectly in the test. However, this error does not manifest if you are passing the most common types of parameter, so it can be a little tricky to spot!
The line
user.put opportunity_candidate_path(opportunity, candidate, request: attributes)
Should be
user.put opportunity_candidate_path(opportunity, candidate), request: attributes
Note the moved parenthesis. D’oh!
The reason this may not be easy to spot is that unknown keys in the ..._path method are still passed as parameters. However, they are parsed having been encoded into the URL querystring, like a GET request.
For the common cases of passing text values and integer foreign keys in as params, this mangling doesn’t prevent anything working, it’s only for more complex objects that it becomes an issue.
Is there a best practice to validate params in a controller?
#user = User.find_by_id(params[:id])
If I tamper with the param to give it an invalid :id param, say by visiting "/users/test", I can generate the following error:
Conversion failed when converting the nvarchar value 'test' to data type int.
I am thinking right now of params that won't go straight to a model and can be validated by model validations.
Yes you should always validate your parameters. People can always mess around with the parameters in their web browser's address bar, or modify parameters stored in the DOM. Another example where parameters can be screwed up is if the webpage is left open a long time. Imagine someone is viewing the page "/users/3/edit" and leaves it open for an hour, then hits refresh. In the mean time that user may have been deleted. You don't want your website to crash - it should handle that gracefully.
Depending on your database and adapter, doing User.find_by_id("test") will not crash. But your database/adapter was not able to convert the string in to an integer. One thing you can do in this particular case is use Ruby's .to_i method.
User.find_by_id(params[:id].to_i)
If params[:id] = "12", Ruby will convert that to the integer 12 and the code will run fine. If params[:id] = "test", Ruby will convert that to the integer 0, and you should never have a database record with an ID of 0.
You can also use regular expressions to test if a string is an integer.
But in general, yes, try to always validate your parameters so you can handle errors gracefully and control the data coming in.
Note * I'm using Factory_girl, not sure if that matters here
Currently, I do the following to prep for doing a post:
p = Factory.build(:le_object, :template_id => t.id.to_s)
Notice that I am deliberately converting it to a string
And then I just do your ordinary post:
post :create, :le_object => p.attributes
But, what is fishy, is when I look in the test log, I see this:
Parameters: {"action"=>"create", ... "template_id"=>0, ... }
Notice how the id is NOT a string.
Now, this messes up the controller, because I do a not .empty? check on that field (because it is optional), and then that method in the controller does something entirely different.
This works fine in the app, when it is actually running, as for the same method, the console outputs this in dev / production:
Parameters: {"action"=>"create", ... "template_id"=>"1"...}
Notice how here the 1 is a string, because it came from a form, and in HTTP, everything is strings.
This is what in my code is failing, specifically:
if not params[:le_object][:template_id].nil? and not params[:le_object][:template_id].empty?
The issue currently, is that in the test, because it's passing a number, instead of the string representation, I'm getting the error undefined method empty? for #:Fixnum
So... How do I change my code (test or otherwise) such that I can get this to work?
Rails defines the .blank? method on Object, meaning it works on just about anything (strings, numbers, etc.) The definition of .blank? is very simple:
respond_to?(:empty?) ? empty? : !self
It will default to use empty?, but if that method doesn't exist on the object, it will just call !self. If the item is nil, then !nil will evaluate to true.