What does this code in ruby do? - ruby-on-rails

I am new here so please be gentle with me and I'm still just a newbie in programming and especially in ruby language.
This is a combo box and I want to know where it goes after I change it or what function it calls. Can you please tell me where it goes or what it does? thanks
<p><%= f.select :done_ratio, ((0..100).step(1).to_a.collect {|r| ["#{r} %", r] }), :required => #issue.required_attribute?('done_ratio') %></p>

It is creating an HTML select (dropdown) box, with the values: 0 %, 1 %, 2 %, ..., 100 %.
The field will be submitted as part of a form. It may or may not be a required field, depending on the value of #issue.required_attribute?('done_ratio'). (This is presumably a method in the Issue model, which can be found in ./app/models/issue.rb.)
Breaking it down:
(0..100) -- This is creating Range object, from 0 to 100 (inclusive).
.step(1) -- This is not actually needed; you could delete it. But it's saying "step through the range 1 at a time" (which is the default anyway). It converts the Range to an Enumerator.
.to_a -- This is not actually needed; you could delete it. This is converting the Enumerator to an Array.
.collect {|r| ["#{r} %", r] } -- This is mapping the Array to a new list of arrays, like: [["0 %", 0], ["1 %", 1], ..., ["100 %", 100]]. (This method would also work perfectly fine on a Range or Enumerator object, since both classes include this method from the Enumerable module. Hence why the above two steps can both be removed!)
f.select :done_ratio, (...) -- This is creating an HTML select element called done_ratio, with the above names/values.

Related

What does parameter a and b mean in an array of elements in Ruby?

I'm practicing the sort function, the target is an array (ary) of a sentence. An example method I have seen is to build and use a block, and finally arrange the elements (words) in the array from short to long, and from a to z.
But I don't understand why there are two parameters a and b in this example, why should we find out a.length and b.length first? This is the original code:
ary = %w(
Ruby is a open source programming language with a afocus on simplicity and productivity.
)
call_num = 0
sorted = ary.sort do |a, b|
call_num += 1
a.length <=> b.length
end
puts "Sort results: #{sorted}" #=>["a", "a", "on", "is", "and", "Ruby", "open", "with", "afocus", "source", "language", "simplicity", "programming", "productivity."]
puts "Number of array elements: #{ary.length}" #=> 14
puts "Number of calls to blocks: #{call_num}" #=>30
To sort the following array of words by their length Ruby has to basically compare each word in the array to each other word (Note that this is not exactly how sorting works internally but in the context of this example we can assume that sorting works like this).
ary = %w(
Ruby is a open source programming language with a afocus on simplicity and productivity.
)
That means in the first step Ruby will need to compare the words Ruby and is and has to decide how to sort those two words, then is and a, then a and open.
Those two words in each step of the comparison are the two block parameters a and b. a.length <=> b.length will then tell Ruby how to sort those two parameters (words).
See Comparable and Enumerable#sort

Shorter way to convert an empty string to an int, and then clamp

I'm looking for a way to simplify the code for the following logic:
Take a value that is either a nil or an empty string
Convert that value to an integer
Set zero values to the maximum value (empty string/nil are converted to 0 when cast as an int)
.clamp the value between a minimum and a maximum
Here's the long form that works:
minimum = 1
maximum = 10_000
value = value.to_i
value = maximum if value.zero?
value = value.clamp(minimum, maximum)
So for example, if value is "", I should get 10,000. If value is "15", I should get 15. If value is "45000", I should get 10000.
Is there a way to shorten this logic, assuming that minimum and maximum are defined and that the default value is the maximum?
The biggest problem I've had in shortening it is that null-coalescing doesn't work on the zero, since Ruby considers zero a truthy value. Otherwise, it could be a one-liner.
you could still do a one-liner with your current logic
minimum, maximum = 1, 10_000
value = ( value.to_i.zero? ? maximum: value.to_i ).clamp(minimum, maximum)
but not sure if your issue is that if you enter '0' you want 1 and not 10_000 if so then try this
minimum, maximum = 1, 10_000
value = (value.to_i if Float(value) rescue maximum).clamp(minimum, maximum)
Consider Fixing the Input Object or Method
If you're messing with String objects when you expect an Integer, you're probably dealing with user input. If that's the case, the problem should really be solved through input validation and/or looping over an input prompt elsewhere in your program rather than trying to perform input transformations inline.
Duck-typing is great, but I suspect you have a broken contract between methods or objects. As a general rule, it's better to fix the source of the mismatch unless you're deliberately wrapping some piece of code that shouldn't be modified. There are a number of possible refactorings and patterns if that's the case.
One such solution is to use a collaborator object or method for information hiding. This enables you to perform your input transformations without complicating your inline logic, and allowing you to access the transformed value as a simple method call such as user_input.value.
Turning a Value into a Collaborator Object
If you are just trying to tighten up your current method you can aim for shorter code, but I'd personally recommend aiming for maintainability instead. Pragmatically, that means sending your value to the constructor of a specialized object, and then asking that object for a result. As a bonus, this allows you to use a default variable assignment to handle nil. Consider the following:
class MaximizeUnsetInputValue
MIN = 1
MAX = 10_000
def initialize value=MAX
#value = value
set_empty_to_max
end
def set_empty_to_max
#value = MAX if #value.to_i.zero?
end
def value
#value.clamp MIN, MAX
end
end
You can easily validate that this handles your various use cases while hiding the implementation details inside the collaborator object's methods. For example:
inputs_and_expected_outputs = [
[0, 10000],
[1, 1],
[10, 10],
[10001, 10000],
[nil, 10000],
['', 10000]
]
inputs_and_expected_outputs.map do |input, expected|
MaximizeUnsetInputValue.new(input).value == expected
end
#=> [true, true, true, true, true, true]
There are certainly other approaches, but this is the one I'd recommend based on your posted code. It isn't shorter, but I think it's readable, maintainable, adaptable, and reusable. Your mileage may vary.

