I'm new to Ruby and I'm having trouble understanding what's happening in this method.
I make this call in a Rails controller -
#arr = SomeClass.find_max_option(params[:x], #pos, params[:y], some_var)
I'm trying to return the value to #arr, which happens successfully, but manipulations I make to #pos within that method are being brought back as well; the value of #pos changes when I'm only trying to get the value for #arr.
Here's more details on the method
#before going into the method
#pos = [a,b]
def self.find_max_option(x, pos, y, some_var)
pos.collect! { |element|
(element == b) ? [c,d] : element
}
end
#new value of pos = [a, [c,d]] which is fine for inside in this method
... #some calculations not relevant to this question, but pos gets used to generate some_array
return some_array
But when the method is finished and gets back to the controller, the value of #pos is now [a,[c,d]] as well.
What's going on here? I thought that pos would be treated separately from #pos and the value wouldn't carry back. As a workaround I just created a new local variable within that method, but I'd like to know what this is happening
#my workaround is to not modify the pos variable
pos_groomed = pos.collect { |element|
(element == b) ? [c,d] : element
}
end
Instead of using collect!, just use collect (without the !). So, rewrite your method as:
def self.find_max_option(x, pos, y, some_var)
pos.collect { |element|
(element == b) ? [c,d] : element
}
end
When using the ! version of collect, you are replacing each element with the value returned by the block. However, when using collect without !, a new array is created, and the object where collect is being called it doesn't get changed. See the docs:
collect! vs collect
Using ! at the end of a method name is a common practice in Ruby. This question is related and would be worth taking a look.
You are using the destructive version of collect.
Destructive methods change the object on which the method is called, while non-destructive methods return new objects.
Ruby developers tend to call these methods 'bang methods', because the convention is that destructive methods have the ! suffix.
pos.collect! # changes pos and returns pos
pos.collect # creates a new object
Your workaround only works because you use the non-destructive collect, while the original code uses collect!
pos.collect do |element|
(element == b) ? [c,d] : element
end
Should work just fine.
As to why the object changes outside of the method:
In ruby, when you pass an argument to a method, you are actually passing the reference to the object.
So passing an array into a method doesn't make a copy, but simply passes the reference to original array.
There is no way to 'pass by value' but you can create a copy yourself with dup or clone, if you really have to.
Related
I am trying to deal with a legacy method which accepts a variable and returns the variable.amount.to_s. But now with some changes, the variable can also be an active record relation.
I basically want to be able to do something like this:
def method(variable)
if variable has map?
variable.map { |v| v.amount.to_f }.reduce(:+)
variable.to_s
else
variable.amount.to_s
end
end
but I am unable to figure out what that if condition should be ?
You can test for the method directly with respond_to?.
if variable.respond_to?(:map)
Or you can check that it is Enumerable which provides a large suite of methods used for iteration, including map.
if variable.is_a?(Enumerable)
The advantage of Enumerable is it tells you more about the object, and you can be more sure that its map is the map you expect and not just some method that happens to be named map. The disadvantage is it will miss anything which is not Enumerable but does implement an applicable map; I can't think of a case where that should happen.
I have an store which can be set directly as follows
self.xxx_bias_store[i][j] = [7,11]
where xxx can be different names
How do I set this using send. I have tried
self.send("#{name}_biases_store[#{i}][#{j}]=".to_sym, [7,11])
but this has no effect. Also interested in how to retrieve value i.e.
send("#{name}_biases_store[#{i}][#{j}]".to_sym)
I'm not quite sure why you're trying to use send for this, and looking at your comment I'm not convinced you're actually asking the right question but regardless, here's how this works.
The methods you're concerned with look like this:
class Array
def [](index)
# Look up the element of the array at index
end
def []=(index, value)
# Set the element of the array at index to value
end
end
thing[5] calls the [] method with argument 5 - that is, it sends the method [] to the receiver thing with the argument 5. Similarly, thing[5]=1 calls the []= method with arguments 5 and 1
A multi-dimensional array is just an array made up of other arrays, so...
thing = [[1,2], [3,4]]
p thing.send(:[], 1) # => [3,4]
p thing.send(:[], 1).send(:[], 0) # => 3
thing.send(:[], 1).send(:[]=, 0, 5)
p thing # => [[1,2], [5,4]]
send("biases_store[#{i}][#{j}]".to_sym)
There's your error, thinking that biases_store[i][j] is one long complex method name. It's not. biases_store is a method, which returns something. And then you call method [] on that value which gets you yet another object. On which you call method [] again.
dynamic_property_name = 'biases_store'
send(dynamic_property_name)[i][j] = whatever
Or, same code rearranged for easier understanding
store = send('biases_store')
store[i][j] = whatever
I have a domain class that is just a list of strings (youtubeLinks).
When saving these links I want to strip out the video ID and save it instead of the entire URL entered on the UI side.
This is what I'm trying (ignore that the regex is flawed)
youtubeLinks.each {
def youtubeRegex = /v=(.*)/
def matcher = ( it =~ youtubeRegex )
it = matcher[0][1]
}
When I save this, it saves the original value of "it". Is there a way to update this reference and have it save properly?
Thanks.
Groovy's each loop is merely an iterator, and as such it neither affects the collection on which it operates, nor returns a value of its own. It's basically equivalent to Java's "advanced for loop (for-each)," only with the convenience of dynamic typing and an implicit loop variable (it). While it can be modified, it's a futile enterprise, as you'd be simply changing a reference to the original value, not the value itself. See this question for more on that.
When you need to modify every element within a collection somehow, the idiomatic Groovy (Grails) solution is to use the collect method. Collect transforms each element via the closure you provide, ultimately returning a new collection ( so, it doesn't actually "modify" anything).
Basically, you'll probably want to do something like this:
def links = '''http://www.youtube.com/watch?v=fl6s1x9j4QQ
http://www.youtube.com/watch?v=tCvMKcNJCAY
'''
assert (links =~ /watch\?v=(.*)/).collect{match -> match[1]} == ["fl6s1x9j4QQ", "tCvMKcNJCAY"]
..Though there are actually a number of ways one could go about such a task in Groovy.
Additionally, Ted Naleid's blog has some nice examples of Groovy pattern matching that you may find useful.
Edit
Here are several ways in which you could abbreviate the solution you submitted:
youtubeLinks = youtubeLinks.collect{link -> (link =~ /\?v=(.*)$/)[0][1]}
or
youtubeLinks = youtubeLinks.collect{link -> link.replaceAll(/^.*\?v=/, "") }
or this (Though it's a little contrived)
youtubeLinks = youtubeLinks.join('\n').replaceAll(/.*\?v=/, '').split()
You were right, it ended up being like
youtubeLinks = youtubeLinks.collect {
def youtubeRegex = /v=(.*)[&]/
def matcher = ( it =~ youtubeRegex )
return matcher[0][1]
}
Thanks, Northover.
I ran in to an interesting problem while using the 'tap' method on objects of type 'String'.
"abc".tap { |o| o = "xyz" } # this line returns "abc" instead of "xyz"
The 'tap' method works on objects of other types.
[].tap { |o| o << "xyz" } # this line returns ["xyz"] as expected
I am using Rails 2.3.2 and Ruby 1.8.6 on Windows XP.
What am I missing here?
Update 1: I resolved this issue. It was an usage error on my part. In the first scenario I was re-assigning the value to the block parameter instead of modifying it. I was able to rewrite the code and get the expected result.
"abc".tap { |o| o.replace "xyz" }
Update 2: Code used here is just to demonstrate the problem. My actual code does not look like this.
Object#tap always returns the original object passed into it after executing the block, even if the block returns something different.
The benefit here is that you can put tap into the middle of a chain and test the value of an object without modifying the original value.
This means that if you pass "abc" into tap, it'll execute the block and return "abc". Your assignment to o doesn't mean anything due to scoping. Likewise, if you pass an empty array into tap, it would return an empty array back. However, in your second example, you modified the original value. Operations that modify the original such as << or gsub! or likewise will modify the object before its returned, therefore you get a different value back.
Check out http://moonbase.rydia.net/mental/blog/programming/eavesdropping-on-expressions for some more cool examples about how to use tap. Don't use it for assignment or modification or you'll get back wonky results.
Update: To your edit, why does that resolve your issue? I don't understand why you'd call tap to do a replace when you can just call replace on the object itself. What benefit is tap providing you?
Hopefully I haven't misunderstood the meaning of "duck typing", but from what I've read, it means that I should write code based on how an object responds to methods rather than what type/class it is.
Here's the code:
def convert_hash(hash)
if hash.keys.all? { |k| k.is_a?(Integer) }
return hash
elsif hash.keys.all? { |k| k.is_a?(Property) }
new_hash = {}
hash.each_pair {|k,v| new_hash[k.id] = v}
return new_hash
else
raise "Custom attribute keys should be ID's or Property objects"
end
end
What I want is to make sure that I end up with a hash where the keys are an integer representing the ID of an ActiveRecord object. I don't particularly enjoy having to iterate through the hash keys twice with all? to determine if I need to grab the ID's out.
Of course, I'll accept any other suggestions to improve this code as well :)
How you write this method should depend on whether you expect an exception to be thrown during the course of normal program execution. If you want a readable exception message because an end-user might see it, then throwing one manually makes sense. Otherwise, I'd just do something like this:
def convert(hash)
new_hash = {}
hash.each_pair { |k,v| new_hash[ k.is_a?(Integer) ? k : k.id ] = v }
return new_hash
end
This will accomplish exactly the same thing, and you'll still get an exception if an array key doesn't have an id field. Even better, this uses a little more duck typing because now anything that has an id field will be acceptable, which is better than explicitly checking for something being a Property. This makes your code more flexible, especially when unit testing.
We still have an explicit check for integer objects, but this kind of occasional special case is usually acceptable, especially when checking for built-in data types.
Duck typing is really just a nuanced version of polymorphism. In a statically typed language like Java you'd have to create an explicit interface that told the compiler all of the methods that a particular variable can accept. With a dynamic language like Ruby the interfaces still exist in an abstract sense, they're just implicit.
The problem is the fact that you're accepting two different data structures into one method. The way to make duck typing work is to require that all the objects that get passed to your method obey the same contract (i.e. it's always a hash of Integers to [Foo] objects.) The process of converting a hash with Property keys into the correct structure should be the job of the client code. That can be done very easily with a simple wrapper class or a conversion function consisting of just the body of your elseif clause.
Bottom line it's up to the guy calling the method to make sure his parameters all quack the way your method expects them to quack. If they don't, he's the one who need's to figure out how to make his turkey quack like a duck, not you.
What I want is to make sure that I end up with a hash where the keys are an integer representing the ID of an ActiveRecord object.
You should probably check for that when you're creating/inserting into the hash. You could try something like this:
h = {}
def h.put obj
self[obj.id]=obj
end
or maybe
h = {}
def h.[]= key, value
raise "hell" unless key == value.id
super
end