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
Related
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.
I need to Add a tractor_beam instance method that takes a string description of an item as a parameter (e.g., "cow"). When called, the method should disable the shield, add the item to the inventory along with the ship's current location if it isn't too heavy to pick up (see algorithm below), enable the shield again, and return true. If the item is too heavy to pick up, the method should skip the inventory update and return false.
Algorithm:
An item is too heavy to pick up if its letters add up to more than 500. using .ord (Not very scientific, i know.) For example, the letters of cow add up to 329, so our tractor beam can abduct a cow, no problem.
My problem is that it returns nil and an empty hash, how do i break down the item to add each together?
Code:
class Spaceship
attr_accessor :name, :location, :item, :inventory
attr_reader :max_speed
def initialize (name, max_speed, location)
puts "Initializing new Spaceship"
#name = name
#max_speed = max_speed
#location = location
#item = item
#inventory = {}
end
def disable_shield
puts "Shield is off!"
end
def enable_shield
puts "Shield is on!"
end
def warp_to(location)
puts "Traveling at #{max_speed} to #{location}!"
#location = location
end
def tractor_beam(item)
disable_shield
item = item.split('')
item.each do |let|
let.ord
let + let.next
end
return item
if item > 500
enable_shield
#inventory[#location] = item
return true
else
return false
end
end
end
Driver Code:
uss_enterprise = Spaceship.new("USS Enterprise","200,000 mph", "China")
hms_anfromeda = Spaceship.new("HMS Andromeda", "108,277 mph", "China")
uss_enterprise.disable_shield
hms_anfromeda.enable_shield
p hms_anfromeda.location
hms_anfromeda.warp_to("Namibia")
p hms_anfromeda.location
hms_anfromeda.tractor_beam("cow")
p hms_anfromeda.item
Terminal:
Initializing new Spaceship
Initializing new Spaceship
Shield is off!
Shield is on!
"China"
Traveling at 108,277 mph to Namibia!
"Namibia"
Shield is off!
nil
Firstly, you have a return statement before your if conditional, so the conditional will never be ran. Remove that.
Secondly, you get the weight of the item by using ord, but you aren't assigning the value to anything:
item.each do |let|
let.ord
let + let.next
end
return item
if item > 500
This should do the trick:
item = item.split('')
weight = 0
item.each do |let|
weight += let.ord # add the ord of this letter to the weight
end
if weight > 500 # weight is now the ord of each letter of item 'cow'
enable_shield
#inventory[#location] = item
return true
else
return false
end
This line return item in your tractor_beam method will get run every time before getting to your if statement I think that is causing the problem.
Also you are not using the instance variable #item that you are created in the initialize method I think you might actually want something like this:
def tractor_beam(item)
disable_shield
#item = item.split('')
weight = 0
#item.each do |let|
weight += let.ord
end
if weight < 500
enable_shield
#inventory[#location] = #item
return true
else
return false
end
end
end
I don't understand the second line of the code below because of "obj = nil" in the first line.Given that, the second line seems to me that "obj" always becomes nil, return false and params[:id].to_i would be put into id_num. Could you tell me why it is written like this?
☆application_controller
def me? obj = nil
id_num = obj !=nil ? obj.member_id : params[:id].to_i
if session[:user_id] == id_num then
return true
else
return false
end
end
Declaring a method that has a parameter set to nil means that the parameter is optional.
def output_object_or_say_duck(obj=nil)
if obj
puts obj
else
puts 'Duck'
end
end
A good example of optional parameters as a design pattern is when you want default behavior that can be customized if necessary. A web request is a good example.
def make_web_request(website, parameters={}) # parameters OR empty hash
Net::HTTP.get("#{website}?#{ parameters.to_query }")
end
This line of code:
id_num = obj !=nil ? obj.member_id : params[:id].to_i
is a ternary operator which says if the object exists, assign id_num to the member_id attribute of obj, otherwise use param[:id].to_i (.to_i converts to an integer).
The obj = nil in the first line simply indicates that the default value of the obj parameter is nil. Meaning that if you don't call the method with any arguments, obj will be set to nil. So the me? method can take 0 or 1 arguments.
I have Coupon model and in this model file I have a suitable_for_use method.I want to list Coupons if coupon.suitable_for_use == true.Is there any short way to do this ? I wrote this code but it doesn't work.
#coupons = []
coupons = Coupon.all.each do |coupon|
if coupon.suitable_for_use
#coupons << coupon
end
end
#coupons = coupons
suitable_for_use method
def suitable_for_use
result = true
if is_used?
result = false
elsif self.start > Time.now.in_time_zone
result = false
elsif self.end < Time.now.in_time_zone
result = false
end
return result
end
The problem is your assigning twice to #coupons. The return value from each is the collection it was given. So your last line reassigns the original set of coupons returned by Coupon.all.
#coupons = Coupon.all.select(&:suitable_for_use)
If your not sure what that does, here's the expanded version.
#coupons = Coupon.all.select {|coupon| coupon.suitable_for_select}
Basically, select takes a block that it will iterate over and if the block returns true then it will add that element to the returned collection. So any coupon that returns false will not be returned by select.
The &:suitable_for_use is called a symbol to proc. It literally expands to the block in the second line and is pretty common in ruby one-liners.
I'm trying to trigger a warning when a price is entered too low. But for some reason, it always returns true and I see the warning regardless. I'm sure there something wrong in the way I'm doing this as I'm really new to RoR.
In model:
def self.too_low(value)
res = Class.find_by_sql("SELECT price ……. WHERE value = '#{value}'")
res.each do |v|
if #{value} < v.price.round(2)
return true
else
return false
end
end
end
In controller:
#too_low = Class.too_low(params[:amount])
if #too_low == true
flash[:warning] = 'Price is too low.'
end
I would write it somewhat different. You iterate over all items, but you are only interested in the first element. You return from inside the iteration block, but for each element the block will be executed. In ruby 1.9.2 this gives an error.
Also i would propose using a different class-name (Class is used to define a class)
So my suggestion:
Class YourGoodClassName
def self.too_low(amount)
res = YourGoodClassName.find_by_sql(...)
if res.size > 0
res[0].price.round(2) < 1.00
else
true
end
end
end
You can see i test if any result is found, and if it is i just return the value of the test (which is true or false); and return true if no price was found.
In the controller you write something like
flash[:warning] = 'Price is too low' if YourGoodClassName.too_low(params[:amount])