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.
Related
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
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've tried reading some tutorials on refactoring and I am struggling with conditionals. I don't want to use a ternary operator but maybe this should be extracted in a method? Or is there a smart way to use map?
detail.stated = if value[:stated].blank?
nil
elsif value[:stated] == "Incomplete"
nil
elsif value[:is_ratio] == "true"
value[:stated] == "true"
else
apply_currency_increment_for_save(value[:stated])
end
If you move this logic into a method, it can be made a lot cleaner thanks to early return (and keyword arguments):
def stated?(stated:, is_ratio: nil, **)
return if stated.blank? || stated == "Incomplete"
return stated == "true" if is_ratio == "true"
apply_currency_increment_for_save(stated)
end
Then...
detail.stated = stated?(value)
stated = value[:stated]
detail.stated = case
when stated.blank? || stated == "Incomplete"
nil
when value[:is_ratio] == "true"
value[:stated] == "true"
else
apply_currency_increment_for_save stated
end
What's happening: when case is used without an expression, it becomes the civilized equivalent of an if ... elsif ... else ... fi.
You can use its result, too, just like with if...end.
Move the code into apply_currency_increment_for_save
and do:
def apply_currency_increment_for_save(value)
return if value.nil? || value == "Incomplete"
return "true" if value == "true"
# rest of the code. Or move into another function if its too complex
end
The logic is encapsulated and it takes 2 lines only
I like #Jordan's suggestion. However, it seems the call is incomplete -- the 'is_ratio' parameter is also selected from value but not supplied.
Just for the sake of argument I'll suggest that you could go one step further and provide a class that is very narrowly focused on evaluating a "stated" value. This might seem extreme but it fits with the notion of single responsibility (the responsibility is evaluating "value" for stated -- while the 'detail' object might be focused on something else and merely makes use of the evaluation).
It'd look something like this:
class StatedEvaluator
attr_reader :value, :is_ratio
def initialize(value = {})
#value = ActiveSupport::StringInquirer.new(value.fetch(:stated, ''))
#is_ratio = ActiveSupport::StringInquirer.new(value.fetch(:is_ratio, ''))
end
def stated
return nil if value.blank? || value.Incomplete?
return value.true? if is_ratio.true?
apply_currency_increment_for_save(value)
end
end
detail.stated = StatedEvaluator.new(value).stated
Note that this makes use of Rails' StringInquirer class.
So in my rails form there are several rows of 2 textfields. For the form to save ok, at least one of the pair of textfields needs to be filled out.
So
nil nil
10 20
nil nil
nil nil
is valid.
This:
nil nil
nil nil
nil nil
nil nil
is invalid
This:
nil 10
nil nil
nil nil
nil nil
is invalid
Here is the method I am using to check all the fields (note that single_field and aggregate_field are strings and are the field names):
def no_values_present?(single_field, aggregate_field)
self.lo_item.lo_line_items.each do |item|
return false if "!item.#{single_field}".nil? && "!item.#{aggregate_field}".nil?
end
true
end
But I guess this doesn't work as it will return true or false several times and will determine that a row is invalid even though a previous row may have been valid.
I need an overall true or false.
How can this be achieved?
Try leveraging any? or none? from the Enumerable module.
Your code could be rewritten as
def no_values_present?(single_field, aggregate_field)
self.lo_item.lo_line_items.none? { |item|
!(item.send(single_field).nil?) && !(item.send(aggregate_field).nil?)
}
end
although I think that it would be clearer to have the condition be positive and to return true when there is a match found. I would write
def any_pairs_present?(single_field, aggregate_field)
self.lo_item.lo_line_items.any? { |item|
!(item.send(single_field).nil?) && !(item.send(aggregate_field).nil?)
}
end
Note that "!item.#{single_field}" will never be nil because it will always be a string! If you want to access instance fields dynamically then one way to do that is with send, but for other options you could look here which suggests the alternatives of instance_eval and instance_variable_get.
The function looks ok, but there seems to be syntax errors, I'd also make a few amendments:
def form_valid?(single_field, aggregate_field)
self.lo_item.lo_line_items.each do |item|
return true if !item.send(single_field).nil? && !item.send(aggregate_field)
end
false
end
What is the "Rails-way" or the "Ruby-way" of doing the following:
In my controller, I'm creating and instance of an Options class. It will be initialized with information in the params hash if the params hash exists. Otherwise, it will check the sessions hash for the information. Finally, it will initialize with defaults if neither params nor session has the data it needs. Here is how I'm doing it now (it works fine, but it seems a little ugly):
if params[:cust_options]
#options = CustomOptions.new( params[:cust_options] )
else
if session[:cust_options
#options = CustomOptions.new( session[:cust_options] )
else
#options = CustomOptions.new
end
end
session[:cust_options] = #options.to_hash
Like I said, everything is working fine, I'm just looking for a more idiomatically Ruby way of writing this block of code.
Update
This is what my code looks like now (thanks henning-koch and jdeseno):
#options = CustomOptions.new( params[:cust_options] || session[:cust_options] || {} )
If I leave the final condition (|| {}) off that line of code, what happens in my initialize method when neither params[:cust_options] nor session[:cust_options] are defined, or both are nil?
My initialize definition looks like this:
def initialize( options = {} )
# stuff happens ...
end
A shorter way to write this would be
#options = CustomOptions.new(params[:cust_options] || session[:cust_options])
Good luck.
You can use the 'or' operator to default:
#options = CustomOptions.new( session[:cust_options] || params[:cust_options] || {} )
You can try to put these codes in the controller that you have. Doing this will make the session key available for all the actions in the controller. It is also guaranteed that before any of the controller's action is called, the session value has been set.
# Place this under the controller class definition
before_filter :set_session_value
private
def set_session_value
session[:cust_options] = find_cust_options_value
end
def find_cust_options_value
return CustomOptions.new(params[:cust_options]) if params[:cust_options]
return CustomOptions.new(session[:cust_options]) if session[:cust_options]
return CustomOptions.new
end