more conventional way to write this ruby - ruby

this ruby code works, but is there a more conventional or simplified way to write it:
options['host'] = begin
a == :jaxon ? 'jaxon-server16.jaxon.local' : 'doric-server5'
end
I just feel like the code is a smell but I cannot put my finger on it.
Thanks.

You don't need begin..end here.
options['host'] = a == :jaxon ? 'jaxon-server16.jaxon.local' : 'doric-server5'
I'd probably put parentheses around right side. Not necessary, just for clarity.
options['host'] = (a == :jaxon ? 'jaxon-server16.jaxon.local' : 'doric-server5')

Usually symbols are used as hash keys because they save memory and are a little faster for comparisons, and the begin..end block is not necessary. Thus it becomes:
options[:host] = (a == :jaxon ? 'jaxon-server16.jaxon.local' : 'doric-server5')
This is a relatively long while, in my head the following parses easier:
options[:host] = 'doric-server5'
options[:host] = 'jaxon-server16.jaxon.local' if a == :jaxon
An iteration on top of that, is that you have what appears to be semi-hardcoded values (jaxon-server16.jaxon.local and doric-server5). You should store these in constants or another data structure in order to have these gathered in one place. For instance, if doric-server5 becomes doric-server6 one day, you'll only have to change it in the top of a class or file somewhere. Furthermore, it makes the code easier to read since they now have more humane names of whatever they represent.
# somewhere else:
JAXON_SERVER = 'jaxon-server16.jaxon.local'
DORIC_SERVER = 'doric-server5'
options[:host] = DORIC_SERVER
options[:host] = JAXON_SERVER if a == :jaxon
Since we've dealt with the original motivation to make it two lines, we could go back to one, nice line:
options[:host] = (a == :jaxon ? JAXON_SERVER : DORIC_SERVER)
If you have a lot of these kinds of statements, you could make a server hash, where e.g. server[:jaxon] = 'jaxon-server16.jaxon.local', but if you just have two, two string constants are fine.
In some cases it's nicer to have the default option (in this case DORIC_SERVER) appear wherever it would default to that value, instead of setting the host to the default value directly. Hash#fetch takes two arguments: a key, and a value to default to, if that key doesn't exist.
options[:host] = JAXON_SERVER if a == :jaxon
# somewhere else:
options.fetch(:host, DORIC_SERVER)
Without more information, it's hard to say which approach is the best in your case. :-)

This is another way to write it
options['host'] = case a
when :jaxon
'jaxon-server16.jaxon.local'
else
'doric-server5'
end
It has a lot more lines but i like its readability. It also makes it easy to add more hosts:
options['host'] = case a
when :jaxon
'jaxon-server16.jaxon.local'
when :staging
'staging-server1'
else
'doric-server5'
end

If you want to surround it with something for readability, you can share a pair of parentheses with the Hash#store method.
options.store( "host",
a == :jaxon ? "jaxon-server16.jaxon.local" : "doric-server5"
)

Related

Expressing "equals" in pseudocode

I was just wondering if there is a special way of saying when something equals something. For example in python, if you declare something equals 2, you say something = 2, whereas when you check if something equals something else, you would say:
if something == somethingelse:
So my question is in pseudocode for algorithms if I'm checking to see if a entered password equals a stored password in an IF THEN ELSE ENDIF loop, would I use one or two equal signs:
WHILE attempts < 3
Get EnteredPassword
**IF EnteredPassword = StoredPassword THEN**
Validated = TRUE
ELSE
attempts = attempts + 1
ENDIF
ENDWHILE
Usually, pseudocode is very broad and every author has their own way of expressing it. As
Aziz has noted, usually x <- 1 is used for an assignment and x := x + 1 for an update. Read ':=' as 'becomes' instead of 'equals', however, they are interchangeably used. As for your question, both = and == are accepted answers, as long as it is clear to your reader what your intention is.
To express equals you use the equal mark symbol once, unlike in python where you use the symbol twice to compare two values (eg if variable == 'one'). An example syntax is:
variable = 'one'
WHILE variable = 'one' DO
SEND "hi" TO DISPLAY

Extending (Monkey Patching) a Binary Search for Array Class and Syntactic Sugar

