My script currently accepts ActiveSupport date string as a command line argument:
my_script --mindate 1.day
Inside my script I am using eval to store it into my config
MyScript.configuration.min_date = eval(min_date_string)
I understand that this is extremely dodgy and insecure as anything can be passed to eval, but what are my alternatives?
You want time durations? I suppose you could use chronic_duration.
my_script --mindate "1 day"
MyScript.configuration.min_date = ChronicDuration.parse(min_date_string)
But since it's natural language heuristics, it becomes not entirely well defined exactly what sorts of strings it will recognize. But it will do fancy things like "1 day and four hours".
Or you could write your own very simple parser/interpreter for the argument. Just split on a space (for "1 day" type of input) or a period (for "1.day") type of input. Recognize a few words in the second position ("hour", "minute" "day", "month", "year"), translate them to seconds, multiply the number by the translated-to-seconds word. A dozen or so lines of ruby probably.
Or you could even take advantage of the ActiveSupport feature that supports things like "1.day" to make it even easier.
str = "11 hours"
number, unit = str.split(' ')
number.to_i.send(unit)
That would let the command line user send any method they want to a number. I'm not sure it matters. For that matter, I'm not sure if the original eval really matters or not -- but I agree with you it's bad practice. For that matter, probably so is send on user input, although not quite as bad.
Or you could just make them send in the raw number of seconds and calulcate it themselves.
my_script --mindate 86400
You realize 1.day just ends up being converted to the number of seconds in a standard day, right? I'm not sure why you're calling a number of seconds "mindate", but that's your business!
edit Or yet another alternative, make them do:
my_script --mindays 2 --minhours 4 --minminutes 3
or something.
How is your script being called? Is this always going to be called by a user with an account on whatever machine is running it? Is this somehow going to get called by a web service, or in a way that someone without access to the machine would be able to call it remotely with their own arguments?
If it's only going to be called by users, and those users already have access to the ruby command or irb, then you're not enabling them to do anything that they can't already do by calling eval.
If it's called remotely, you should probably not be using eval. Or, a quick and dirty solution could be to match patterns that you will eval with a regex. Something like /^[0-9]+(\.[a-z_0-9]+){,2}$/ would ensure that it's within 2 method calls of a Fixnum literal before evaluating.
Related
I would like to do something similar:
seconds=Time.parse("0:26:29.489").magic{|z| z.hour+z.min+z.sec+z.nsec.fdiv(1_000_000)}
to convert a timestamp into seconds (with fractions too), instead of writing:
d=Time.parse("0:26:29.489")
seconds=d.hour+d.min+d.sec+d.nsec.fdiv(1_000_000)
to spare a temporary "d" variable. But what should I use for "magic" if any?
Ruby has tap, but that won't help you here. What you want is something that would be called pipe, but sadly it's not there. At least not without a gem that monkey patches Object. Though I think it should be.
You can create a lambda and immediately call it, which will avoid the intermediate variable (or at least contain it within the lambda's block scope, as in your magic example), but I'm not sure you gain much, and would probably stick with what you have. The lambda approach would look like this:
# will return the value for "seconds"
->(d) { d.hour+d.min+d.sec+d.nsec.fdiv(1_000_000) }.(Time.parse("0:26:29.489"))
If the following is not the best style, what is for the equivalent expression?
if (some_really_long_expression__________ && \
some_other_really_long_expression)
The line continuation feels ugly. But I'm having a hard time finding a better alternative.
The parser doesn't need the backslashes in cases where the continuation is unambiguous. For example, using Ruby 2.0:
if true &&
true &&
true
puts true
end
#=> true
The following are some more-or-less random thoughts about the question of line length from someone who just plays with Ruby. Nor have I had any training as a software engineer, so consider yourself forewarned.
I find the problem of long lines is often more the number of characters than the number of operations. The former can be reduced by (drum-roll) shortening variable names and method names. The question, of course, is whether the application of a verbosity filter (aka babbling, prattling or jabbering filter) will make the code harder to comprehend. How often have you seen something fairly close to the following (without \)?
total_cuteness_rating = cats_dogs_and_pigs.map {|animal| \
cuteness_calculation(animal)}.reduce {|cuteness_accumulator, \
cuteness_per_animal| cuteness_accumulator + cuteness_per_animal}
Compare that with:
tot_cuteness = pets.map {|a| cuteness(a)}.reduce(&:+)
Firstly, I see no benefit of long names for local variables within a block (and rarely for local variables in a method). Here, isn't it perfectly obvious what a refers to in the calculation of tot_cuteness? How good a memory do you need to remember what a is when it is confined to a single line of code?
Secondly, whenever possible use the short form for enumerables followed by a block (e.g, reduce(&:+)). This allows us to comprehend what's going on in microseconds, here as soon as our eyes latch onto the +. Same, for .to_i, _s or _f. True, reduce {|tot, e| tot + e} isn't much longer, but we're forcing the reader's brain to decode two variables as well as the operator, when + is really all it needs.
Another way to shorten lines is to avoid long chains of operations. That comes at a cost, however. As far as I'm concerned, the longer the chain, the better. It reduces the need for temporary variables, reduces the number of lines of code and--possibly of greatest importance--allows us to read across a line, as most humans are accustomed, rather than down the page. The above line of code reads, "To calculate total cuteness, calculate each pet's cuteness rating, then sum those ratings". How could it be more clear?
When chains are particularly long, they can be written over multiple lines without using the line-continuaton character \:
array.each {|e| blah, blah, ..., blah
.map {|a| blah, blah, ..., blah
.reduce {|i| blah, blah, ..., blah }
}
}
That's no less clear than separate statements. I think this is frequently done in Rails.
What about the use of abbreviations? Which of the following names is most clear?
number_of_dogs
number_dogs
nbr_dogs
n_dogs
I would argue the first three are equally clear, and the last no less clear if the writer consistently prefixes variable names with n_ when that means "number of". Same for tot_, and so on. Enough.
One approach is to encapsulate those expressions inside meaningful methods. And you might be able to break it into multiple methods that you can later reuse.
Other then that is hard to suggest anything with the little information you gave. You might be able to get rid of the if statement using command objects or something like that but I can't tell if it makes sense on your code because you didn't show it.
Ismael answer works really well in Ruby (there may be other languages too) for 2 reasons:
Ruby has very low overhead to creating methods due to lack of type
definition
It allows you to decouple such logic for reuse or future adaptability and testing
Another option I'll toss out is create logic equations and store the result in a variable e.g.
# this are short logic equations testing x but you can apply same for longer expressions
number_gt_5 = x > 5
number_lt_20 = x < 20
number_eq_11 = x == 11
if (number_gt_5 && number_lt_20 && !number_eq_11)
# do some stuff
end
"Inches/yard: #{12*3}"
"#{"Tora! "*3}"
The second example is confusing, unless you remember that everything is an object in
Ruby (yes, even string literals! They are of class String.). Since the string literal
creates a String object, you can act on it just like any other object. In this case,
multiplying a string by 3 simply does what you would think: makes three copies of the
string.
I read the above para in one of the ruby book. The first line says The second example is confusing, unless you remember that everything is an object in Ruby. What is there in the second example that i should remember ,everything is an object in ruby ? Isn't it just a feature that multiplying by 3 will print tora three times ?
I don't exactly understand what does the author want me to understand from the above paragraph
Well, yes you can consider it a feature maybe, but what the author is perhaps trying to explain (although not very clearly at least in this one paragraph), is that what actually is happening is this:
"Tora !" is an object of class String ("everything is an object")
"you can act on it just like any other object", meaning:
"you can call any method on it just like any other object".
In this case you are calling the method * (multiply).
So what ACTUALLY is happening is that the "Tora !" String gets called in a fashion like this:
"Tora ! ".*(3)
=> "Tora ! Tora ! Tora ! "
You see? The operator * is just a method on the String object.
In many simpler languages operators are actually "baked into" the language itself, and do not operate on the targets as method calls.
If you're not used to other languages you might not find it all that remarkable, since in Ruby it's just a normal everyday thing. You just never need to type 1.+(2), Ruby does it for you automatically when you type 1 + 2.
So this is what the author wants you to remember - all operators and operations are just essentially method calls on other objects.
"Tora! a" is an instance of the String class. You can call methods on it. This isn't possible in many languages, e.g. PHP.
('*' is just the method name)
"#{"Tora! "*3}"
"Isn't it just a feature that multiplying by 3 will print tora three times ?".
That is inexact. There is no such thing as a multiplying operator in Ruby. There is a method ".*" (and the parser treats "*" the same way) defined on both String and Numeric objects (but both don't print anything). They do very different things, but the result is what you'd expect.
Well, maybe this was not what the author wanted you to understand, it is important anyway.
Not sure if this is possible but can I call a method from an irb shell with spaces between parameters rather than commas (don't ask) ? Lets say I have a method
def start_band(member1, member2, member3, member4)
#do something
end
And then I call it like the following:
irb>> start_band "John" "Paul" "George" "Ringo"
EDIT: Would it be possible to detect every keypress instead?
No, you can't do that. Not with strings anyway.
No.
You could use something like treetop to write a really simple DSL, or just play monkey-parsing games, but that won't solve your exact question.
The other obvious answer is this, which also fails:
irb>> start_band %W(John Paul George Ringo)
Creating an irb-like CLI isn't difficult, and may be adequate, depending on what your actual requirements are.
There is actually a very easy way to get rid of the commas. You can even get rid of the quotes, too:
def start_band(members)
#members is an array
end
start_band %w(John Paul George Ringo)
The limitation is that you can't use spaces inside your strings, and you still need start-end terminations (can use other characters instead of parenthesis though).
Durr! I really approached this the wrong way. I simply needed to run
#members = gets
to allow the input as required. Thanks for the responses nonetheless.
Feel free to delete this topic if it's discussed or quite obvious. I hail from C# background and I'm planning to learn Ruby. Everything I read about it seems quite intriguing. But I'm confused over this basic philosophy of Ruby that "there's more than one way to do one thing". Can someone provide 2 or 3 simple arithmetic or string examples to make this point clear, like if its about the syntaxes or logics etc.
Thanks
"More than one way of doing something" means having the choice of doing something the way you want it. That way you can use various programming styles, no matter what background you're coming from.
Iteration using for vs. blocks
You can iterate over an array of things like so. This is pretty basic, and if you're from a Java background, this feels kind of natural.
for something in an_array
print something
end
A more Ruby-like way would be the following:
an_array.each do |something|
print something
end
The first is a rather well known way of doing things. The second one is using blocks, a very powerful concept that you'll find in many Ruby idioms. Basically, the array knows how to iterate over its contents, so you can modify this and add something like:
an_array.each_with_index do |something, index|
print "At #{index}, there is #{something}"
end
You could have done it like this too, but now you see that the above one looks easier:
index = 0
for something in an_array
print "At #{index}, there is #{something}"
index += 1
end
Passing arguments as usual or using Hashes
Normally, you would pass arguments like so:
def foo(arg1, arg2, arg3)
print "I have three arguments, which are #{arg1}, #{arg2} and #{arg3}"
end
foo("very", "easy", "classic")
=> "I have three arguments, which are very easy and classic"
However, you may also use a Hash to do that:
def foo(args)
print "I have multiple arguments, they are #{args[:arg1]}, #{args[:arg2]} and #{args[:arg3]}"
end
foo :arg1 => "in a", :arg2 => "hash", :arg3 => "cool"
=> "I have three arguments, which are in a hash and cool"
The second form is one used excessively by Ruby on Rails. The nice thing is that you now have named parameters. When you are passing them, you will more easily remember what they are used for.
It means a lot of confusion, style wars, and bugs due to subtle differences, all in the name of freedom of choice.
A somewhat trivial example is the use of alias/alias_method (also note that there are two similar ways for almost the same thing, e. g. alias versus alias_method).
Consider that you are working in a project and you forgot which API to use.
What was the name of the method again?
Well, you can just remain within the domain logic of your program at hand, and continue to work with it the way you want to; then you are going to simply add an alias in the main entry point of your other program.
People can use by default .collect or they can use .map, it makes little difference what you personally would use (I use .map since it is shorter).
The use of aliases helped me because after some months, I often can not remember how to use something. Yes, I could look it up, but why would I have to bother anyway? I can just use an alias instead. (Note that I do try to remain as simple as possible with aliases and APIs.)