Does Ruby have something similar to .=, like +=? - ruby

Since one can do:
a += 1
I was thinking whether one can also do something similar to:
a .= 1
The use case would be, for example, with ActiveRecord:
query = Model
query .= where(name: 'John') # instead of query = query.where(name: 'John')
Is this possible somehow?

Nope, ruby does not have anything like this. Only certain "compound operators" are allowed by Ruby syntax and this operator is not among them.
However, there might be workarounds under specific circumstances (not this one, though). If, say, you had an array, then instead of
ary = ary.select { ... } if foo
ary = ary.select { ... } if bar
ary = ary.compact
you'd be able to do
ary.select! { ... } if foo
ary.select! { ... } if bar
ary.compact!
(this can have unintended consequences, yes, because in-place mutation is dangerous in general. But in some cases it is desirable. Don't do it to shorten your code.)

Ruby is able to detect line continuations automatically:
query = Model
.where(name: 'John')
.select(:first_name)
This is equivalent to this (with the dots at the end):
query = Model.
where(name: 'John').
select(:first_name)
Note that due to the way lines are evaluated in IRB, only the second syntax (with the dots at the end of the line) works as intended there. With the first example, IRB would evaluate the line as soon as it sees the newline. In a Ruby script, both options work quite well.
There apply different style considerations here, with people having different opinions on which one is best.
Generally, this approach requires that the combined line is syntactically equivalent to a single line. You can't use inline conditions that way. This would thus be invalid syntax:
query = Model
.where(name: 'John') if some_condition
.select(:first_name)
If you require these conditions, it is just fine to assign intermediate results to a local variable as shown by Sergio Tulentsev in his answer. You'll often see this and it is not a code smell at all.

Related

Overwrite a variable without repeating the name

Is there a method to overwrite variable without copying its name? For example, when I want to change my_var = '3' to an integer, I must do something like this:
my_var = my_var.to_i
Is there way to do this without copying variable's name? I want to do something like this:
my_var = something_const.to_i
For numbers there exists +=, -= etc, but is there universal way to do this for all methods ?
There is no way to covert a string to an integer like that, without repeating the variable name. Methods such as String#upcase! and Array#flatten! work by mutating the object; however, it is not possible to define such a method like String#to_i! because we are converting the object to an instance of a different class.
For example, here is a (failed) attempt to define such a method:
# What I want to be able to do:
# my_var = "123"
# my_var.to_i! # => my_var == 123
class String
def to_i!
replace(Integer(self))
end
end
my_var = "123"
my_var.to_i! # TypeError: no implicit conversion of Fixnum into String
...And even if this code were valid, it would still offer no performance gain since a new object is still being created.
As for your examples of += and -=, these are in fact simply shorthand for:
x += 1
# Is equivalent to:
x = x + 1
So again, there is no performance gain here either; just slightly nicer syntax. A good question to ask is, why doesn't ruby support a ++ operator? If such an operator existed then it would offer performance gain... But I'll let you research for yourself why this is missing from the language.
So to summarise,
is there universal way to do this for all methods?
No. The special operators like +=, -=, |= and &= are all predefined; there is no "generalised" version such as method_name=.
You can also define methods that mutate the object, but only when appropriate. Such methods are usually named with a !, are called "bang-methods", and have a "non-bang" counterpart. On String objects, for example, there is String#capitalize! (and String#capitalize), String#delete! (and String#delete), String#encode! (and String#encode), .... but no String#to_i! for the reasons discussed above.

Rails: control ordering of query parameters in url_for?

I'd like to generate a URL where the "p=1" query param appears at the end of the URL, like:
/path?foo=X&bar=Y&p=1
Is it possible to control the ordering of query parameters when generating URLs via:
url_for(params.merge({ p: page_num }))
?
Update:
I tried ChuckE's suggestion below. It turns out that in Ruby 1.9 Hashes are already ordered, so the code in ActiveSupport::OrderedHash is effectively no-op'd. You can verify with Ruby 1.9 that order is preserved:
>> h = {one: 1, two: 2, three: 3 }
{:one=>1, :two=>2, :three=>3}
>> f = h.except(:one)
{:two=>2, :three=>3}
>> f[:one] = 1
1
>> f
{:two=>2, :three=>3, :one=>1}
However, url_for still puts the "p" param first. It seems that any potential solution will need to address how url_for iterates the hash.
After further digging, I see that what's happening is that url_for is actually sorting the parameters by key lexicographically, independent of their insertion order in the hash. Apparently this is being done to aid caching, since URL params are often used for page cache keys.
In short, you can't do it without patching Hash, specifically, you need to override activesupport/core_ext/object/to_param.rb so that Hash#to_param does not call .sort on the return value.
Related question: How to generate custom sorted query string URL in Rails link_to?.
First question is: why would you need something like that? The order which the parameters appear in the url in doesn't influence the way they are fetched by the server, since they are basic key/value associations. So, no matter where the parameter appears, it will always be recognized by the server.
Nonetheless, to answer your question, yes, it is possible. You just have to use ordered hashes. They are available through active support.
opts = OrderedHash.new
opts[:foo] = 'X'
opts[:bar] = 'Y'
opts[:p] = 1
your_helper_url(opts)
Should do the trick for you.

Ruby: how to find the next match in an array

I have to search an item in an array and return the value of the next item. Example:
a = ['abc.df','-f','test.h']
i = a.find_index{|x| x=~/-f/}
puts a[i+1]
Is there any better way other than working with index?
A classical functional approach uses no indexes (xs.each_cons(2) -> pairwise combinations of xs):
xs = ['abc.df', '-f', 'test.h']
(xs.each_cons(2).detect { |x, y| x =~ /-f/ } || []).last
#=> "test.h"
Using Enumerable#map_detect simplifies it a litte bit more:
xs.each_cons(2).map_detect { |x, y| y if x =~ /-f/ }
#=> "test.h"
The reason something like array.find{something}.next doesn't exist is that it's an array rather than a linked list. Each item is just it's own value; it doesn't have a concept of "the item after me".
#tokland gives a good solution by iterating over the array with each pair of consecutive items, so that when the first item matches, you have your second item handy. There are strong arguments to be made for the functional style, to be sure. Your version is shorter, though, and I'd argue that yours is also more quickly and easily understood at a glance.
If the issue is that you're using it a lot and want something cleaner and more to the point, then of course you could just add it as a singleton method to a:
def a.find_after(&test)
self[find_index(&test).next]
end
Then
a.find_after{|x| x=~/-f/}
is a clear way to find the next item after the first match.
All of that said, I think #BenjaminCox makes the best point about what appears to be your actual goal. If you're parsing command line options, there are libraries that do that well.
I don't know of a cleaner way to do that specific operation. However, it sure looks like you're trying to parse command-line arguments. If so, I'd recommend using the built-in OptionParser module - it'll save a ton of time and hair-pulling trying to parse them yourself.
This article explains how it works.
Your solution working with indexes is fine, as others have commented. You could use Enumerable#drop_while to get an array from your match on and take the second element of that:
a = ['abc.df','-f','test.h']
f_arg = a.drop_while { |e| e !~ /-f/ }[1]

Need help breaking up Ruby code into methods

I am writing a program to better learn to program and I wish to use RSpec so that I can learn that as well. However, as is, the code isn't particularly RSpec friendly, so I need to break it up into methods so that I can test it.
I don't need anyone to write the code for me, but perhaps explain how I can break it up. I am new to programming and this kind of thing (breaking things up into methods) is a really difficult concept for me.
Here's what I have:
if params[:url] != ''
url = params[:url] #line created so I can return url more easily (or, in general)
words = params[:word].gsub("\n", ",").delete("\r").split(",") #.delete redundant?
words.reject!(&:empty?)
words.each(&:lstrip!)
return "#{words}", "#{url}" #so that I can return url, not sure how to do that yet
end
The code is a SERP checker, it takes a url and keywords and checks their location in the search engines.
For url, it'll just be the url of the website the user wishes to check... for word, it would be the keywords they wish to check their site against in Google.. a user may fill out the input form like so:
Corn on the cob,
Fibonacci,
StackOverflow
Chat, Meta, About
Badges
Tags,,
Unanswered
Ask Question
Your code takes a sloppy string and turns it into a clean array. You first clean up the string, then you polish the array. You could define methods for these actions.
def clean_up_words(str)
#code to clean str
str
end
def clean_up_list(arr)
#code to clean arr
arr
end
dirty_list = clean_up_words( params[:word]).split(',')
clean_list = clean_up_list( dirty_list )
def foo params
url = params[:url]
url.empty? ? nil : [params[:word].scan(/[^\s\r,]+/), url]
end
You are assigning url = params[:url]. If you are going to do that, you should do it before other places where you refer to the same thing to reduce the amount of calling [] on param.
You have several conditions on the words to be extracted. (a) Either split by "\n", ",", "\r", (b) the word should not be of 0 length, (c) white characters should be stripped off. All of this can be put together as scan(/[^\s\r,]+/).
You want to return two variables when url is not empty. Use an array in that case.

Summarize object area with a Hash in Ruby

require 'sketchup'
entities = Sketchup.active_model.entities
summa = Hash.new
for face in entities
next unless face.kind_of? Sketchup::Face
if (face.material)
summa[face.material.display_name] += face.area
end
end
I'm trying to get the structure in the array as such:
summa { "Bricks" => 500, "Planks" => 4000 }
By the way, I'm making a ruby script for Google Sketchup
But if I run this code I only get
Error: #<NoMethodError: undefined method `+' for nil:NilClass>
C:\Program Files (x86)\Google\Google SketchUp 7\Plugins\test.rb:17
C:\Program Files (x86)\Google\Google SketchUp 7\Plugins\test.rb:14:in `each'
C:\Program Files (x86)\Google\Google SketchUp 7\Plugins\test.rb:14
C:\Program Files (x86)\Google\Google SketchUp 7\Plugins\test.rb:8:in `call'
As I'm used to using PHP and just doing $array['myownassoc'] += bignumber;
But I guess this isn't the right approach when using Ruby?
So any help in how I need to go would be nice.
The problem is this:
summa[face.material.display_name] += face.area
This is (roughly) equivalent to
summa[face.material.display_name] = summa[face.material.display_name] + face.area
However, you start out with summa as an empty hash:
summa = Hash.new
Which means that whenever you encounter a specific material for the first time (and obviously, this is going to be already the case in the very first iteration of the loop), summa[face.material.display_name] simply doesn't exist. So, you are trying to add a number to something that doesn't exist, which obviously cannot work.
The quick fix would be to just initialize the hash with a default value, so that it returns something useful instead of nil for a non-existing key:
summa = Hash.new(0)
There are, however, a lot of other improvements that could be made to the code. Here's how I would do it:
require 'sketchup'
Sketchup.active_model.entities.grep(Sketchup::Face).select(&:material).
reduce(Hash.new(0)) {|h, face|
h.tap {|h| h[face.material.display_name] += face.area }
}
I find that much easier to read, instead of "loop over this, but skip one iteration if that thing happens, and also don't do this if that happens".
This is actually a common pattern, that pretty much every Rubyist has already written a dozen times, so I actually had a code snippet lying around that I only needed to slightly adapt. However, I am going to show you how I could have refactored your original code step-by-step if I hadn't already had the solution.
First, let's start with coding style. I know it's boring, but it is important. What the actual coding style is, is not important, the important thing is that the code is consistent, which means that one piece of code should look the same as any other piece of code. In this particular instance, you are asking the Ruby community to provide you with unpaid support, so it is polite to at least format the code in a style that members of that community are used to. This means standard Ruby coding style: 2 spaces for indentation, snake_case for method and variable names, CamelCase for constants which refer to modules or classes, ALL_CAPS for constants, and so on. Don't use parentheses unless they clear up the precedence.
In your code, for example, you use sometimes 3 spaces, sometimes 4 spaces, sometimes 5 spaces and sometimes 6 spaces for indentation, and all of that in just 9 non-empty lines of code! Your coding style is not only inconsistent with the rest of the community, it isn't even consistent with its own next line!
Let's fix that first:
require 'sketchup'
entities = Sketchup.active_model.entities
summa = {}
for face in entities
next unless face.kind_of? Sketchup::Face
if face.material
summa[face.material.display_name] += face.area
end
end
Ah, much better.
As I already mentioned, the first thing we need to do, is fix the obvious problem: replace summa = {} (which BTW would be the idiomatic way to write it) with summa = Hash.new(0). Now, the code at least works.
As a next step, I would switch the assignment of the two local variables: first you assign entities, then you assign summa, then you do something with entities and you have to look three lines up to figure out what entities was. If you switch the two, the usage and the assignment of entities are right next to each other.
As a result, we see that entities is assigned, then immediately used and then never used again. I don't think this improves readability much, so we can get rid of it altogether:
for face in Sketchup.active_model.entities
Next comes the for loop. Those are highly un-idiomatic in Ruby; Rubyists strongly prefer internal iterators. So, let's switch to one:
Sketchup.active_model.entities.each {|face|
next unless face.kind_of? Sketchup::Face
if face.material
summa[face.material.display_name] += face.area
end
}
One advantage this has, is that now face is local to the body of the loop, whereas before, it was leaking out into the surrounding scope. (In Ruby, only module bodies, class bodies, method bodies, block bodies and script bodies have their own scope; for and while loop bodies as well as if/unless/case expressions don't.)
Let's get on to the body of the loop.
The first line is a guard clause. That's good, I like guard clauses :-)
The second line is, well, if face.material is true-ish, it does something otherwise it does nothing, which means the loop is over. So, it's another guard clause! However, it is written in a totally different style than the first guard clause, directly one line above it! Again, consistency is important:
Sketchup.active_model.entities.each {|face|
next unless face.kind_of? Sketchup::Face
next unless face.material
summa[face.material.display_name] += face.area
}
Now we have two guard clauses right next to each other. Let's simplify the logic:
Sketchup.active_model.entities.each {|face|
next unless face.kind_of? Sketchup::Face && face.material
summa[face.material.display_name] += face.area
}
But now there is only one single guard clause guarding only one single expression. So, we can just make the whole expression itself conditional:
Sketchup.active_model.entities.each {|face|
summa[face.material.display_name] += face.area if
face.kind_of? Sketchup::Face && face.material
}
However, that's still kind of ugly: we are looping over some collection, and then inside the loop we skip over all the items we don't want to loop over. So, if we don't want to loop over them, we do we loop over them in the first place? We don't we just select the "interesting" items first and then loop over just them?
Sketchup.active_model.entities.select {|e|
e.kind_of? Sketchup::Face && e.material
}.each {|face|
summa[face.material.display_name] += face.area
}
We can do some simplification on this. If we realize that o.kind_of? C is the same as C === o, then we can use the grep filter which uses === to pattern match, instead of select:
Sketchup.active_model.entities.grep(Sketchup::Face).select {|e| e.material
}.each { … }
Our select filter can further be simplified by using Symbol#to_proc:
Sketchup.active_model.entities.grep(Sketchup::Face).select(&:material).each { … }
Now let's get back to the loop. Anybody who has some experience in a higher-order language such as Ruby, JavaScript, Python, C++ STL, C#, Visual Basic.NET, Smalltalk, Lisp, Scheme, Clojure, Haskell, Erlang, F#, Scala, … basically any modern language at all, will immediately recognize this pattern as a catamorphism, reduce, fold, inject:into:, inject or whatever your language of choice happens to call it.
What a reduce does, is basically it "reduces" several things into just one thing. The most obvious example is the sum of a list of numbers: it reduces several numbers into just one number:
[4, 8, 15, 16, 23, 42].reduce(0) {|accumulator, number| accumulator += number }
[Note: in idiomatic Ruby, this would be written just as [4, 8, 15, 16, 23, 42].reduce(:+).]
One way to spot a reduce lurking behind a loop is to look for the following pattern:
accumulator = something # create an accumulator before the loop
collection.each {|element|
# do something with the accumulator
}
# now, accumulator contains the result of what we were looking for
In this case, the accumulator is the summa hash.
Sketchup.active_model.entities.grep(Sketchup::Face).select(&:material).
reduce(Hash.new(0)) {|h, face|
h[face.material.display_name] += face.area
h
}
Last but not least, I don't like this explicit returning of h at the end of block. We could obviously write it on the same line:
h[face.material.display_name] += face.area; h
But I prefer the use of Object#tap (aka the K-combinator) instead:
Sketchup.active_model.entities.grep(Sketchup::Face).select(&:material).
reduce(Hash.new(0)) {|h, face|
h.tap {|h| h[face.material.display_name] += face.area }
}
And, that's it!
summa[face.material.display_name] returns nil by default when face.material.display_name isn't an existing key. When creating a hash, you can specify a different default value to return. Something like:
summa = Hash.new(0)
Just a note on your summary of face areas - you must also take into account of group/components might be scaled so you need make use of the transformations of the whole hierarchy of the groups/components containing the face you inspect. Remember that groups/components can also be skewed - so that has to be taken into account as well.

Resources