【问题标题】:Variable Syntax in Ruby on RailsRuby on Rails 中的变量语法
【发布时间】:2019-10-31 22:01:12
【问题描述】:

我正在阅读 ruby​​ on rails 教程,但我仍然对语法有些困惑。

class PostController < ApplicationController
    def index
        @posts = Post.all
    end

    def create
        @post = Post.new(post_params)
    end 

private
    def post_params
        params.require(:post).permit(:title, :body)
    end
end

我来自 javascript 背景。

据我了解,@posts@ 是从模型文件夹中的 Post 类实例化的变量。但是:post: 到底是什么意思,params 来自哪里,又是什么?

【问题讨论】:

  • 感谢您解决这个问题。

标签: ruby-on-rails ruby


【解决方案1】:

这里有两件事。一种是实例变量,或@-前缀变量。这些是对象实例的本地。即@posts 可以被任何在同一个对象实例上调用的方法 访问。这些不在对象实例之间共享。

如果您熟悉 JavaScript,这很像 this.posts,其中 this 部分表示“此对象的本地”而不是“名为 posts 的某个变量”。

Rails 使用这些作为从控制器到视图共享数据的一种方式。您定义的任何实例变量都会传播到可以使用它们的视图实例。这是 Rails 独有的行为。

:post 是一个符号,或者是一个内部化字符串。从概念上讲,符号只是表示该特定名称的常量。 :post 的每个实例都指向同一个对象,因此这意味着它们在存储方面非常便宜,而且比较也很简单。

比较两个字符串时需要做更多的工作。 "post" == "post":post == :post 需要做更多的计算。您会在 Ruby 哈希结构中看到很多符号键,它们通常用于表示事物,例如方法调用或类名,如 belongs_to :model_type 等。

将它们视为“单例字符串”。

关于 Ruby 编程语言的一个更奇特的事情是它对符号的依赖程度很高,以及符号很容易与字符串混淆,因为许多方法会采取非此即彼的方式,而另一些则非常特殊。

当您在概念上习惯它们时,它们确实非常方便,但在 C++、Python、JavaScript 等其他语言中却看不到。

【讨论】:

  • 这更有意义。感谢您的详尽回答
  • 实际上,ECMAScript 确实Symbols,它使用它们的目的与 Ruby 相同。它们在许多方面都非常相似,除了一个关键的区别:虽然 Ruby Symbols 保证具有相同名称的 Symbols总是相同,但 ECMAScript 的 Symbols 保证它们是即使同名也不相同。这允许它们被用作不可伪造的安全令牌。 Symbols 在 Ruby 和 ECMAScript 的共同祖先 Lisp 和 Smalltalk 家族中也很普遍。
  • Ruby 的Symbols 与使用Symbol.for 构造的ECMAScript Symbols 基本相同。
  • @JörgWMittag 从技术上讲,是的,但是没有像 Ruby 那样在 JavaScript 中创建它们的简写语法,而且你真的看不到 JavaScript 中普遍使用的符号,如果有的话。人们。我认为 Ruby 更接近于 Erlang's "atoms"
【解决方案2】:

首先,非常重要的是,没有“Ruby on Rails 中的可变语法”之类的东西

Ruby on Rails 只是一个用 Ruby 编写的框架。 Ruby 不允许用户代码更改基本语言语法。因此,Ruby on Rails 中的变量语法与 Sinatra 或 Padrino 中的变量语法完全相同,只是 Ruby 中的变量语法。这与 ECMAScript 相同,其中也没有“Express 中的可变语法”或“JQuery 中的可变语法”。

事实上,几乎所有语言都可以工作;允许用户代码更改语法的语言,更不用说更改诸如变量之类的基本事物的语法了,这只是主流之外的极少数方式。

据我了解,@posts@ 是从模型文件夹中的 Post 类实例化的变量。

我必须承认我无法解析你的意思。 Ruby 没有“文件夹”的概念,因此完全不相关。而且我不明白您所说的“实例化”是什么意思。但无论如何我都会尝试回答:

@posts 是一个instance variable。与 ECMAScript 中最接近的类似物是 private instance field。实例变量属于实例 (duh) aka 对象,而不是您最常看到的其他类型的变量:local variables belong to the lexical scope

请注意,这 不同于 private 字段,例如Java,其中 other 相同类型的对象被允许访问private 字段。 Ruby 具有真正的面向对象封装,其中只有对象本身被允许访问其实例变量。 (我相信 ECMAScript 提案具有相同的语义。)

但是 :post: 到底是什么意思和

这是一个 Symbol 文字。 ECMAScript 也有 Symbols,但与 Ruby 不同,它没有文字语法。

Ruby 从其祖先 Lisp 和 Smalltalk 继承了 Symbols。顺便说一句,它们也是 ECMAScript 的祖先(Lisp 通过 Scheme 和 Smalltalk 通过 NewtonScript → Act-1 → Self),因此两者之间有相似之处也就不足为奇了。