How do I naturally sort an array of multi-dot numbers in ruby [duplicate]

How do I sort a list of versions in Ruby? I've seen stuff about natural sort, but this is a step beyond that.
Input is a bunch of strings like this:
input = ['10.0.0b12', '10.0.0b3', '10.0.0a2', '9.0.10', '9.0.3']
I can almost do it with the naturally gem:
require 'naturally'
Naturally.sort(input)
=> ["9.0.3", "9.0.10", "10.0.0a2", "10.0.0b12", "10.0.0b3"]
Problem: 10.0.0b3 is sorted after 10.0.0b12; 10.0.0b3 should be first.
Anyone have a way that works? Other languages are helpful too!
Ruby ships with the Gem class, which knows about versions:
ar = ['10.0.0b12', '10.0.0b3', '10.0.0a2', '9.0.10', '9.0.3']
p ar.sort_by { |v| Gem::Version.new(v) }
# => ["9.0.3", "9.0.10", "10.0.0a2", "10.0.0b3", "10.0.0b12"]
If you interpret this as "sort by each segment of digits", then you the following will handle your example input above:
input.map{ |ver| ver.split(%r{[^\d]+}).map(&:to_i) }.zip(input).sort.map(&:last)
=> ["9_0", "9_1", "10_0b3", "10_0b12"]
That is,
for each value, eg 10_0b3
split on any length of non-digit characters, eg ["10","0","3"]
cast each digit segment to integer, eg [10,0,3]
zip with original input, yields [[[10, 0, 12], "10_0b12"], [[10, 0, 3], "10_0b3"], [[9, 0], "9_0"], [[9, 1], "9_1"]]
sort, by virtue of [10,0,3] < [10,0,12]
get last value of each element, which is the original input value which corresponds to each processed sortable value
Now granted, this is still quite custom -- version numbers as simple as "9_0a" vs "9_0b" won't be handled, both will appear to be [9,0] -- so you may need to tweak it further, but hopefully this starts you down a viable path.
EDIT: Example input above changed, so I changed the regex to make sure the digit-matching is greedy, and with that it still holds up:
irb(main):018:0> input = ['10.0.0b12', '10.0.0b3', '9.0.10', '9.0.3']
=> ["10.0.0b12", "10.0.0b3", "9.0.10", "9.0.3"]
irb(main):025:0> input.map{ |ver| ver.split(%r{[^\d]+}).map(&:to_i) }.zip(input).sort.map(&:last)
=> ["9.0.3", "9.0.10", "10.0.0b3", "10.0.0b12"]
In the specific case that you are working with NuGet and want to parse, compare or sort by NuGet's peculiar own versioning scheme from Ruby code, there is now this:
https://rubygems.org/gems/nuget_versions
I created it specifically to solve this problem. NuGet's version numbers are a bit weird, they are a superset of SemVer that also permits the use of 4 components instead of 3.

Ruby method returns hash values in binary

