Ruby: How to write a bang method, like map?
EDIT - Updated answer to reflect the changes to your question.
class Array
def stuff!
self[0] = "a"
end
end
foo = [1,2,3,4]
foo.stuff!
p foo #=> ['a',2,3,4]
Write two similar methods -one with bang, one without- while respecting DRY concept
You can also factor out the code for a single item, then be a little more verbose in the array methods. The left_outer_join_element()
method here makes practical sense on its own and is reusable even for non-Array objects.
def left_outer_join(ary, &block)
self.map { |e| left_outer_join_element(e, ary, &block) }
end
def left_outer_join!(ary, &block)
self.map! { |e| left_outer_join_element(e, ary, &block) }
end
protected
def left_outer_join_element(element, ary, &block)
ary.each do |obj|
if yield element, obj
obj.keys.each do |key|
element[key] = obj[key]
end
break
end
end
element
end
How to create bang method that modifies the argument
You can modify the object passed as argument, but you have to use the appropriate methods to modify the receiver:
def preload! hash
hash.replace(foo: 1, bar: 2)
end
h = {}
preload! h
h #=> {:foo=>1, :bar=>2}
Assigning a new hash to hash
inside preload!
just affects the hash
variable inside the method, not the h
variable outside:
def preload! hash
hash = {foo: 1, bar: 2} # doesn't work as expected
end
Ruby: Is it true that #map generally doesn't make sense with bang methods?
Base object
Here's an array with strings as element :
words = ['hello', 'world']
New array
If you want a new array with modified strings, you can use map
with gsub
:
new_words = words.map{|word| word.gsub('o','#') }
p new_words
#=> ["hell#", "w#rld"]
p words
#=> ["hello", "world"]
p new_words == words
#=> false
The original strings and the original array aren't modified.
Strings modified in place
If you want to modify the strings in place, you can use :
words.each{|word| word.gsub!('o','#') }
p words
#=> ["hell#", "w#rld"]
map and gsub!
new_words = words.map{|word| word.gsub!('o','#') }
p words
#=> ["hell#", "w#rld"]
p new_words
#=> ["hell#", "w#rld"]
p words == new_words
#=> true
p new_words.object_id
#=> 12704900
p words.object_id
#=> 12704920
Here, a new array is created, but the elements are the exact same ones!
It doesn't bring anything more than the previous examples. It creates a new Array for nothing. It also might confuse people reading your code by sending opposite signals :
gsub!
will indicate that you want to modifiy existing objectsmap
will indicate that you don't want to modify existing objects.
What does the map method do in Ruby?
The map
method takes an enumerable object and a block, and runs the block for each element, outputting each returned value from the block (the original object is unchanged unless you use map!)
:
[1, 2, 3].map { |n| n * n } #=> [1, 4, 9]
Array
and Range
are enumerable types. map
with a block returns an Array. map!
mutates the original array.
Where is this helpful, and what is the difference between map!
and each
? Here is an example:
names = ['danil', 'edmund']
# here we map one array to another, convert each element by some rule
names.map! {|name| name.capitalize } # now names contains ['Danil', 'Edmund']
names.each { |name| puts name + ' is a programmer' } # here we just do something with each element
The output:
Danil is a programmer
Edmund is a programmer
Ruby: Reference materials to learn more about assigning values to self
First read the article in Wikipedia about self (even if it does not mention Ruby at all).
To make a long story short:
- Ruby has borrowed a lot of concepts from other languages, and
self
comes from Smalltalk. self
is called in Smalltalk a pseudo-variable, which means it is variable, but it is set by the runtime environment, not by the program or programmer.self
references all the time the receiver of a message.super
references the superclass of that message that is implemented by the method the referencesuper
is in. (Glad that you did not ask forsuper
).self
in Ruby (as in Smalltalk) references all the time the current object, and that may be an instance of a class or even a class itself. So if you define methods on the class-side (only callable on the class), even thereself
references the object, which is the class. So it is possible in Ruby to use onlyself
, you never have to write down the name of the class to denote the receiver. That helps a little bit when refactoring.
If you have get all that, take a look at Metaprogramming Ruby which tells you some more tricks how to use self
, classes, eigenclasses and some other interesting things.
Why are bang methods dangerous in Ruby?
There are two widespread meanings of "dangerous" in standard library and common gems:
Method mutates the receiver, as opposed to returning a copy of the receiver. Example:
Array#map!
Method will raise an exception if its primary function can't be performed. Example:
ActiveRecord::Base#save!
,ActiveRecord::Base#create!
. If, say, an object can't be saved (because it's not valid or whatever),save!
will raise an error, whilesave
will returnfalse
.
I usually add a third meaning to it in my code:
- Method will immediately persist data in the database, instead of just changing some attributes and hoping that later someone will save the object. Example: hypothetical
Article#approve!
Ruby: Why does this way of using map throw an error?
&:foo
may erroneously be seen as &:
plus foo
(terms like "pretzel colon" reinforce this mistaken view). But no method foo
is being called here. &:foo
is actually &
plus :foo
, the latter being a plain symbol.
When calling a method, &object
(without :
) invokes object.to_proc
(which is supposed to return a Proc
) and passes the returned proc as a block argument to the method.
object
often happens to be a symbol and Symbol#to_proc
's implementation would look somehow like this in Ruby: (it's actually written in C)
class Symbol
def to_proc
proc { |object, *args| object.public_send(self, *args) }
end
end
So this:
method(&:symbol)
effectively becomes this:
method { |object, *args| object.public_send(:symbol, *args) }
or, if method
doesn't yield multiple values (like map
), it's simply:
method { |object| object.public_send(:symbol) }
Obviously, you can't pass additional arguments via a symbol.
But ... object
doesn't have to be a symbol. You could use another class with a custom to_proc
implementation. Let's abuse Array
for demonstration purposes:
class Array
def to_proc
method, *args = self
proc { |obj| obj.public_send(method, *args) }
end
end
This hack would allow you to write:
["foo\nbar", "baz\nqux"].map(&[:gsub, "\n", '-'])
#=> ["foo-bar", "baz-qux"]
Related Topics
Cannot Load Such File -- Rack/Handler/Puma
Using Activemodel::Serializer in Rails - JSON Data Differs Between JSON and Index Response
Rails - Aciverecord Use :Dependent => :Destroy on Condition
Access Ruby Hash Using Dotted Path Key String
What Are All the "Conventions" for Ruby on Rails
Accessing Variables from Included Files in Ruby
Custom Filtering of Parameters in Rails 3 Using Config.Filter_Parameters
What Are the Differences Between Lazy, Greedy and Possessive Quantifiers
Rails 4: How to Upload Files with Ajax
Error Installing Rubymine, No Sdk Specified, But It Is Listed
Extract All Urls Inside a String in Ruby
Uploading a File to a Website with Ruby/Rails
Ruby 1.9.2 - Read and Parse a Remote CSV
Rails Render of Partial and Layout in Controller
Duplicating a Ruby Array of Strings
Could Not Find Rake with Bundle Exec
Ruby Tcpsocket: Find Out How Much Data Is Available
Alter $Path in Vim/Macvim So as to Find the Right Ruby Binary