Does 'Upcase!' Not Mutate a Variable in Ruby

Why can you change the value of a local variable in Ruby in a function using another method but not with an assignment operator?

The key here is that you can never change a Ruby object's core self, once it has been created it will always be the same object until garbage collected and destroyed. It is only possible to alter properties of the object.

You can, however, change variables or object references such as instance variables, constants, attr_accessor properties among other things.

So in this case:

def uppercase2(value)
value = "WILLIAM"
end

This reassigns the value local variable. It does nothing to the original object.

If you want to replace that text you need to use methods on the object to effect it, if supported. In this case there is a method:

def uppercase2(value)
value.replace("WILLIAM")
end

These are generally termed in-place modifications as in the object itself is manipulated rather than swapped for another object.

Why do some Ruby methods like String#replace mutate copies of variables?

The issue here is not called recursion, and Ruby variables are not recursive (for any normal meaning of the word - i.e. they don't reference themselves, and you don't need recursive routines in order to work with them). Recursion in computer programming is when code calls itself, directly or indirectly, such as a function that contains a call to itself.

In Ruby, all variables point to objects. This is without exception - although there are some internal tricks to make things fast, even writing a=5 creates a variable called a and "points" it to the Fixnum object 5 - careful language design means you almost don't notice this happening. Most importantly, numbers cannot change (you cannot change a 5 into a 6, they are always different objects), so you can think that somehow a "contains" a 5 and get away with it even though technically a points to 5.

With Strings though, the objects can be changed. A step-by-step explanation of your example code might read like this:

a = 'red'

Creates a new String object with the contents "red", and points variable a at it.

b = a

Points variable b to same object as a.

b.replace('blue')

Calls the replace method on the object pointed to by b (and also pointed to by a) The method alters the contents of the String to "blue".

b = 'green'; 

Creates a new String object with the contents "green", and points variable b at it. The variables a and b now point to different objects.

print a 

The String object pointed to by a has contents "blue". So it is all working correctly, according to the language spec.

When will I ever use this?

All the time. In Ruby you use variables to point, temporarily, to objects, in order to call methods on them. The objects are the things you want to work with, the variables are the names in your code you use to reference them. The fact that they are separate can trip you up from time to time (especially in Ruby with Strings, where many other languages do not have this behaviour)

and does this mean I can't pass the value of "a" into another variable without any changes recursing back to "a"?

If you want to copy a String, there are a few ways to do it. E.g.

b = a.clone

or

b = "#{a}"

However, in practice you rarely just want to make direct copies of strings. You will want to do something else that is related to the goal of your code. Usually in Ruby, there will be a method that does the manipulation that you need and return a new String, so you would do something like this

b = a.something

In other cases, you actually will want changes to be made to the original object. It all depends on what the purpose of your code is. In-place changes to String objects can be useful, so Ruby supports them.

Furthermore it seems sometimes a method will recurse into "a" and sometimes it will cause "b" to become a new object_id.

This is never the case. No methods will change an object's identity. However, most methods will return a new object. Some methods will change an object's contents - it is those methods in Ruby that you need to be more aware of, due to possibility of changing data being used elsewhere - same is true in other OO languages, JavaScript objects are no exception here, they behave in the exact same way.

Converting camel case to underscore case in ruby

Rails' ActiveSupport
adds underscore to the String using the following:

class String
def underscore
self.gsub(/::/, '/').
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
gsub(/([a-z\d])([A-Z])/,'\1_\2').
tr("-", "_").
downcase
end
end

Then you can do fun stuff:

"CamelCase".underscore
=> "camel_case"

Ruby string pass by reference function parameter

I understand ruby does pass by reference for function parameters

Ruby is strictly pass-by-value, always. There is no pass-by-reference in Ruby, ever.

This is simply out of curiosity -- any explanations would be appreciated

The simple explanation for why your code snippet doesn't show the result you would expect for pass-by-reference is that Ruby isn't pass-by-reference. It is pass-by-value, and your code snippet proves that.

Here is a small snippet that demonstrates that Ruby is, in fact, pass-by-value and not pass-by-reference:

#!/usr/bin/env ruby

