@user.update_languages(params[:language][:language1], 
                       params[:language][:language2], 
                       params[:language][:language3])
lang_errors = @user.errors
logger.debug "--------------------LANG_ERRORS----------101-------------" 
                + lang_errors.full_messages.inspect

if params[:user]
  @user.state = params[:user][:state]
  success = success & @user.save
end
logger.debug "--------------------LANG_ERRORS-------------102----------" 
                + lang_errors.full_messages.inspect

if lang_errors.full_messages.empty?

@user对象将错误添加到update_languages方法中的lang_errors变量中。 当我在@user对象上执行保存时,我丢失了最初存储在lang_errors变量中的错误。

虽然我正在尝试做的更多的是一个黑客(似乎没有工作)。我想知道为什么变量值被洗掉了。我理解通过引用传递,所以我想知道值如何可以保存在那个变量中而不被洗掉。


当前回答

Ruby使用“对象引用传递”

(使用Python术语。)

说Ruby使用“按值传递”或“按引用传递”并不是真正的描述性足够有帮助。我想现在大多数人都知道,这个术语(“值”vs“引用”)来自c++。

在c++中,“传递值”意味着函数获得变量的副本,对副本的任何更改都不会改变原始变量。对象也是如此。如果你通过值传递一个对象变量,那么整个对象(包括它的所有成员)都会被复制,对成员的任何更改都不会改变原始对象上的那些成员。(如果你通过值传递一个指针,这是不同的,但Ruby没有指针,AFAIK。)

class A {
  public:
    int x;
};

void inc(A arg) {
  arg.x++;
  printf("in inc: %d\n", arg.x); // => 6
}

void inc(A* arg) {
  arg->x++;
  printf("in inc: %d\n", arg->x); // => 1
}

int main() {
  A a;
  a.x = 5;
  inc(a);
  printf("in main: %d\n", a.x); // => 5

  A* b = new A;
  b->x = 0;
  inc(b);
  printf("in main: %d\n", b->x); // => 1

  return 0;
}

输出:

in inc: 6
in main: 5
in inc: 1
in main: 1

在c++中,“引用传递”意味着函数可以访问原始变量。它可以分配一个全新的字面值整数,然后原始变量也会有这个值。

void replace(A &arg) {
  A newA;
  newA.x = 10;
  arg = newA;
  printf("in replace: %d\n", arg.x);
}

int main() {
  A a;
  a.x = 5;
  replace(a);
  printf("in main: %d\n", a.x);

  return 0;
}

输出:

in replace: 10
in main: 10

如果参数不是一个对象,Ruby使用按值传递(在c++的意义上)。但是在Ruby中,所有的东西都是一个对象,所以在Ruby中没有c++意义上的值传递。

在Ruby中,“通过对象引用传递”(使用Python的术语)被使用:

在函数内部,对象的任何成员都可以有新的值赋给他们,这些变化将在函数返回.*后持续存在 在函数内部,将一个全新的对象赋值给变量会导致变量停止引用旧对象。但是在函数返回后,原始变量仍然会引用旧对象。

因此Ruby不使用c++意义上的“引用传递”。如果存在,那么将一个新对象分配给函数内的变量将导致在函数返回后忘记旧对象。

class A
  attr_accessor :x
end

def inc(arg)
  arg.x += 1
  puts arg.x
end

def replace(arg)
  arg = A.new
  arg.x = 3
  puts arg.x
end

a = A.new
a.x = 1
puts a.x  # 1

inc a     # 2
puts a.x  # 2

replace a # 3
puts a.x  # 2

puts ''

def inc_var(arg)
  arg += 1
  puts arg
end

b = 1     # Even integers are objects in Ruby
puts b    # 1
inc_var b # 2
puts b    # 1

输出:

1
2
2
3
2

1
2
1

*这就是为什么在Ruby中,如果你想在函数中修改一个对象,但在函数返回时忘记这些更改,那么你必须在对副本进行临时更改之前显式地创建一个对象的副本。

其他回答

关于Ruby的“按值传递引用”如何工作的理论,有很多很棒的答案。但以身作则,我能更好地学习和理解一切。希望这对你们有帮助。

