I'm upgrading an application from Rails 2 to Rails 3. Apparently, calling render() now returns ActionView::OutputBuffer and not String. I need to pass the results of render() to URI.escape(), and this fails with exception...
Here is my brief testing in the console
ob = ActionView::OutputBuffer.new("test test")
URI.escape(ob)
`NoMethodError: undefined method 'each_byte' for nil:NilClass`.
from /opt/ruby19/lib/ruby/1.9.1/uri/common.rb:307:in `block in escape'
from ..../ruby/1.9.1/gems/activesupport-3.2.1/lib/active_support/core_ext/string/output_safety.rb:160:in `gsub'
from ..../ruby/1.9.1/gems/activesupport-3.2.1/lib/active_support/core_ext/string/output_safety.rb:160:in `gsub'
from /opt/ruby19/lib/ruby/1.9.1/uri/common.rb:304:in `escape'
from /opt/ruby19/lib/ruby/1.9.1/uri/common.rb:623:in `escape'
Moreover, calling to_s on OutputBuffer returns same OutputBuffer class, so I cannot even convert this buffer into a honest string?
ob.to_s.class
ActionView::OutputBuffer
Of course, calling URI.escape("test test") returns "test%20test" as expected, so this is not URI problem.
Environment:
ruby 1.9.3p125 (2012-02-16 revision 34643) [i686-linux]
Rails 3.2.1
My question is: Why does this happen and how can I work around this issue?
Update: Apparently, using '' + ob as a form of ob.to_s converts OutputBuffer to String, which effectively works around the problem... But my question 'why does this happen' still remains, e.g. is this a bug, should I report it, or I'm doing something wrong?
This is a bug in Rails:
When calling gsub with a block on an ActiveSupport::SafeBuffer the global variables $1, $2, etc. for referencing submatches are not always properly set (anymore?) when the block is called.
This is why URI.escape (and any other function that uses gsub() will fail on ActiveSupprt::Safebuffer.
There are several discussions about this, apparently the safest route right now is to call to_str before passing SafeBuffer to anything that can call gsub, e.g. URI.encode, escape_javascript and similar functions.
My other quesion about to_s returning the same class - obviously safe buffer will return itself and not a bare String, this is by design. In order to get a true String, .to_str can be used.
This is due to the fact that Rails 3 introduced the concept of safe buffers
In Rails3 your Views are protected by XSS by default by making all rendering be safely escaped unless you explicitly use the raw() helper or html_safe
This is a dumb bug that i'm currently encountering in Rails 5. My stupid workaround was to do something like
ob = ActionView::OutputBuffer.new("test test")
URI.escape(ob.to_sym.to_s)
Again it works, but i'm still looking for a cleaner solution.
Related
Like the title, from 2.3.0 ruby version. We can use Safe Navigation Operator(&.) instead of try for dealing with nil value in Rails.
But when I use the new syntax, my reviewer told that is not good for reviewer, or someone else about the meaning my code.
So I would like to know which syntax I should use when work with nil value?
nil.try(:method)
nil&.method
&. works like #try!, not #try. documentation
#try not native in ruby but it is provided by rails.
Safe navigation #&. is almost 3~4 times faster than using the #try
require 'active_support/all'
require 'benchmark'
foo = nil
puts Benchmark.measure { 10_000_000.times { foo.try(:boo) } }
puts Benchmark.measure { 10_000_000.times { foo&.boo } }
Output
1.210000 0.000000 1.210000 ( 1.211835)
0.360000 0.000000 0.360000 ( 0.363127)
As mentioned in #dinjas's answer, .try is a method provided by Rails, whereas &. is a method that is provided by Ruby itself, and hence, will be usable outside Rails apps as well.
Additionally, the two operators do NOT work in EXACTLY the same manner. The safe navigation operator prevents NoMethodErrors only for those cases when it is invoked on a value which is nil. For example:
a = nil
a&.do_something # Result: nil
a = false
a&.do_something # Result: NoMethodError: undefined method `asd' for false:FalseClass
a.try(:do_something) # Result: nil
As seen above, while .try handles falsy values as well and returns nil as the response when a method is invoked on an object that does not support it. However, &. does not handle that and invokes methods on false as well. The same applies to blank strings.
In terms of what you should use, I believe that decision should be taken by having a discussion with your team. I believe neither of the methods are incorrect and either can be used. However, I would strongly recommend not using BOTH try and &. throughout the codebase as that could lead to unintended effects if the codebase grows large.
This blog by Georgi Mitrev describes the safe navigation operator in more depth and I would recommend giving it a read once.
try is a method provided by Rails' Active Support
The safe-navigation operator is provided by Ruby itself (since version 2.3).
If you were to open a Ruby console (not Rails), and define a method foo:
def foo
:bar
end
You could see something like the following:
>> nil.try(:foo)
Traceback (most recent call last):
4: from /Users/dinjas/.rbenv/versions/2.6.1/bin/irb:23:in `<main>'
3: from /Users/dinjas/.rbenv/versions/2.6.1/bin/irb:23:in `load'
2: from /Users/dinjas/.rbenv/versions/2.6.1/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
1: from (irb):7
NoMethodError (undefined method `try' for nil:NilClass)
>> nil&.foo
=> nil
IMHO, I prefer using Ruby methods over Rails methods as they are usable outside of the context of Rails.
That said, I also feel that whichever you use, it should be used sparingly. In my experience, it's a symptom of writing timid code (see Avdi Grimm's book, Confident Ruby).
I'm currently unable to use any of the active support time methods inside my ruby on rails 5.0 model like the following:
5.seconds
2.days
10.minutes
throws an error:
NoMethodError: undefined method `seconds' for AS::Duration:0x007f97a5903b90 #value=5, #parts=[[:seconds, 5]] Did you mean? send
EDIT: here is the actual code causing an issue.
ReminderJob.set(wait: 5.seconds).perform_later(self.user.id)
Even tho I can see people using the below code fine and it works
UserReminderJob.set(wait: 1.week).perform_later user
However, it works in my console and in my controllers and views.
The error message states that the object is AS::Duration:0x007f97a5903b90, NOT an integer -- therefore the example of 5.seconds will not reproduce the problem.
This is also unusual, since 5.seconds will normally return an ActiveSupport::Duration object, not AS::Duration.
I would therefore hazard a guess that you're actually using the as-duration ruby gem rather than built-in rails behaviour. This extends the the Integer class in a different way, and returns an object that doesn't behave like an integer.
I think that an actual reproduction of your error could be achieved with: 5.seconds.seconds. In standard rails, this works fine (and returns the same value as 5.seconds), since ActiveSupport::Duration instances behave like Integers. But with this gem, it fails with the above error.
Here is my code:
class Order < Grape::Entity
expose :id { |order, options| order.id.obfuscate }
expose :time_left_to_review do |order, options|
byebug
order&.time_left_to_review # ERROR
end
expose :created_at { |order, options| order.last_transition.created_at }
end
# NoMethodError Exception: undefined method `time_left_to_review' for #<Order:0x007f83b9efc970>
I thought &. is a shortcut for .try but I guess I was wrong. May someone point me to the right direction regarding what I am missing?
I feel like it's not ruby related. Grape maybe? Though I don't get how it could be.
&. works like #try!, not #try.
And here is description of #try! (from documentation):
Same as #try, but will raise a NoMethodError exception if the receiving is not nil and does not implemented the tried method.
So basically it saves you from calling a method on nil, but if an object is presented it will try to call its method as usual.
The quote is from Rails Documentation, and so it's important to emphasize
that Ruby does not provide #try; it's provided by Rails, or more accurately ActiveSupport. The safe navigation operator (&.) however, is a language feature presented in Ruby 2.3.0.
The try method ignores a lot of things, it just gives it a shot and calls it a day if things don't work out.
The & conditional navigation option will only block calls on nil objects. Anything else is considered to be valid and will proceed with full consequences, exceptions included.
I am arriving to the party a bit late here, the other answers have covered how it works, but I wanted to add something that the other answers have not covered.
Your question asks What is the difference between try and &. in Ruby. Ruby being the key word here.
The biggest difference is that try doesn't exist in Ruby, it is a method provided by Rails. you can see this or yourself if you do something like this in the rails console:
[1, 2, 3].try(:join, '-')
#=> "1-2-3"
However if you do the same thing in the irb console, you will get:
[1, 2, 3].try(:join, '-')
NoMethodError: undefined method `try' for [1, 2, 3]:Array
The &. is part of the Ruby standard library, and is therefore available in any Ruby project, not just Rails.
In addition to the above answers, I am adding some examples.1
account = Account.new(owner: Object.new)
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
As we can see, try doesn't check if the receiver responds to the given method or not. Whereas try! and &. behaves the same. If the receiver responds to the address method, all of them will return the same result. I prefer using &. as it looks more cleaner.
For more information on The Safe Navigation Operator (&.), I have found this blog really helpful https://mitrev.net/ruby/2015/11/13/the-operator-in-ruby/
I'm using rails 5 with ruby 2.3.3 . Today I added a gem, there was a version conflict so I took the gem out. Since then dot notation such as hash.test no longer works. It gives NoMethodError: private method test called for {:test=>"value"}:Hash
How can I access hashes with dot notation again?
Whatever you're using to use dot-notation to access a hash is probably using method_missing to trap your dot-notation method calls. But everything has a test method because Kernel#test exists and everything includes Kernel; also, pretty much everything in Kernel is private because Kernel is where methods go that we want to pretend are functions. For example:
> 'pancakes'.test
NoMethodError: private method `test' called for "pancakes":String
I suspect that you problem is your choice of :test as hash key.
I don't know why these two pieces of code behave different in Ruby 1.8.7 since one seems to be the single line version of the other.
The first piece of code (it works as it should):
if #type.present?
type = #type
orders = Order.where{type.eq(type)}
end
The single line version (it doesn't work at all, no error but seems no execution too):
orders = Order.where{type.eq(type)} if (type = #type).present?
NOTE: I'm using the squeel gem, that is the reason a block follows the where method. Also the variable type has to capture the instance variable #type since the execution context changes inside the block and the instance variables are not shared between the main context and the block context.
NOTE 2: I have to use Ruby 1.8.7 for legacy reasons.
Any idea? Thank you!
There is a problem with the order of parsing of your code. Variables need to be defined before they are used.
Even though variables defined inside if statement clauses "leak" out into the current scope, they do not leak "backwards" in Ruby code.
Ruby is a little bit curious in that way that variables need to be defined before the parser parses the code. The parsing is done from top to bottom and left to right.
Hence since the variable type is defined after your block code where you use it, it will not be available in the block.
Example:
>> 3.times { puts x } if (x = 123)
NameError: undefined local variable or method `x' for main:Object
The reason you don't get any error message is that in Ruby 1.8 type is a method that is a synonym for Object#class.
So what your code is really doing is (probably):
orders = Order.where{type.eq(this.class)} if (type = #type).present?
To fix it you have to define type before you use it. Therefore you can't really turn that into a one-liner unless you simply do this instead:
orders = Order.where{type.eq(#type)} if #type.present?
All in all it's not a good idea in Ruby 1.8 to use type as a variable in Rails models, because of the Object#class issue it will most likely bring you headaches in the long run.
similar problem that i got was that 'type' is a keyword in database, it allows for our model to have the field as 'type' but it works strangely in different conditions. if changing the name is an option for you then check after changing it, worked for me...
gem Squeel uses instance_eval method when calls block which passed to where. So, there is no any #type in squeel instance. If you want to use methods or variables from another context, try to wrap it into method my with block
orders = Order.where { type.eq my { #type } } if #type.present?
PS sorry for my English