Why datamapper does not update records / detect dirtiness? - ruby

I recently wanted to write a simple migration script. I wrote:
#entries = Entries.all(:text => /test/)
#entries.each do |entry|
entry.update(:text => entry.text.gsub!(/test/, "no-test"))
end
It didn't save the records, even though the update statement returned true. What did I miss?

In the 1.x series of datamapper the dirty tracking is done via calling #== on the new and old attribute values to detect dirtyness. If an object is mutated inplace (for example with the String bang methods), the change cannot be detected as the "orignal" state gets mutated also.
Basically the following happens internally:
a = "foo"
b = a.gsub!("foo", "bar")
a == b # => true both a and b refer to the same mutated object
a.equal?(b) # => true
In your example you assign the original mutated attribute back to the object, no identity change => no update detected.
In case you create a new object via String#gsub istead of mutating the original attribute value via String#gsub! you end up with a detectable change.
With assigning a new object with different value the following happens:
a = "foo"
b = a.gsub("foo", "bar")
a == b # => false, loaded state does not equal resource state so change is detected
a.equal?(b) # => false
And for having all cases covered, assigning a new object with same value:
a = "foo"
b = "foo"
a == b # => true, no dirtyness detected.
a.equal?(b) # => false
Hopefully this explains the semantic differences good enough to explain all similar cases.
BTW In datamapper 2.0 we have a differend mechanism that will also catch in place mutations. Disclaimer, I'm the author of this component called dm-session.

Remove the exclamation.
entry.update(:text => entry.text.gsub(/test/, "no-test"))
The record doesn't go dirty when you replace the string content. You should reassign it.

Related

Can `merge!` change a CONST in Ruby?