def is_ruby_pass_by_value?(foo)
foo << <<~HERE
More precisely, it is call-by-object-sharing!
Call-by-object-sharing is a special case of pass-by-value,
where the value is always an immutable pointer to a (potentially mutable) value.
HERE
foo = 'No, Ruby is pass-by-reference.'
return
end

bar = ['Yes, of course, Ruby *is* pass-by-value!']

is_ruby_pass_by_value?(bar)

puts bar
# Yes, of course, Ruby *is* pass-by-value!,
# More precisely, it is call-by-object-sharing!
# Call-by-object-sharing is a special case of pass-by-value,
# where the value is always an immutable pointer to a (potentially mutable) value.

Ruby does however allow mutation of objects, it is not a purely functional language like Haskell or Clean.

Capitalize the first letter of string in AngularJs

use this capitalize filter

var app = angular.module('app', []);
app.controller('Ctrl', function ($scope) { $scope.msg = 'hello, world.';});
app.filter('capitalize', function() { return function(input) { return (angular.isString(input) && input.length > 0) ? input.charAt(0).toUpperCase() + input.substr(1).toLowerCase() : input; }});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script><div ng-app="app">    <div ng-controller="Ctrl">        <p><b>My Text:</b> {{msg | capitalize}}</p>    </div></div>

undefined method 'becomes' for nil:NilClass where it shouldn't

Look at this ruby snippet:

if true
foo = "hello"
end
puts foo

#=> hello

And this one:

if false
foo = "hello"
end
puts foo

#=> nil

In many languages, if-statements have their own scope, but in ruby they share the scope of the surrounding function. This means variables declared inside if-statements are accessible outside of if-statements.

The issue here is that variables are declared at compile-time, before ruby knows whether the if-statement is true or false. So in ruby, all local variables are declared and initialized as nil, even if they're in a conditional statement.

This code:

unless resource.nil?
resource = resource.becomes(Accounts::Admin)
end

Causes problems because of another rule in ruby which gives local variables priority over methods. So when you say resource = resource you're actually calling the method resource and saving its value to the local variable resource, which then overshadows the method by the same name.

Ultimately you're getting the error:

undefined method `becomes' for nil:NilClass

because at compile-time, the local variable resource is being created, overshadowing the method. Then, at run-time, the condition is being executed because resource is not yet nil. But at the line where you create the local variable, it instantly comes into scope, making resource = nil and causing this the error.

The error can be reproduced in this generalized example:

def blah
"foo"
end

unless blah.nil?
blah = blah.size
end
puts blah

And the fix for it is to specify the method itself:

def blah
"foo"
end

def blah= value
#do nothing
end

unless blah.nil?
self.blah = blah.size
end

puts blah

That being said, I'm not sure whether devise actually implements resource=(). If it doesn't, then your best solution is the one you already came up with- use a local variable:

unless resource.nil?
res = resource.becomes(Accounts::Admin)
end
puts res

After doing some research, I found that local variables in ruby are defined from top to bottom and left to right, based on position in the file, not their position in program flow. For example:

if x="foo"
puts x
end
#=> "foo"

puts y if y="foo"
#NameError: undefined variable or method 'y'

This is part of the ruby spec, according to matz.

Ruby creating title case method, can't handle words like McDuff or McComb

There are several issues with your "Mc" code:

if (word.include?("mc"))

This will always return false, because you have already capitalized word. It has to be:

if word.include?('Mc')

This line doesn't work either:

letter_array = word.split!("")

because there is no split! method, just split. There is however no reason to use a character array at all. String#[] allows you to access a string's characters (or sub-strings), so the next line becomes:

if (word[0] == 'M') && (word[1] == 'c')

or just:

if word[0, 2] == 'Mc'

or even better using start_with?:

if word.start_with?('Mc')

In fact, we can replace the first if with this one.

The next line is a bit tricky:

letter_array[2].capitalize!

Using String#[] this becomes:

word[2].capitalize!

But unfortunately, both don't work as expected. This is because [] returns a new object, so the bang method doesn't change the original object. Instead you have to call the element assignment method []=:

word[2] = word[2].upcase

Everything put together:

if word.start_with?('Mc')
word[2] = word[2].upcase
end

Or in a single line:

word[2] = word[2].upcase if word.start_with?('Mc')


Related Topics



Leave a reply



Submit