def foo(bar)
  puts "bar (#{bar}) entering foo with object_id #{bar.object_id}"
  bar =  "reference"
  puts "bar (#{bar}) leaving foo with object_id #{bar.object_id}"
end

bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"

# Output
bar (value) before foo with object_id 60
bar (value) entering foo with object_id 60
bar (reference) leaving foo with object_id 80 # <-----
bar (value) after foo with object_id 60 # <-----

正如你所看到的,当我们输入这个方法时,我们的工具条仍然指向字符串“value”。但是之后我们给bar分配了一个字符串对象“reference”,它有一个新的object_id。在这种情况下,foo内部的bar具有不同的作用域,无论我们在方法中传递什么,当我们重新分配它并将其指向内存中包含String "reference"的新位置时,bar都不再访问它。

现在考虑同样的方法。唯一的区别是在方法中使用do

def foo(bar)
  puts "bar (#{bar}) entering foo with object_id #{bar.object_id}"
  bar.replace "reference"
  puts "bar (#{bar}) leaving foo with object_id #{bar.object_id}"
end

bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"

# Output
bar (value) before foo with object_id 60
bar (value) entering foo with object_id 60
bar (reference) leaving foo with object_id 60 # <-----
bar (reference) after foo with object_id 60 # <-----

注意到区别了吗?我们在这里所做的是:我们修改了String对象的内容,变量所指向的对象。bar的范围在方法内部仍然是不同的。

所以要注意如何处理传递给方法的变量。如果修改传入的变量-in-place (gsub!, replace, etc),然后在方法的名称中以砰的一声表示so,比如“def foo!”

注:

重要的是要记住,foo的“bar”的内部和外部是“不同的”“bar”。它们的范围不同。在该方法中,您可以将“bar”重命名为“club”,结果将是相同的。

我经常看到变量在方法内部和外部被重用,虽然这很好,但它降低了代码的可读性,是一种代码气味。我强烈建议不要做我在上面例子中所做的:),而是这样做

def foo(fiz)
  puts "fiz (#{fiz}) entering foo with object_id #{fiz.object_id}"
  fiz =  "reference"
  puts "fiz (#{fiz}) leaving foo with object_id #{fiz.object_id}"
end

bar = "value"
puts "bar (#{bar}) before foo with object_id #{bar.object_id}"
foo(bar)
puts "bar (#{bar}) after foo with object_id #{bar.object_id}"

# Output
bar (value) before foo with object_id 60
fiz (value) entering foo with object_id 60
fiz (reference) leaving foo with object_id 80
bar (value) after foo with object_id 60

Ruby是严格意义上的值传递,但是值是引用。

这可以称为“pass-reference-by-value”。这篇文章有我读过的最好的解释:http://robertheaton.com/2014/07/22/is-ruby-pass-by-reference-or-pass-by-value/

按值传递引用可以简单地解释如下:

函数接收(并将访问)调用者在内存中使用的同一对象的引用。但是,它不接收调用方存储该对象的盒子;与pass-value-by-value一样,函数提供自己的方框,并为自己创建一个新变量。

结果行为实际上是引用传递和值传递的经典定义的组合。

需要注意的是,您甚至不需要使用“replace”方法来更改原始值。如果你为一个哈希值分配了其中一个哈希值,你就是在改变原始值。

def my_foo(a_hash)
  a_hash["test"]="reference"
end;

hash = {"test"=>"value"}
my_foo(hash)
puts "Ruby is pass-by-#{hash["test"]}"

其他的答案都是正确的,但是一个朋友让我向他解释这一点,归根结底就是Ruby是如何处理变量的,所以我想分享一些我为他写的简单的图片/解释(很抱歉篇幅太长,可能有些过于简化了):


Q1:当你将一个新变量str赋值为'foo'时会发生什么?

str = 'foo'
str.object_id # => 2000

答:一个名为str的标签被创建,指向对象'foo',对于这个Ruby解释器的状态来说,它恰好位于内存位置2000。


Q2:当你使用=将现有变量str赋值给一个新对象时会发生什么?

str = 'bar'.tap{|b| puts "bar: #{b.object_id}"} # bar: 2002
str.object_id # => 2002

A:标签str现在指向一个不同的对象。


