How to pass arguments in ruby on rails - ruby-on-rails

I have an issue with the code below, it takes no argument, and i am suppose to create a method to return the answer. I don't know how to get the value passed.
And please what is the name of this concept, so i can read it up.
describe "reverser" do
it "reverses the string returned by the default block" do
result = reverser do
"hello"
end
result.should == "olleh"
end
end
def reverser(sent)
words = sent.split(" ")
i = 0
while i < words.length
words[i] = words[i].reverse
i += 1
end
words.join(" ")
end

def reverser
yield.reverse
end
Here is the code in question:
result = reverser do
"hello"
end
That is equivalent to:
result = reverser() do
"hello"
end
Breaking that down:
method call -+ +- 'do' marks the start of the block
| |
V V
result = reverser() do
"hello"
end
^
|
+--- end of the block
A block is like a method, and it's as if the block is passed as an invisible argument to the reverse() method. Inside reverse(), you can call the block using yield:
def reverser
return_value_of_block = yield
return_value_of_block.reverse
end
Writing yield, or yield(), is like writing unamed_block_method(). In other words, yield is the name ruby gives to the block-method.
And if the block is supposed to take arguments, then the block will look like this:
result = do_stuff do |x|
x * 2
end
And do_stuff() will look like this:
def do_stuff
return_value_of_block = yield(4) #call block with the argument 4
end
And please what is the name of this concept, so i can read it up.
Search for ruby blocks.

Your reverser takes a block which you call to get it return value:
def reverser(&block)
string = block.call
string.reverse
end
Note: You do not need the string variable. I just use it for readability reasons. You can also just write block.call.reverse.

Related

array.select not iterating through every element