I have a situation where merge! seems to modify the value of a CONST. Can this occur? How?
I'm doing some API ingestion and mapping, like you do...
module Placement
FEATURE_DEFAULTS = {
"thingone" => "false",
"thingtwo" => "false"
}
def extract_features!(feat)
feat['norm_features'] ||= FEATURE_DEFAULTS
feat['norm_features'].merge!(
Array(feat['attributes']['feature']).reduce({}) do |h,f|
h[f] = "true"
h
end
)
end
def get_placement(_opts)
data_source["things"]["thing"].map do |thing|
product = {}
thing.each do |key, value|
new_key = RENAME_FIELDS[key] || key
new_value = REPLACE_FIELDS[key] || value
product[new_key] = new_value
end
binding.pry # 1
extract_features!(product)
binding.pry # 2
product
end
end
end
Later, I include this in a class for the API client, and then I call the get_placement method.
Dilemma
step 1
For the first run, in pry binding 1 & 2, the value of FEATURE_DEFAULTS is as seen above. For 2 the value of FEATURE_DEFAULTS is the same, and the value of product['norm_features'] is the same (plus the results of the meshing operation of extract_features!
step 2
The output (to the caller of get_placement), for every thing/product, is
FEATURE_DEFAULTS = {
"thingone" => true,
"thingtwo" => true
}
step 3
When I run this the second time (after starting up the service / app), the value of FEATURE_DEFAULTS, and pry binding 1 & 2, is
FEATURE_DEFAULTS = {
"thingone" => true,
"thingtwo" => true
}
What is happening here?
This seems to confirm that, after running the extract_features! method, the FEATURE_DEFAULTS CONST is changed. If I do not use merge! in extract_features!, and use merge instead, then the CONST value does not change.
I can post more code, if needed, or most to a Gist.
Ruby MRI 2.2.2
I am doing this inside a rails app, but I don't see that it matters.
I have a situation where merge! seems to modify the value of a CONST. Can this occur? How?
No, in general, methods cannot change variable bindings, regardless of whether those variables are local variables, instance variables, class variables, global variables, or constants. Variables aren't objects in Ruby, you can't call methods on them, you can't pass them as arguments to methods, ergo, you can't tell them to change themselves.
The exception to this are meta-programming methods like Binding#local_variable_set, Object#instance_variable_set, Module#class_variable_set, or Module#const_set.
What you can do, however, and what you are doing in this case, is tell the object the variable points to to change itself. The documentation for Hash#merge! is unfortunately a bit unclear in that it does not explicitly mention that Hash#merge! mutates its receiver.
This seems to confirm that, after running the extract_features! method, the FEATURE_DEFAULTS CONST is changed. If I do not use merge! in extract_features!, and use merge instead, then the CONST value does not change.
No, it only confirms that the state of the object FEATURE_DEFAULTS points to changed, it doesn't say anything about whether the binding of FEATURE_DEFAULTS, i.e. which object it points to, changed. You can confirm that the constant still points to the same object by looking at its object_id.
Of course, constants can be re-assigned anyway, albeit triggering a warning.

Multiple method calling

This just returns the original name.
name = "George"
name.reverse.upcase!
puts(name)
I'm wondering why and if there is any way to do what I tried above.
reverse returns a new string. upcase! upcases the string it is called on in-place. You are creating a new reversed string, upcasing that new string, and then never using it again.
If you wanted to reverse and upcase the original string you could name.reverse!.upcase!
The methods you are calling do not affect the calling object. name.reverse will return a new String and leave the original alone. What you want to do is reassign name after your call.
name = George
name = name.reverse.upcase
There is a gotcha here in that bang methods, ending in ! will often modify the object being operated upon. So you could do something like below:
name = George
name.reverse!.upcase!
In general, I would avoid the ! methods unless you have a good reason. The first example of setting "name = " is very clear, easy to read and unambiguous.
Perhaps this will help explain what's happening:
name = "George" # => "George"
name.object_id # => 70228500576040
object_id is the memory reference for the actual variable, in other words, where it lives as the script is running.
reversed_name = name.reverse # => "egroeG"
reversed_name.object_id # => 70228500574980
We can tell that reverse created a new variable because the object_id is different from that of name.
upcased_reversed_name = reversed_name.upcase! # => "EGROEG"
upcased_reversed_name.object_id # => 70228500574980
The upcase! method modified the same variable as reversed_name.
If we use upcase instead, the object_id changes because a new version of the variable is created:
upcased_reversed_name = reversed_name.upcase # => "EGROEG"
upcased_reversed_name.object_id # => 70228500572960
upcased_reversed_name # => "EGROEG"
The short lesson is you can't assign a ! method result to a variable because it acts on the original variable and changes it in place.

Ruby variables referencing other variables

I have two variables, one (b) that references the other (a). When I modify a with a method, b is also modified:
a = "TEXT"
b = a
print b
#=> TEXT
a.downcase!
print b
#=> text
However, when I modify a directly, b retains its value:
a = "TEXT"
b = a
print b
#=> TEXT
a = "Something Else"
print b
#=> TEXT
Why is the behavior of b different when the variable it initially referenced is modified directly as opposed to by a method?
Is this an improper thing to do in Ruby, and if so, what would a better practice be for referencing one variable with another?
Ruby works with references, and you are making a little mistake in there.
This:
a.downcase!
as the 'bang' method suggests, is changing the value referenced by a.
So a is still the referencing the same object, which was just changed by the downcase! method
But this:
a = "Something Else"
is actually saying to a to reference a new object which happens to also be a string.
Since b was referencing another object and that object didn't changed, it still prints TEXT.
You can use the object_id to see what is going on here.
a = "text"
a.object_id
=> 70200807828580
b = a
b.object_id
=> 70200807828580 # b points to the same object that a does.
a = "new"
a.object_id
=> 70200807766420 # a now points to a new object
b.object_id
=> 70200807828580 # b still points to the original object.
So you see that the variable actually doesn't store the object itself. Instead it stores the id of the object. That's why if you copy an object you usually just copy the id of it rather than creating a whole new object.

Creating Copies In Ruby

I have the following code as an example.
a = [2]
b = a
puts a == b
a.each do |num|
a[0] = num-1
end
puts a == b
I want b to refer to a's value, and the value of b not to change when a is changed.(The second puts should return false).
Thank you in advance.
Edited-
The answer posted by user2864740 seems to work for the example I gave. However, I'm working on a sudoku solving program, and it doesn't seem to work there.
#gridbylines = [[1,0,0,9,2,0,0,0,0],
[5,2,4,0,1,0,0,0,0],
[0,0,0,0,0,0,0,7,0],
[0,5,0,0,0,8,1,0,2],
[0,0,0,0,0,0,0,0,0],
[4,0,2,7,0,0,0,9,0],
[0,6,0,0,0,0,0,0,0],
[0,0,0,0,3,0,9,4,5],
[0,0,0,0,7,1,0,0,6]]
save = Array.new(#gridbylines) #or #gridbylines.dup
puts save == #gridbylines #returns true
puts save.equal?(#gridbylines) #returns false
#gridbylines[0][0] = 'foo'
puts save.equal?(#gridbylines) #returns false
puts save == #gridbylines #returns true, but I want "save" not to change when I change "#gridbylines"
Does this have something to do with the fact that I'm using a global variable, or the version of Ruby I'm using, or even because it's a multidimentional array unlike the previous example?
Variables name or "refer to" objects1. In the code above the same object (which has two names, a and b) is being changed.
A simple solution in this case is to make a (shallow) copy of the original Array object, such as b = a.dup or b = Array.new(a). (With a shallow copy, elements in the array are also shared and will exhibit the similar phoneme as the original question unless they to are [recursively] duplicated, etc.2)
a = [2]
b = Array.new(a) # create NEW array object, a shallow-copy of `a`
puts a == b # true (same content)
puts a.equal?(b) # false (different objects)
a.each do |num|
a[0] = num-1 # now changing the object named by `a` does not
# affect the object named by `b` as they are different
end
puts a == b # false (different content)
And an isolated example of this "naming" phenomena (see the different equality forms):
a = []
b = a # assignment does NOT make a copy of the object
a.equals?(b) # true (same object)
c = a.dup # like Array.new, create a new shallow-copy object
a.equals?(c) # false (different object)
1 I find it most uniform to talk about variables being names, as such a concept can be applied across many languages - the key here is that any object can have one or more names, just as a person can have many nicknames. If an object has zero names then it is no longer strongly reachable and, just like a person, is forgotten.
However, another way to view variables (naming objects) is that they hold reference values, where the reference value identifies an object. This leads to phrasing such as
"variable a contains a reference [value] to object x" - or,
"variable a refers to / references object x" - or, as I prefer,
"variable a is a name for object x".
For the case or immutable "primitive" or "immediate" values the underlying mechanics are slightly different but, being immutable the object values cannot be changed and such a lack-of-shared object nature will not manifest itself.
See also:
String assignment by reference/copy? (examines object relationship after assignment)
Strange Feature? of Ruby Arrays (examines same-object mutation)
Is Ruby pass by reference or by value? (assignment works in the same way as argument passing)
2 As per the updated question with nested arrays, this is explained by the previous rules - the variables (well, really expressions) still name shared objects. In any case, one way to "clone an array of arrays" (to two levels, although not recursively) is to use:
b = a.map {|r| r.dup}
This is because Array#map returns a new array with the mapped values which are, in this case, duplicates (shallow clones) of the corresponding nested arrays.
See How to create a deep copy of an object in Ruby? for other "deep[er] copy" approaches - especially if the arrays (or affect mutable objects) were nested to N-levels.

Changing value of ruby variables/references

I just stumbled upon something i don't quite understand. I know that variables in ruby are references. So that awesome stuff is possible. But when i pass a variable to a method, it behaves strangely:
my_var_a = "nothing happend to me"
my_var_b = "nothing happend to me"
def parse_set(my_var_set)
my_var_set = "my value changed"
end
def parse_sub(my_var_sub)
my_var_sub.sub! /(.*)/, "my value changed"
end
parse_set(my_var_a)
parse_sub(my_var_b)
my_var_a # => "nothing happend to me"
my_var_b # => "my value changed"
Can you explain to me why it works with sub! and = leaves the object unchanged? How can I avoid to use sub! but having the same result?
my_var_a and my_var_set are different references, but they point at the same object. If you modify the object in my_var_set, the change shows up in my_var_a. However, if you repoint my_var_set at a new object, that doesn't change what my_var_a points at.
Edit: clarification...
What Ruby does is called passing references by value. When you say
my_var_a = "nothing happend to me"
Ruby saves the string "nothing happend to me" in a memory location (let's call it 1000), and saves the my_var_a reference in another memory location (let's say 2000). When your code uses my_var_a, the interpreter looks at location 2000, see that it points to 1000, then gets the actual string value from 1000.
When you call parse_set(my_var_a), Ruby actually creates a new reference named my_var_set and points it to the string that my_var_a was pointing at (memory location 1000). However, my_var_set is a copy of the my_var_a reference -- let's say my_var_set was created at memory location 3000. my_var_a and my_var_set are 2 completely different references in memory, they just happen to point at the same exact memory location which holds the string value.
The statement my_var_set = "my value changed" in parse_set creates a new string in memory and points my_var_set at that new memory location. However, this doesn't change what the original my_var_a reference points at! Now that my_var_set points at a different memory location, nothing that you do to that variable will affect my_var_a.
The same reference copy happens for parse_sub as well. But the reason that parse_sub changes the string is because you're calling a method directly on the my_var_sub reference. When you do this, the interpreter gets the object that my_var_sub is pointing at and then modifies it. So that change will show up in the my_var_a reference, because it still points at the same string.
irb(main):008:0* a = 'abc'
=> "abc"
irb(main):009:0> a.replace('def')
=> "def"
irb(main):010:0> a
=> "def"
I've had to use replace exactly zero times in all the years I've been programming in Ruby. I wonder if there's a better design for your code.
Most string methods which change the receiver are suffixed by ! (sub!, capitalize!, chomp!, etc.) Some that change the receiver are not suffixed by ! (replace is one). If you call a method that changes the receiver, any and all references to that object will see the change. if you call a method that does not change receiver, the method returns a new string.
gsub does not modify the receiver, but instead returns a new instance of String:
a = "foo"
b = a
p a.sub(/o/, '#') # "f##"
p a # => "foo"
p b # => "foo"
gsub! does modify the receiver:
a = "foo"
b = a
p a.sub!(/o/, '#') # => "f#o"
p a # => "f#o"
p b # => "f#o"
"How can I avoid to use sub! but having the same result?"
def parse_set()
"my value changed"
end
a = parse_set()
Set it as an instance variable of an object (although this works just in your main script, I recommend making your own class if you want to use the instance variables approach).
#my_var_a = "nothing happend to me"
def parse_set()
#my_var_a = "my value changed"
end

Resources