I wrote a method that takes six names then generates an array of seven random numbers using four 6-sided dice. The lowest value of the four 6-sided dice is dropped, then the remainder is summed to create the value. The value is then added to an array.
Once seven numbers have been generated, the array is then ordered from highest to lowest and the lowest value is dropped. Then the array of names and the array of values are zipped together to create a hash.
This method ensures that the first name in the array of names receives the highest value, and the last name receives the lowest.
This is the result of calling the method:
{:strength=>1, :dexterity=>1, :constitution=>0, :intelligence=>0, :wisdom=>0, :charisma=>1}
As you can see, all the values I receive are either "1" or "0". I have no idea how this is happening.
Here is the code:
module PriorityStatGenerator
def self.roll_stats(first_stat, second_stat, third_stat, fourth_stat, fifth_stat, sixth_stat)
stats_priority = [first_stat, second_stat, third_stat, fourth_stat, fifth_stat, sixth_stat].map(&:to_sym)
roll_array = self.roll
return Hash[stats_priority.zip(roll_array)]
end
private
def self.roll
roll_array = []
7.times {
roll_array << Array.new(4).map{ 1 + rand(6) }.sort.drop(1).sum
}
roll_array.reverse.delete_at(6)
end
end
This is how I'm calling the method while I'm testing:
render plain: PriorityStatGenerator.roll_stats(params[:prioritize][:first_stat], params[:prioritize][:second_stat], params[:prioritize][:third_stat], params[:prioritize][:fourth_stat], params[:prioritize][:fifth_stat], params[:prioritize][:sixth_stat])
I added require 'priority_stat_generator' where I'm calling the method, so it is properly calling it.
Can someone help me make it return proper values between 1 and 18?
Here's a refactoring to simplify things and use an actually random number generator, as rand is notoriously terrible:
require 'securerandom'
module PriorityStatGenerator
def self.roll_stats(*stats)
Hash[
stats.map(&:to_sym).zip(self.roll(stats.length).reverse)
]
end
private
def self.roll(n = 7)
(n + 1).times.map do
4.times.map { 1 + SecureRandom.random_number(6) }.sort.drop(1).inject(:+)
end.sort.last(n)
end
end
This makes use of inject(:+) so it works in plain Ruby, no ActiveSupport required.
The use of *stats makes the roll_stats function way more flexible. Your version has a very rigid number of parameters, which is confusing and often obnoxious to use. Treating the arguments as an array avoids a lot of the binding on the expectation that there's six of them.
As a note it's not clear why you're making N+1 roles and then discarding the last. That's the same as generating N and discarding none. Maybe you meant to sort them and take the N best?
Update: Added sort and reverse to properly map in terms of priority.
You need to learn to use IRB or PRY to test snippets of your code, or better, learn to use a debugger. They give you insight into what your code is doing.
In IRB:
[7,6,5,4,3,2,1].delete_at(6)
1
In other words, delete_at(6) is doing what it's supposed to, but that's not what you want. Instead, perhaps slicing the array will behave more like you expect:
>> [7,6,5,4,3,2,1][0..-2]
[
[0] 7,
[1] 6,
[2] 5,
[3] 4,
[4] 3,
[5] 2
]
Also, in your code, it's not necessary to return a value when that operation is the last logical step in a method. Ruby will return the last value seen:
Hash[stats_priority.zip(roll_array)]
As amadan said, I can't see how you are getting the results you are, but their is a definite bug in your code.
The last line in self.roll is the return value.
roll_array.reverse.delete_at(6)
Which is going to return the value that was deleted. You need to add a new lines to return the roll_array instead of the delete_at value. You are also not sorting your array prior to removing that last item which will give you the wrong values as well.
def self.roll
roll_array = []
7.times {
roll_array << Array.new(4).map{ 1 + rand(6) }.sort.drop(1).sum
}
roll_array.sort.drop(1)
roll_array
end

are there any additional inject shorthand

I recently ran into this issue:
I've always used inject like so (i knew that (0) part is optional and can be omitted)
array = [13,23,13]
#=> [13, 23, 13]
array.inject(0) { |sum,i| sum+i }
#=> 49
By chance i found out that you can use:
array.inject(:+)
#=> 49
array.inject(:-)
#=> -23
array.inject(:*)
#=> 3887
array.inject(:/)
#=> 0
Googling on the issue i found a nice article on inject, but no mentioning there about what i've tried....
Can anyone explain to me or give some info about these inject commands that I've just used?
From the doc on Enumerable#inject:
... If you specify a symbol instead, then each element in the collection will be passed to the named method of memo. In either case, the result becomes the new value for memo. At the end of the iteration, the final value of memo is the return value for the method.
If you do not explicitly specify an initial value for memo, then uses the first element of collection is used as the initial value of memo.
So, if you specify a symbol, it treats it as a method name and invokes this method on every element of the enumerable, replacing memo as stated above. Now, the math operators (+-*/) are just methods, nothing else. These lines produce identical result:
13 + 23 # => 36
13.+(23) # => 36
13.send(:+, 23) # => 36
When you pass a symbol to inject or reduce it uses the third form to dynamically apply that operator to elements:
[1,2,3].inject(:+) # => 6
This shorthand can be used with methods other than operators as well:
[{"a"=>1}, {"b"=>2}].inject(:merge) # => {"a"=>1, "b"=>2}

Resources