I've been studying a few searching algorithms and my last problem comes down to binary searching. I watched a few youtube videos to understand the concept and then tried to solve the problem, but keep getting an endless loop error. I've looked through stack overflow, and reddit, and wherever Google would lead me, but I can't quite find a solution that fits my method of coding. Also, please excuse the term 'monkey patching', it's been brought to my attention that the technical term is called 'extending' so the fault lies on my instructors for teaching it to us as 'monkey patching'.
Here's my code:
class Array
def my_bsearch(target)
return nil if self.empty?
middle_idx = self.length/2
left = self.take(middle_idx)
right = self.drop(middle_idx + 1)
return middle_idx if self[middle_idx] == target
until self[middle_idx] == target || self.nil? == nil
if self[middle_idx] < target
right.my_bsearch(target)
elsif self[middle_idx] > target
left.my_bsearch(target)
end
end
end
end
I have a solution, but I don't want to just memorize it-- and I'm having trouble understanding it; as I'm trying to translate it, learn from it, and implement what I'm missing into my own code.
class Array
def my_bsearch(target)
return nil if size == 0
mid = size/2
case self[mid] <=> target
when 0
return mid
when 1
return self.take(mid).my_bsearch(target)
else
search_res = self.drop(mid+1).my_bsearch(target)
search_res.nil? ? nil : mid + 1 + search_res
end
end
end
I guess I understand case/when despite not use to using it. I've tried following it with debugger, but I think I'm hung up on what's going on in the ELSE section. The syntactic sugar, while making this obviously more concise than my logic, isn't straight-forward/clean to someone of my ruby literacy level. So, yeah, my ignorance is most of the problem I guess.
Is there someone who is a little more literate, and patient, able to help me break this down into something I can understand a bit better so I can learn from this?
First, take and drop have sufficiently similar interfaces that you don't actually want your + 1 for drop. It will disregard one element in the array if you do.
Next, self.nil? will always be false (and never nil) for instances of this class. In fact, .nil? is a method exactly to avoid having to ever compare against nil with ==.
You want self.empty?. Furthermore, with the exception of setters, in Ruby messages are sent to self by default. In other words, the only time self. is a useful prefix is when the message ends in = and operates as an lvalue, as in self.instance_var = 'a constant', since without the self., the tokens instance_var = would be interpreted as a local variable rather than an instance variable setting. That's not the case here, so empty? will suffice just as well as self.empty?
So I figured it out, and I decided to answer my own post in hopes to help someone else out if they run into this issue.
So, if I have an Array and the target is the middle_element, then it will report middle_element_idx. That's fine. What if the target is less than middle_element? It recursively searches the left-side of the original Array. When it finds it, it reports the left_side_idx. There's no problem with that because elements in an array are sequentially counted left to right. So, it starts at 0 and goes up.
But what if the target is on the right side of the middle element?
Well, searching for the right side is easy. Relatively the same logic as searching left. Done recursively. And it will return a target_idx if it's found on that right side --however that's the target's idx as it was found in the right-side array! So, you need to take that returned target_idx and add 1 to it and the original middle_element_idx. See below:
def my_bsearch(target)
return nil if self.empty?
middle_idx = self.length/2
left = self.take(middle_idx)
right = self.drop(middle_idx + 1)
if self[middle_idx] == target
return middle_idx
elsif self[middle_idx] > target
return left.my_bsearch(target)
else
searched_right_side = 1 + right.my_bsearch(target)
return nil if searched_right_side.nil? == true
return searched_right_side + middle_idx
end
end
end
Notice how many more lines this solution is? The spaceship operator used in conjunction with case/when and a ternary method will reduce the number of lines significantly.
Based on suggestions/feedback from Tim, I updated it to:
def my_bsearch(target)
return nil if empty?
middle_idx = self.length/2
left = self.take(middle_idx)
right = self.drop(middle_idx)
if self[middle_idx] == target
return middle_idx
elsif self[middle_idx] > target
return left.my_bsearch(target)
else
searched_right_side = right.my_bsearch(target)
return nil if searched_right_side.nil?
return searched_right_side + middle_idx
end
end
end

What programming patterns or strategy should I use to deal with small inconsistencies in data processing?

In the ruby gem I am writing I have to take in as input certain known query parameters and massage them into a query string and then use that constructed (url) string as a rest endpoint to retrieve that data.
Now there are some weird inconsistencies in inputs coming in and I am forking my code to normalize inputs into a consistent output.
def build_query(params, endpoint)
limit = Hash[limit: params[:limit] || 0]
skip = Hash[skip: params[:skip] || 0]
asc = Hash[asc: params[:asc] || ""]
desc = Hash[desc: params[:desc] || ""]
query = [limit, skip, asc, desc].select { |hash| hash.values.none? { |val| val == '' || val == 0 } }
encoded = query.map{ |q| q.to_query }.join("&")
references = build_references(params[:include]) || ""
query_string = references.empty? ? "#{endpoint}#{encoded}" : "#{endpoint}#{references}&#{encoded}"
end
You will see above that the references piece of the params are not handled the same way as the rest of the parameters. There are more slightly inconsistent edge cases coming soon. And the only way I know how to deal with these is to keep forking my code inside this function. It's going to get messy soon!
So how should I now refactor this code? Where should I go from here to manage this complexity? Should I use collaborating objects (ParamsBuilder or QueryManager) and some kind of polymorphism strategy?
I would like to keep my code simple and functional as much as possible.
plain = %i|limit skip asc desc| # plain values
built = { include: ->(input) { build_references(input) } } # procs
query = (plain | built).map do |p|
case p
when Symbol then params[p]
when Array then p.last.(params[p.first])
end
end.reject(&:blank?).map(&:to_query).join("&")
[endpoint, query].join
Basically, you have two types of parameters: those you are to pass through as is (like :limit,) and those, you are to transform (like :include.)
Former are just passed through, latter are transformed using the list of lambdas specified in the very beginning of this snippet.
Since you were using to_query in the original question, I suggest you use rails, hence you have blank? method on hand and there is no need to explicitly check for empty strings and/or zeroes.
In the last step, we reject blanks and join everything with an ampersand.