Q3:当你分配一个新的变量= str会发生什么?

str2 = str
str2.object_id # => 2002

答:创建一个名为str2的新标签,指向与str相同的对象。


Q4:如果被str和str2引用的对象被改变了会发生什么?

str2.replace 'baz'
str2 # => 'baz'
str  # => 'baz'
str.object_id # => 2002
str2.object_id # => 2002

答:两个标签仍然指向同一个对象,但是对象本身已经发生了变化(它的内容已经改变为其他东西)。


这和最初的问题有什么关系?

这与第三季度和第四季度的情况基本相同;该方法获得传入给它的变量/ label (str2)的私有副本(str)。它不能改变标签str指向的对象,但它可以改变它们都引用的对象的内容,以包含else:

str = 'foo'

def mutate(str2)
  puts "str2: #{str2.object_id}"
  str2.replace 'bar'
  str2 = 'baz'
  puts "str2: #{str2.object_id}"
end

str.object_id # => 2004
mutate(str) # str2: 2004, str2: 2006
str # => "bar"
str.object_id # => 2004

Ruby使用“对象引用传递”

(使用Python术语。)

说Ruby使用“按值传递”或“按引用传递”并不是真正的描述性足够有帮助。我想现在大多数人都知道,这个术语(“值”vs“引用”)来自c++。

在c++中,“传递值”意味着函数获得变量的副本,对副本的任何更改都不会改变原始变量。对象也是如此。如果你通过值传递一个对象变量,那么整个对象(包括它的所有成员)都会被复制,对成员的任何更改都不会改变原始对象上的那些成员。(如果你通过值传递一个指针,这是不同的,但Ruby没有指针,AFAIK。)

class A {
  public:
    int x;
};

void inc(A arg) {
  arg.x++;
  printf("in inc: %d\n", arg.x); // => 6
}

void inc(A* arg) {
  arg->x++;
  printf("in inc: %d\n", arg->x); // => 1
}

int main() {
  A a;
  a.x = 5;
  inc(a);
  printf("in main: %d\n", a.x); // => 5

  A* b = new A;
  b->x = 0;
  inc(b);
  printf("in main: %d\n", b->x); // => 1

  return 0;
}

输出:

in inc: 6
in main: 5
in inc: 1
in main: 1

在c++中,“引用传递”意味着函数可以访问原始变量。它可以分配一个全新的字面值整数,然后原始变量也会有这个值。

void replace(A &arg) {
  A newA;
  newA.x = 10;
  arg = newA;
  printf("in replace: %d\n", arg.x);
}

int main() {
  A a;
  a.x = 5;
  replace(a);
  printf("in main: %d\n", a.x);

  return 0;
}

输出:

in replace: 10
in main: 10

如果参数不是一个对象,Ruby使用按值传递(在c++的意义上)。但是在Ruby中,所有的东西都是一个对象,所以在Ruby中没有c++意义上的值传递。

在Ruby中,“通过对象引用传递”(使用Python的术语)被使用:

在函数内部,对象的任何成员都可以有新的值赋给他们,这些变化将在函数返回.*后持续存在 在函数内部,将一个全新的对象赋值给变量会导致变量停止引用旧对象。但是在函数返回后,原始变量仍然会引用旧对象。

因此Ruby不使用c++意义上的“引用传递”。如果存在,那么将一个新对象分配给函数内的变量将导致在函数返回后忘记旧对象。

class A
  attr_accessor :x
end

def inc(arg)
  arg.x += 1
  puts arg.x
end

def replace(arg)
  arg = A.new
  arg.x = 3
  puts arg.x
end

a = A.new
a.x = 1
puts a.x  # 1

inc a     # 2
puts a.x  # 2

replace a # 3
puts a.x  # 2

puts ''

def inc_var(arg)
  arg += 1
  puts arg
end

b = 1     # Even integers are objects in Ruby
puts b    # 1
inc_var b # 2
puts b    # 1

输出:

1
2
2
3
2

1
2
1

*这就是为什么在Ruby中,如果你想在函数中修改一个对象,但在函数返回时忘记这些更改,那么你必须在对副本进行临时更改之前显式地创建一个对象的副本。