就像在 ECMAScript、Smalltalk 和 Lisp 中一样,Ruby 中的 Symbol 是一种表示“标签”或“名称”概念的数据类型。因此,每当您需要“命名”某些东西时,您都可以使用Symbol。例如,当您定义一个方法时,该方法定义的计算结果为 Symbol

def foo; end
#=> :foo

当你向 Ruby 询问对象的方法列表时,它会返回 ArraySymbols:

''.methods
#=> [:unpack1, :encode!, :include?, :%, :*, :+, :count, :partition, :sum, :next, :casecmp, :casecmp?, :insert, :<=>, :bytesize, :match?, :succ!, :match, :==, :===, :next!, :=~, :index, :[], :[]=, :getbyte, :rindex, :replace, :upto, :chr, :scrub, :empty?, :eql?, :undump, :scrub!, :setbyte, :byteslice, :clear, :+@, :-@, :capitalize, :upcase, :downcase, :downcase!, :dump, :upcase!, :split, :capitalize!, :swapcase!, :freeze, :inspect, :grapheme_clusters, :lines, :swapcase, :oct, :codepoints, :crypt, :bytes, :hex, :concat, :ljust, :length, :size, :chars, :succ, :scan, :reverse, :reverse!, :chop, :<<, :strip, :end_with?, :lstrip, :prepend, :rjust, :to_str, :to_sym, :intern, :delete_prefix, :chomp, :sub!, :to_s, :to_i, :to_f, :delete_suffix, :lstrip!, :gsub!, :chop!, :center, :sub, :ord, :start_with?, :delete_prefix!, :delete_suffix!, :chomp!, :rstrip, :delete, :rstrip!, :gsub, :tr_s!, :tr, :tr_s, :strip!, :squeeze, :tr!, :each_codepoint, :delete!, :squeeze!, :each_line, :each_byte, :each_char, :force_encoding, :each_grapheme_cluster, :hash, :slice!, :rpartition, :encoding, :unpack, :b, :valid_encoding?, :slice, :unicode_normalize, :unicode_normalize!, :unicode_normalized?, :ascii_only?, :to_c, :to_r, :encode, :clamp, :<=, :between?, :>=, :>, :<, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, :methods, :untrusted?, :trust, :singleton_methods, :tainted?, :private_methods, :untrust, :frozen?, :method, :public_send, :public_method, :singleton_method, :protected_methods, :define_singleton_method, :extend, :to_enum, :enum_for, :!~, :respond_to?, :object_id, :send, :display, :singleton_class, :nil?, :class, :yield_self, :clone, :dup, :itself, :untaint, :then, :taint, :!, :equal?, :!=, :instance_eval, :instance_exec, :__id__, :__send__]

当你要求 Ruby 在两个方法之间创建一个别名时,你会给它现有方法的名称,别名为Symbols:

alias_method :foo, :bar

等等。

不过,Ruby 和 ECMAScript 的 Symbols 之间有两个区别。

第一个是语法的区别,我在上面提到过:Ruby 有Symbols (:foo) 的字面语法,ECMAScript 没有。

第二个是重要的语义区别。 (您只询问了语法,但我相信这种差异对于具有 ECMAScript 背景的人来说很重要。)在 Ruby 中,Symbols 始终是实习生,这意味着当您按名称引用 Symbol 时,它总是相同的Symbol。换句话说:

:foo.equal?(:foo)
#=> true

'foo'.to_sym.equal?('foo'.to_sym)
#=> true

:foo.equal?('foo'.to_sym)
#=> true

等等。

而在 ECMAScript 中,完全相反是正确的:两个 Symbols从不相同,即使它们具有相同的名称

Symbol('foo') === Symbol('foo')
//=> false

这允许将 ECMAScript 中的 Symbols 用作不可伪造的安全令牌。例如,如果我想确保只有我信任的人可以调用特定方法,那么我可以将该方法存储在名称为 Symbol 的属性中:

const foo = {
    [Symbol('bar')]: () => console.log('Hello')
}

没有人将能够调用bar,因为不可能构造一个匹配属性键的Symbol。只有当我某人 exact Symbol 我用来存储该方法时,他们才能访问它。 (其实连我都不能调用这个方法,因为我忘了把Symbol放在某个地方,现在连我都不能再构造了!)

[注意:要使其真正起作用,我还必须使属性不可枚举,否则我仍然可以通过迭代对象来发现它。]

Ruby Symbols 无法做到这一点。

ECMAScript 有一个全局符号注册表,我可以使用Symbol.for 来存储和检索Symbols。 Ruby 的 Symbols 的行为就像在 ECMAScript 中使用 Symbol.for 一样。

params 来自哪里,它是什么?

如果您在没有发布的上下文的情况下问这个问题,答案将是:它可能是或者是一个局部变量一个消息发送(在 ECMAScript 中它将被称为“方法调用”)具有隐式接收器且没有参数列表。从语法上讲,在 Ruby 中,无法区分取消引用局部变量和发送带有隐式接收器且没有参数列表的消息。在 ECMAScript 中,这种歧义是不存在的,因为方法调用总是需要一个参数列表,即使它是空的。