refactor this ruby database/sequel gem lookup

I know this code is not optimal, any ideas on how to improve it?
job_and_cost_code_found = false
timberline_db['SELECT Job, Cost_Code FROM [JCM_MASTER__COST_CODE] WHERE [Job] = ? AND [Cost_Code] = ?', job, clean_cost_code].each do |row|
job_and_cost_code_found = true
end
if job_and_cost_code_found == false then
info = linenum + "," + id + ",,Employees default job and cost code do not exist in timberline. job:#{job} cost code:#{clean_cost_code}"
add_to_exception_output_file(info)
end
You're breaking a lot of simple rules here.
Don't select what you don't use.
You select a number of columns, then completely ignore the result data. What you probably want is a count:
SELECT COUNT(*) AS cost_code_count FROM [JCM_MASTER__COST_CODE] WHERE [Job] = ? AND [Cost_Code] = ?'
Then you'll get one row that will have either a zero or non-zero value in it. Save this into a variable like:
job_and_cost_codes_found = timberline_db[...][0]['cost_code_count']
Don't compare against false unless you need to differentiate between that and nil
In Ruby only two things evaluate as false, nil and false. Most of the time you will not be concerned about the difference. On rare occasions you might want to have different logic for set true, set false or not set (nil), and only then would you test so specifically.
However, keep in mind that 0 is not a false value, so you will need to compare against that.
Taking into account the previous optimization, your if could be:
if job_and_cost_codes_found == 0
# ...
end
Don't use then or other bits of redundant syntax
Most Ruby style-guides spurn useless syntax like then, just as they recommend avoiding for and instead use the Enumerable class which is far more flexible.
Manipulate data, not strings
You're assembling some kind of CSV-like line in the end there. Ideally you'd be using the built-in CSV library to do the correct encoding, and libraries like that want data, not a string they'd have to parse.
One step closer to that is this:
line = [
linenum,
id,
nil,
"Employees default job and cost code do not exist in timberline. job:#{job} cost code:#{clean_cost_code}"
].join(',')
add_to_exception_output_file(line)
You'd presumably replace join(',') with the proper CSV encoding method that applies here. The library is more efficient when you can compile all of the data ahead of time into an array-of-arrays, so I'd recommend doing that if this is the end goal.
For example:
lines = [ ]
# ...
if (...)
# Append an array to the lines to write to the CSV file.
lines << [ ... ]
end
Keep your data in a standard structure like an Array, a Hash, or a custom object, until you're prepared to commit it to its final formatted or encoded form. That way you can perform additional operations on it if you need to do things like filtering.
It's hard to refactor this when I'm not exactly sure what it's supposed to be doing, but assuming that you want to log an error when there's no entry matching a job & code pair, here's what I've come up with:
def fetch_by_job_and_cost_code(job, cost_code)
timberline_db['SELECT Job, Cost_Code FROM [JCM_MASTER__COST_CODE] WHERE [Job] = ? AND [Cost_Code] = ?', job, cost_code]
end
if fetch_by_job_and_cost_code(job, clean_cost_code).none?
add_to_exception_output_file "#{linenum},#{id},,Employees default job and cost code do not exist in timberline. job:#{job} cost code:#{clean_cost_code}"
end

Ruby: Assign output of a function only if it does not return nil

When programming in Ruby I quite often have assignments like the following
test = some_function if some_function
With that assignments I want to assign the output of a function, but if it returns nil I want to keep the content of the variable. I know there are conditional assignments, but neither ||= nor &&= can be used here. The shortest way I found to describe the statement above is
test = (some_function or test)
Is there a better / shorter way to do this?
I don't think there's anything better than the last snippet you showed but note that or is used for flow control, use || instead:
test = some_function || test
It's usually better to assign new values to new names, the resulting code is easier to understand and debug since variables/symbols have the same value throughout the scope:
some_different_and_descriptive_name_here = some_function || test
I'd just add parentheses
(a = b) unless b.nil?
(a = b) if b
being inferior because if b is false then a remains as before
Keep in mind that this evaluates b twice, so if b is a function with side-effects (such as changing variables outside of its scope or printing) it will do that twice; to avoid this you must use
temp = b; (a = temp) unless temp.nil?
(which can, of course, be split into)
temp = b
(a = temp) unless temp.nil?

Resources