When I used respond_with and passed a literal hash, it gave me the error:
syntax error, unexpected tASSOC, expecting '}'
`respond_with {:status => "Not found"}`
However, when I enclosed the literal hash in parentheses like so:
respond_with({:status => "Not found"})
the function runs without a hitch. Why do the parentheses make a difference? Isn't a hash an enclosed call?
When calling a method, the opening curly bracket directly after the method name is interpreted as the start of a block. This has precedence over the interpretation as a hash. One way to circumvent the issue is to use parenthesis to enforce the interpretation as a method argument. As an example, please note the difference in meaning of these two method calls:
# interpreted as a block
[:a, :b, :c].each { |x| puts x }
# interpreted as a hash
{:a => :b}.merge({:c => :d})
Another way is to just get rid of the curly brackets as you can always skip the brackets on the last argument of a method. Ruby is "clever" enough to interpret everything which looks like an association list at the end of an argument list as a single hash. Please have a look at this example:
def foo(a, b)
puts a.inspect
puts b.inspect
end
foo "hello", :this => "is", :a => "hash"
# prints this:
# "hello"
# {:this=>"is", :a=>"hash"}
Related
I've got an array of strings. A few of the strings in this array contain a certain substring I'm looking for. I want to get an array of those strings containing the substring.
I would hope to do it like this:
a = ["abc", "def", "ghi"]
o.select(&:include?("c"))
But that gives me this error:
(repl):2: syntax error, unexpected ')', expecting end-of-input
o.select(&:include?("c"))
^
If your array was a file lines.txt
abc
def
ghi
Then you would select the lines containing c with the grep command-line utility:
$ grep c lines.txt
abc
Ruby has adopted this as Enumerable#grep. You can pass a regular expression as the pattern and it returns the strings matching this pattern:
['abc', 'def', 'ghi'].grep(/c/)
#=> ["abc"]
More specifically, the result array contains all elements for which pattern === element is true:
/c/ === 'abc' #=> true
/c/ === 'def' #=> false
/c/ === 'ghi' #=> false
You can use the &-shorthand here. It's rather irrational (don't do this), but possible.
If you do manage to find an object and a method so you can make checks in your select like so:
o.select { |e| some_object.some_method(e) }
(the important part is that some_object and some_method need to be the same in all iterations)
...then you can use Object#method to get a block like that. It returns something that implements to_proc (a requirement for &-shorthand) and that proc, when called, calls some_method on some_object, forwarding its arguments to it. Kinda like:
o.m(a, b, c) # <=> o.method(:m).to_proc.call(a, b, c)
Here's how you use this with the &-shorthand:
collection.select(&some_object.method(:some_method))
In this particular case, /c/ and its method =~ do the job:
["abc", "def", "ghi"].select(&/c/.method(:=~))
Kinda verbose, readability is relatively bad.
Once again, don't do this here. But the trick can be helpful in other situations, particularly where the proc is passed in from the outside.
Note: you may have heard of this shorthand syntax in a pre-release of Ruby 2.7, which was, unfortunately, reverted and didn't make it to 2.7:
["abc", "def", "ghi"].select(&/c/.:=~)
You are almost there, you cannot pass parameter in &:. You can do something like:
o.select{ |e| e.include? 'c' }
I'm writing a simple method to detect and strip tags from text strings. Given this input string:
{{foobar}}
The function has to return
foobar
I thought I could just chain multiple chomp! methods, like so:
"{{foobar}}".chomp!("{{").chomp!("}}")
but this won't work, because the first chomp! returns a NilClass. I can do it with regular chomp statements, but I'm really looking for a one-line solution.
The String class documentation says that chomp! returns a Str if modifications have been made - therefore, the second chomp! should work. It doesn't, however. I'm at a loss at what's happening here.
For the purposes of this question, you can assume that the input string is always a tag which begins and ends with double curly braces.
You can definitely chain multiple chomp statements (the non-bang version), still having a one-line solution as you wanted:
"{{foobar}}".chomp("{{").chomp("}}")
However, it will not work as expected because both chomp! and chomp removes the separator only from the end of the string, not from the beginning.
You can use sub
"{{foobar}}".sub(/{{(.+)}}/, '\1')
# => "foobar"
"alfa {{foobar}} beta".sub(/{{(.+)}}/, '\1')
# => "alfa foobar beta"
# more restrictive
"{{foobar}}".sub(/^{{(.+)}}$/, '\1')
# => "foobar"
Testing this out, it's clear that chomp! will return nil if the separator it's provided as an argument is not present at the end of the string.
So "{{text}}".chomp!("}}") returns a string, but "{{text}}".chomp!("{{") reurns nil.
See here for an answer of how to chomp at the beginning of a string. But recognize that chomp only looks at the end of the string. So you can call str.reverse.chomp!("{{").reverse to remove the opening brackets.
You could also use a regex:
string = "{{text}}"
puts [/^\{\{(.+)\}\}$/, 1]
# => "text"
Try tr:
'{{foobar}}'.tr('{{', '').tr('}}', '')
You can also use gsub or sub but if the replacement is not needed as pattern, then tr should be faster.
If there are always curly braces, then you can just slice the string:
'{{foobar}}'[2...-2]
If you plan to make a method which returns the string without curly braces then DO NOT use bang versions. Modifying the input parameter of a method will be suprising!
def strip(string)
string.tr!('{{', '').tr!('}}', '')
end
a = '{{foobar}}'
b = strip(a)
puts b #=> foobar
puts a #=> foobar
Why is it not possible to print a hash directly.
p {:First=>1, :Second=>2}
syntax error, unexpected =>, expecting '}'
But its possible to save it to a variable and then print that variable?
my_hash = {:First=>1, :Second=>2}
p my_hash
{:First=>1, :Second=>2}
It appears that it's because Ruby is confused and thinks you're passing a block to the p method.
p {:First=>1, :Second=>2}
SyntaxError: unexpected =>, expecting '}'
As you can see from the error, it was expecting a } where your hash rocket for the first key is. As you've already found out, you can set this as a variable and it works. But you can also pass the hash to the p method without setting it as a variable if you use parenthesis, because p, puts and print are just methods too. This works, because passing a block to a method in Ruby requires it to be outside of the parenthesis which then cuts down on the ambiguity of what you're trying to do.
p({:First=>1, :Second=>2})
{:First=>1, :Second=>2}
#=> {:First=>1, :Second=>2}
As noted by tadman in the comments, you can also omit the parenthesis and curly braces as Ruby knows that key/value pairs passed as arguments to a method are the equivalent to passing a hash and wouldn't be misinterpreted as a block.
p :First => 1, :Second => 2
{:First=>1, :Second=>2}
#=> {:First=>1, :Second=>2}
I don't understand the behavior of print and puts?
I know print would not make a new line but puts can.
but why the output of print will change from symbol to string when using puts instead of print?
`$ ruby -e 'print Kernel.private_instance_methods(false)'
[:initialize_copy, :remove_instance_variable, :sprintf, :format, :Integer, :Float, :String, :Array, :warn, :raise, :fail, :global_variables, :__method__, :__callee__, :eval, :local_variables, :iterator?, :block_given?, :catch, :throw, :loop, :caller, :trace_var`
$ ruby -e 'puts Kernel.private_instance_methods(false)'
initialize_copy
remove_instance_variable
sprintf
format
Integer
Float
String
Array
warn
raise
fail
global_variables
__method__
__callee__
eval
local_variables
When you call puts, what really gets called is the rb_io_puts C function, which basically works like this:
If there is no argument, output a newline.
For each argument check if it's of type string (T_STRING in Ruby C lingo) and if yes, call rb_io_write with it. Also, if the string was of length zero or didn't finish in a newline, add a \n.
If the argument is an array, recursively call io_puts_ary on it.
In any other case, call rb_obj_as_string on the argument, which basically is the low-level equivalent of to_s.
So when you puts [:a, :b, :c], you'll hit the third case and io_puts_ary will take over. Long story short this will do something similar as what I described above, and will call rb_obj_as_string on each element and output it followed by a newline.
The print function will call array's to_s function,
the array's to_s function is the alias of inspect function.
This can be found in ruby's array.c code.
rb_define_alias(rb_cArray, "to_s", "inspect");
Therefore:
array = Kernel.private_instance_methods(false)
$stout.write(array.to_s)
will also output the same result.
So the loose tolerance of Ruby to use braces sometimes and not REQUIRE them has led to alot of confusion for me as I'm trying to learn Rails and when/where to use each and why?
Sometimes parameters or values are passed as (#user, #comment) and other times they seem to be [ :user => comment ] and still others it's just: :action => 'edit'
I'm talking about the us of [ ] vs ( ) vs { }
What ARE the rules? And are there any tricks you have to help you remember?
Parentheses () are for grouping logical or mathematical expressions and grouping arguments to a function call, e.g.:
a = 2 * (3 + 4)
b = (x==y) || (m==n)
Hash.new.send('[]=', :a, :b)
Curly Braces {} are used for hash literals and blocks, e.g.:
h = {1=>2, 2=>3}
h.each {|k,v| puts k+v}
Square Brackets [] are used for array literals, array indexing and slicing, and fetching from a hash, e.g.:
arr = [1, 2, 3]
two = arr[1]
three = h[2]
To confuse the matter, hash literals can also be used in-place as an argument to a method call without needing the curly braces or parentheses as long as it is the last argument (thanks samuil). Additionally, hash literals can be used in-place in square brackets to create a single-item array containing the hash:
puts 1=>2, 3=>4 #=> 1234
[5=>6, 7=>8] #=> [{5=>6, 7=>8}]
When in doubt, always use parentheses to group items and wrap your hashes in curly braces.