为了知道它是消息还是局部变量,我们必须考虑上下文:局部变量是在对变量的第一次赋值被解析时创建的。 (注意,这很重要:它是在赋值被解析时创建的,而不是在求值时创建的。例如,if false then foo = 42 end 将创建局部变量 foo。)

在你的代码中,我们可以清楚的看到之前在本地范围内没有给params赋值(实际上params是范围内的第一个token,所以没有“之前”),这意味着它不能是局部变量,它必须是消息发送。

因此等价于

this.params().require(Symbol.for('post')).permit(Symbol.for('title'), Symbol.for('body'))

【讨论】:

    【解决方案3】:

    @posts 是一个实例变量 - 将其视为对象的私有字段或私有属性。

    :post 是一个符号 - 类似于常量字符串,在这种情况下用作哈希键(params[:id])或指示允许哪些参数的键(params.require(:post).permit(:title, :body)

    【讨论】:

    • 好的,我想我明白了。那么@posts 在技术上可以以任何方法调用和使用吗?我仍然对参数的实例化感到困惑。变量参数是一个错误的东西吗?
    • @posts 可以在任何方法中使用。 params 是一个用来保存 HTTP 参数的东西。
    【解决方案4】:

    @post 是一个实例变量。实例变量是与类的实例一样存在的变量。普通变量的范围要窄得多,让我举个例子:

    class Counter
      def initialize(start = 0)
        @counter = start
      end
    
      def increment
        @counter += 1
      end
    end
    
    counter = Counter.new
    counter.increment #=> 1
    counter.increment #=> 2
    

    如果你在哪里用 counter 替换 @counter 你会得到:

    class Counter
      def initialize(start = 0)
        counter = start
      end
    
      def increment
        counter += 1
      end
    end
    
    counter = Counter.new
    counter.increment # NoMethodError (undefined method `+' for nil:NilClass)
    

    不同之处在于counter是在构造函数中设置的,但仅在该方法中可用。所以在调用increment 时,使用的counter 是一个尚未初始化的变量。

    您可以阅读有关实例变量here 的更多信息。


    现在是问题的第二部分:post 与 : 的含义到底是什么意思”。 Ruby 有你应该从 JavaScript 中知道的字符串。 Ruby 也有符号,它们类似于字符串,但是是全局单例对象。它们具有比字符串更快的查找和比较时间,但是一旦使用了符号,它就会在整个程序持续时间内保持加载在符号寄存器 (Symbol.all_symbols) 中。它们主要用作哈希方法和标签的选项(其他语言中的字典)。

    为了向您展示我对单例对象的含义,让我举一个例子:

    :foo.object_id #=> 1686748
    :foo.object_id #=> 1686748
    

    如您所见,尽管我们似乎创建了两个实例,但该对象保持不变。第一次使用 :foo 时,它被创建并添加到寄存器中,第二次 Ruby 检测到 :foo 已经在寄存器中并返回该符号。

    为了向您展示不同之处,让我们展示相同的内容,但使用字符串:

    'foo'.object_id #=> 24742300
    'foo'.object_id #=> 26029360
    

    如您所见,两个字符串都有不同的对象 ID(尽管包含相同的内容),这使得它们成为不同的对象。

    您可以阅读有关符号here 的更多信息。


    对于你的问题的最后一部分,params 来自哪里,它是什么?”,我们将不得不看看 Ruby on Rails 框架。您的 PostController 继承自 ApplicationController ,而 ApplicationController 又继承自 ActionController::Base ,后者为您提供了一些辅助方法。 params 就是其中一种方法。

    在 Ruby 中,括号在调用方法时是可选的,并且在调用不带参数的方法时大多不用。这就是这里发生的事情,通过运行代码:

    params.require(:post).permit(:title, :body)
    

    您将首先调用params 方法。该方法继承自ActionController::Base,返回web请求的参数。然后在返回的参数上调用require,它获取符号/标签:post 下的值,如果它不存在,则会引发异常ActionController::ParameterMissing(这是require 行为的一部分)。然后,您使用参数:title:body 在返回值上调用permit,这允许这些标签/符号及其值。

    总结一下,下面一行将所有内容联系在一起:

    @post = Post.new(post_params)
    

    说,给实例变量@post分配一个新的post实例。您将post_params 方法的返回值传递给构造函数。在这种情况下,:title:body 被列入白名单,并将在初始化期间使用。


    最后要知道的是,Rails 做了一些幕后工作,以使控制器中的实例变量传递到视图。因此,当您为 @post 分配一个值时,您将能够在您正在呈现的视图中访问它。

    【讨论】:

      猜你喜欢
      • 2012-12-28
      • 1970-01-01
      • 2012-09-24
      • 2013-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多