I have a rails controller and this code only loop through the first element in the metrics array? Why is that?
# /metrics/:id
def values
#metric = metrics.select do |metric|
id = metric['href'].split('/').last
p "id == params[:id] = #{id == params[:id]}" # false on the first iteration (but never gets to the next iteration
return id == params[:id]
end
p "HERE?" # We never get here!
end
You need to remove the return statement from your method, Ruby uses implicit return (see https://jtrudell.github.io/blog/ruby_return_values/), so the result of a block is the last line that is evaluated in that block, the return statement in your code is treated as a return from the values method. Your method needs to look something like:
def values
#metric = metrics.select do |metric|
metric['href'].split('/').last == params[:id]
end
end

How do I create accessors for ActiveRecord models from an array of symbols? [duplicate]

When I run code below it raise error:
implicit argument passing of super from method defined by define_method() is not supported. Specify all arguments explicitly. (RuntimeError).
I am not sure what is the problem.
class Result
def total(*scores)
percentage_calculation(*scores)
end
private
def percentage_calculation(*scores)
puts "Calculation for #{scores.inspect}"
scores.inject {|sum, n| sum + n } * (100.0/80.0)
end
end
def mem_result(obj, method)
anon = class << obj; self; end
anon.class_eval do
mem ||= {}
define_method(method) do |*args|
if mem.has_key?(args)
mem[args]
else
mem[args] = super
end
end
end
end
r = Result.new
mem_result(r, :total)
puts r.total(5,10,10,10,10,10,10,10)
puts r.total(5,10,10,10,10,10,10,10)
puts r.total(10,10,10,10,10,10,10,10)
puts r.total(10,10,10,10,10,10,10,10)
The error message is quite descriptive. You need to explicitly pass arguments to super when you call it inside of define_method block:
mem[args] = super(*args)

Why is initializing variables so important?

Please can someone explain to me, why NOT initializing first_idx and last_idx causes the code not to run??
When I run it I get this error "undefined local variable or method last_idx". I know that the advice is to always initialize the variables, but I don't understand why. After all first_idx and last_idx will ALWAYS get a value inside the loop because the argument letter is always present in the string (in this particular problem).
I'd really appreciate some (simple) insight. Thank you!
P.S, I also know that the problem is easily solved using #index and #rindex in Ruby, but I'm not allowed to solve it using straightforward methods.
def find_for_letter(string, letter)
first_idx = nil
0.upto(string.length - 1) do |idx1|
if string[idx1] == letter
first_idx = idx1
break
end
end
last_idx = nil
(string.length - 1).downto(0) do |idx2|
if string[idx2] == letter
last_idx = idx2
break
end
end
if last_idx == first_idx
return [first_idx]
else
return [first_idx, last_idx]
end
end
def first_last_indices(word)
h = {}
word.chars.each do |char|
h[char] = find_for_letter(word, char)
end
h
end
Variables in block
From the Ruby Programming Language:
Blocks define a new variable scope: variables created within a block
exist only within that block and are undefined outside of the block.
Be cautious, however; the local variables in a method are available to
any blocks within that method. So if a block assigns a value to a
variable that is already defined outside of the block, this does not
create a new block-local variable but instead assigns a new value to
the already-existing variable.
a = 0
2.times do
a = 1
end
puts a #=> 1
b = 0
2.times do |i;b| # <- b will stay a block-local variable
b = 1
end
puts b #=> 0
2.times do |i|
c = 1
end
puts c #=> undefined local variable or method `c' for main:Object (NameError)
Refactoring your code
Iterating with chars and index
Here's a smaller method for your goal.
It keeps a hash with minmax indices for each character.
The default hash value is an empty array.
The method iterates over each character (with index).
If minmax array already contains 2 values :
it replaces the second one (max) with current index.
it adds current index to the array otherwise.
def first_last_indices(word)
minmax_hash = Hash.new { |h, k| h[k] = [] }
word.each_char.with_index do |char, index|
minmax = minmax_hash[char]
if minmax.size == 2
minmax[1] = index
else
minmax << index
end
end
minmax_hash
end
p first_last_indices('hello world')
{"h"=>[0], "e"=>[1], "l"=>[2, 9], "o"=>[4, 7], " "=>[5], "w"=>[6], "r"=>[8], "d"=>[10]}
With group_by
Here's another possibility. It uses group_by to get all the indices for each character, and minmax to get just the first and last indices :
def first_last_indices(word)
word.each_char.with_index
.group_by{ |c, _| c }.map{ |c, vs|
[c, vs.map(&:last).minmax.uniq]
}.to_h
end
p first_last_indices('hello world')
{"h"=>[0], "e"=>[1], "l"=>[2, 9], "o"=>[4, 7], " "=>[5], "w"=>[6], "r"=>[8], "d"=>[10]}
Even if you do not declare last_idx, you can still initialise it inside the loop, i.e.:
(string.length - 1).downto(0) do |idx2|
if string[idx2] == letter
last_idx = idx2 # works absolutely fine
break
end
end
However notice where you declared the variable. Its a local variable and hence its tied to the block you are in. Now when you try to access that variable outside the block, you get the error:
undefined local variable or method last_idx
To make the variable available outside the block, you have to declare it outside. That is what you are doing when you declare last_idx = nil before the block where its assigned a value.
UPDATE:
Though by using instance variables you can avoid declaration, the best practices suggests it should be used in cases where information that these variables have is relevant to all or almost all of the class. On the other hand, if the information is very much limited to this particular method use local variables.
This is just the way that local variables work.
If you use instance variables, Ruby will assume that they have been initialised inside the conditional block, but will not for local variables.
def find_for_letter(string, letter)
0.upto(string.length - 1) do |idx1|
if string[idx1] == letter
#first_idx = idx1
break
end
end
(string.length - 1).downto(0) do |idx2|
if string[idx2] == letter
#last_idx = idx2
break
end
end
if #last_idx == #first_idx
return [#first_idx]
else
return [#first_idx, #last_idx]
end
end
This works fine.

TypeError: no implicit conversion of Symbol into Integer

I encounter a strange problem when trying to alter values from a Hash. I have the following setup:
myHash = {
company_name:"MyCompany",
street:"Mainstreet",
postcode:"1234",
city:"MyCity",
free_seats:"3"
}
def cleanup string
string.titleize
end
def format
output = Hash.new
myHash.each do |item|
item[:company_name] = cleanup(item[:company_name])
item[:street] = cleanup(item[:street])
output << item
end
end
When I execute this code I get: "TypeError: no implicit conversion of Symbol into Integer" although the output of item[:company_name] is the expected string. What am I doing wrong?
Your item variable holds Array instance (in [hash_key, hash_value] format), so it doesn't expect Symbol in [] method.
This is how you could do it using Hash#each:
def format(hash)
output = Hash.new
hash.each do |key, value|
output[key] = cleanup(value)
end
output
end
or, without this:
def format(hash)
output = hash.dup
output[:company_name] = cleanup(output[:company_name])
output[:street] = cleanup(output[:street])
output
end
This error shows up when you are treating an array or string as a Hash. In this line myHash.each do |item| you are assigning item to a two-element array [key, value], so item[:symbol] throws an error.
You probably meant this:
require 'active_support/core_ext' # for titleize
myHash = {company_name:"MyCompany", street:"Mainstreet", postcode:"1234", city:"MyCity", free_seats:"3"}
def cleanup string
string.titleize
end
def format(hash)
output = {}
output[:company_name] = cleanup(hash[:company_name])
output[:street] = cleanup(hash[:street])
output
end
format(myHash) # => {:company_name=>"My Company", :street=>"Mainstreet"}
Please read documentation on Hash#each
myHash.each{|item|..} is returning you array object for item iterative variable like the following :--
[:company_name, "MyCompany"]
[:street, "Mainstreet"]
[:postcode, "1234"]
[:city, "MyCity"]
[:free_seats, "3"]
You should do this:--
def format
output = Hash.new
myHash.each do |k, v|
output[k] = cleanup(v)
end
output
end
Ive come across this many times in my work, an easy work around that I found is to ask if the array element is a Hash by class.
if i.class == Hash
notation like i[:label] will work in this block and not throw that error
end

Caching, when nil or false are acceptable results [duplicate]

This question already has answers here:
How can I memoize a method that may return true, false, or nil in Ruby?
(2 answers)
Closed 8 years ago.
In some ruby classes, it is useful to cache the results of an expensive operation using the ||= operator, as in the following snippet:
class CacheableCalculations
def foobar
#foobar ||= lambda { some_expensive_calculation }.call
end
end
The issue arrises when the returned value is either nil or false, as this test shows:
class Example
attr_accessor :counter
def initialize(value)
#counter = 0
#value = value
end
def fancy_calculation
#foo ||= lambda { #counter += 1; #value }.call
end
end
first = Example.new(true)
5.times { first.fancy_calculation }
puts first.counter # <== 1, expected
second = Example.new(false)
5.times { second.fancy_calculation }
puts second.counter # <== 5, not caching
third = Example.new(nil)
5.times { third.fancy_calculation }
puts third.counter # <== 5, not caching
Is there any pros or cons with using the defined? operator instead, as in the following block of code?
class Example
attr_accessor :counter
def initialize(value)
#counter = 0
#value = value
end
def fancy_calculation
(defined? #foo) ? #foo : (#foo = lambda { #counter += 1; #value }.call)
end
end
This is still one 1 line, but is quite repetitive.
Is there a better way of easily returning cached results, regardless of what the value is?
The problem with the way it is written is that the ternary operator ?: has higher precedence than assignment = so it is parsed as
def fancy_calculation
((defined? #foo) ? #foo : #foo) = lambda { #counter += 1; #value }.call # NO
end
which you don't want, because #foo is always assigned to.
Instead, do this
def fancy_calculation
defined?(#foo) ? #foo : (#foo = lambda { #counter += 1; #value }.call)
end
This is probably about as succinct as can be without using a separate package/function specifically for memoization.
What you are trying to achieve is called memoization. There used to be a method doing what you need in Rails but at some point they extracted to a separate memoist gem. Check it out: https://github.com/matthewrudy/memoist
There is an alternative one as well: https://github.com/dkubb/memoizable

Resources