rails的接口查询详解

博客 分享
0 277
优雅殿下
优雅殿下 2023-04-23 13:27:34
悬赏:0 积分 收藏

rails的接口查询详解

Retrieving Objects from the Database

find

"find"是一种常用的数据库查询方法,在Rails中被用于从数据库中查找单个记录。它可以接收一个主键作为参数,也可以接收一组条件参数。

以下是"find"方法的使用方式:

# 使用主键查找单个记录
Model.find(1)

# 使用条件参数查找单个记录
Model.find_by(name: 'John')

在上面的示例中,"Model"是你需要查询记录的Rails模型,"find"方法可以接收一个主键作为参数,例如第一个示例中的"1",以查找具有指定主键的记录。如果找不到这样的记录,"find"方法会引发一个"ActiveRecord::RecordNotFound"异常。

"find"方法还可以接收一组条件参数,例如第二个示例中的"name: 'John'",以查找满足这些条件的单个记录。如果找不到这样的记录,"find_by"方法会返回"nil",而不是引发异常。

总之,"find"方法是一个常用的用于从数据库中查找单个记录的方法。

take

irb> customer = Customer.take
=> #<Customer id: 1, first_name: "Lifo">

这行代码使用Active Record的take方法从数据库中检索一个Customer对象,并将其分配给名为customer的变量。take方法不接受参数,它将从数据库中随机选择一个对象,而不考虑任何特定的排序或筛选条件。

如果数据库中没有任何Customer对象,则customer变量将被分配为nil。如果数据库中有多个Customer对象,则take方法将从这些对象中随机选择一个对象。

这个代码片段可以用于在Rails应用程序中获取一个随机的客户端对象,并将其用于某些特定的任务。例如,如果我们有一个名为"Customer of the Day"的功能,可以使用take方法从数据库中随机选择一个客户端,并将其作为今天的"客户端之星"。

需要注意的是,使用take方法时,不能保证总是返回相同的对象,因为它是从数据库中随机选择一个对象。如果需要按照特定的顺序或条件检索对象,则应使用其他查询方法,如findwhereorder等。

first

在Active Record中,first是一种查询方法,它用于检索符合条件的第一个对象。它可以与其他查询方法(如whereorder)一起使用,以指定特定的条件和顺序来检索对象。

例如,我们可以使用first方法从数据库中检索第一个创建的Product对象。代码如下:

@oldest_product = Product.order(created_at: :asc).first

这个代码片段使用了Active Record的orderfirst方法来构建查询。order方法按照创建时间(created_at)的升序排序,first方法返回第一个对象。

在查询中,如果没有符合条件的对象,则first方法将返回nil

first方法还可以接受一个可选参数,用于指定要返回的对象数量。例如,我们可以使用first(5)方法检索最早创建的5个Product对象。

需要注意的是,first方法返回的对象可能会随着数据库中数据的变化而变化。如果需要按照特定的顺序或条件检索对象,则应使用其他查询方法,如findwhereorder等。

last

在Active Record中,last是一种查询方法,它用于检索符合条件的最后一个对象。它可以与其他查询方法(如whereorder)一起使用,以指定特定的条件和顺序来检索对象。

例如,我们可以使用last方法从数据库中检索最后一个创建的Product对象。代码如下:

@latest_product = Product.order(created_at: :desc).last

这个代码片段使用了Active Record的orderlast方法来构建查询。order方法按照创建时间(created_at)的降序排序,last方法返回最后一个对象。

在查询中,如果没有符合条件的对象,则last方法将返回nil

last方法还可以接受一个可选参数,用于指定要返回的对象数量。例如,我们可以使用last(5)方法检索最近创建的5个Product对象。

需要注意的是,last方法返回的对象可能会随着数据库中数据的变化而变化。如果需要按照特定的顺序或条件检索对象,则应使用其他查询方法,如findwhereorder等。

find_by

在Active Record中,find_by是一种查询方法,它用于查找符合条件的第一个对象。与where方法不同的是,find_by返回的是一个对象而不是一个关系集合。如果没有符合条件的对象,则返回nil

find_by方法需要传递一个参数,用于指定查询条件。查询条件可以是任何一个模型中定义的属性,例如:

@product = Product.find_by(name: 'Widget')

这个查询将返回符合name属性为'Widget'的第一个Product对象。

查询条件也可以是多个属性,例如:

@product = Product.find_by(name: 'Widget', price: 10.99)

这个查询将返回符合name属性为'Widget'且price属性为10.99的第一个Product对象。

需要注意的是,find_by方法只返回符合条件的第一个对象。如果需要返回所有符合条件的对象,则应使用where方法。

另外,可以使用find_by!方法来查找符合条件的第一个对象,如果没有找到,则会抛出ActiveRecord::RecordNotFound异常。

find_each

在Active Record中,find_each是一种查询方法,它用于按批次检索大量记录。与find方法不同的是,find_each方法会将结果分批返回,以避免加载大量数据时内存不足的情况。

find_each方法需要传递一个块(block),块中的代码将应用于每个批次中的记录。例如:

Product.find_each(batch_size: 500) do |product|
  # 处理每个产品的代码
end

这个代码片段将每个Product对象分批检索,每个批次中包含500个记录。对于每个批次中的记录,块中的代码将被调用一次。

find_each方法还可以接受其他选项,例如:

  • start:指定查询起始的记录ID,默认为1。
  • finish:指定查询结束的记录ID,默认为nil,表示查询到最后一条记录。
  • batch_size:指定每个批次中记录的数量,默认为1000。
  • order:指定记录的排序方式,默认为主键的升序排序。

需要注意的是,find_each方法返回的结果是一个Enumerator对象。如果需要将结果作为数组返回,则应使用to_a方法,例如:

products = Product.find_each(batch_size: 500).to_a

另外,find_each方法仅适用于基于主键的查询。如果需要使用其他查询条件,应使用where方法。

假设我们有一个名为Product的模型,其中包含idnameprice属性。我们想要使用find_each方法检索id属性在2000到5000之间的所有产品,并按照价格(price)降序排序。我们可以这样实现:

Product.where(id: 2000..5000).order(price: :desc).find_each(start: 2000, batch_size: 500) do |product|
  # 处理每个产品的代码
end

这个代码片段使用了where方法指定了查询条件,使用order方法指定了排序方式。同时,我们使用了start选项来指定起始的记录ID为2000,使用了batch_size选项来指定每个批次中包含500条记录。

在块中,我们可以使用product变量访问每个批次中的记录,并执行必要的处理。

需要注意的是,find_each方法返回的结果是一个Enumerator对象。如果需要将结果作为数组返回,则应使用to_a方法,例如:

products = Product.where(id: 2000..5000).order(price: :desc).find_each(start: 2000, batch_size: 500).to_a

另外,find_each方法仅适用于基于主键的查询。如果需要使用其他查询条件,应使用where方法。

Conditions

Pure String Conditions

在Active Record中,可以使用“纯字符串条件”(Pure String Conditions)来指定查询条件。纯字符串条件是指用字符串表示的查询条件,可以在where方法中直接使用。

例如,我们可以使用以下字符串条件来查询Product模型中价格在10到20之间的产品:

Product.where("price BETWEEN 10 AND 20")

这个查询中,我们使用了字符串"price BETWEEN 10 AND 20"作为查询条件。这个字符串指定了价格在10到20之间的产品。使用where方法将这个字符串作为参数传递给Product模型,即可执行查询。

需要注意的是,使用纯字符串条件时,应确保字符串中的查询语句是安全的,以避免SQL注入等安全问题。如果字符串中包含用户输入的内容,应使用参数化查询(Parameterized Queries)来保证查询的安全性。

除了where方法,纯字符串条件还可以用于其他查询方法,如find_by_sqljoins等。但是,尽可能地使用Active Record的查询API(如wherejoinsgrouporder等)来构建查询,可以使查询更易于阅读、维护和安全。

Array Conditions

在Active Record中,可以使用“数组条件”(Array Conditions)来指定查询条件。数组条件是指将查询条件表示为数组形式,可以在where方法中直接使用。

例如,我们可以使用以下数组条件来查询Product模型中价格在10到20之间的产品:

Product.where(price: 10..20)

这个查询中,我们使用了price: 10..20作为查询条件。这个条件指定了价格在10到20之间的产品。使用where方法将这个条件作为参数传递给Product模型,即可执行查询。

数组条件还可以用于指定多个查询条件,例如:

Product.where("name LIKE ?", "%widget%").where(price: 10..20)

这个查询中,我们使用了两个条件来查询产品。第一个条件使用了纯字符串条件,查询名称中包含“widget”的产品;第二个条件使用了数组条件,查询价格在10到20之间的产品。

需要注意的是,数组条件只适用于等于(=)操作符和范围(IN)操作符。如果需要使用其他操作符,应使用纯字符串条件。

另外,数组条件还可以用于指定NULL值的查询条件,例如:

Product.where(price: nil)

这个查询将返回价格为空(NULL)的产品。要查询非空值,可以使用where.not方法,例如:

Product.where.not(price: nil)

这个查询将返回价格非空的产品。

总之,数组条件是一种方便、易于理解和安全的查询方法,可以使查询代码更加简洁和易于维护。

Placeholder Conditions

在Active Record中,可以使用“占位符条件”(Placeholder Conditions)来指定查询条件。占位符条件是指使用?占位符来表示查询条件,可以在where方法中直接使用。

例如,我们可以使用以下占位符条件来查询Product模型中价格在10到20之间的产品:

Product.where("price BETWEEN ? AND ?", 10, 20)

这个查询中,我们使用了字符串"price BETWEEN ? AND ?"作为查询条件,其中?表示占位符。使用where方法将这个字符串和两个参数(10和20)作为参数传递给Product模型,即可执行查询。

占位符条件还可以用于指定多个查询条件,例如:

Product.where("name LIKE ? AND price BETWEEN ? AND ?", "%widget%", 10, 20)

这个查询中,我们使用了三个占位符来查询产品。第一个占位符表示名称中包含“widget”的产品;第二个占位符表示价格大于等于10的产品;第三个占位符表示价格小于等于20的产品。

占位符条件可以有效地避免SQL注入等安全问题,因为查询条件中的值不会被直接拼接到查询语句中,而是使用占位符传递给数据库引擎进行处理。

需要注意的是,占位符条件不能用于指定列名、表名等标识符,只能用于指定查询条件的值。如果需要使用列名或表名等标识符,应使用纯字符串条件。

总之,占位符条件是一种方便、安全和可读性较高的查询方法,可以避免SQL注入等安全问题,建议在查询中使用。

Conditions That Use LIKE

在Active Record中,可以使用LIKE操作符和占位符条件来进行模糊查询。LIKE操作符用于匹配字符串,可以在where方法中直接使用。

例如,我们可以使用以下条件来查询Product模型中名称包含“widget”的产品:

Product.where("name LIKE ?", "%widget%")

这个查询中,我们使用了字符串"name LIKE ?"作为查询条件,其中?表示占位符。使用where方法将这个字符串和"%widget%"作为参数传递给Product模型,即可执行查询。"%widget%"表示名称中包含“widget”的字符串,%表示匹配任意字符。

LIKE操作符还支持以下通配符:

  • %:匹配任意字符(包括空格)。
  • _:匹配单个字符。
  • []:匹配方括号内任意一个字符。
  • [^]:匹配不在方括号内的任意一个字符。

例如,我们可以使用以下条件来查询名称以“w”开头、后面跟着两个任意字符、然后是“dget”的产品:

Product.where("name LIKE ?", "w__dget")

这个查询中,我们使用了字符串"name LIKE ?"作为查询条件,其中?表示占位符。使用where方法将这个字符串和"w__dget"作为参数传递给Product模型,即可执行查询。"w__dget"中的两个下划线表示匹配两个任意字符。

需要注意的是,LIKE操作符比较耗费计算资源,因为它需要对每条记录进行模式匹配。如果匹配的字符串很长或匹配的范围很大,查询性能可能会受到影响。

总之,LIKE操作符是一种非常有用的查询条件,可以用来进行模糊查询。在使用LIKE操作符时,应该注意通配符的使用,以及查询性能的影响。

Hash Conditions

在Active Record中,可以使用哈希条件(Hash Conditions)来指定查询条件。哈希条件是指使用哈希表(Hash)来表示查询条件,可以在where方法中直接使用。

例如,我们可以使用以下哈希条件来查询Product模型中价格在10到20之间的产品:

Product.where(price: 10..20)

这个查询中,我们使用了一个哈希表{price: 10..20}作为查询条件,其中price是列名,10..20表示价格在10到20之间的范围。使用where方法将这个哈希表作为参数传递给Product模型,即可执行查询。

哈希条件还可以用于指定多个查询条件,例如:

Product.where(name: "widget", price: 10..20)

这个查询中,我们使用了一个哈希表{name: "widget", price: 10..20}来查询产品。这个哈希表表示名称为“widget”且价格在10到20之间的产品。

哈希条件的优点是可读性高,可以直接使用列名作为键名,不需要使用字符串或占位符。同时,哈希条件也可以指定多个查询条件,更加灵活。

需要注意的是,哈希条件只能用于指定相等条件、范围条件和空值条件,不能用于指定其他类型的条件,例如模糊查询和复杂的逻辑查询。如果需要使用这些条件,应该使用字符串条件或其他类型的查询条件。

总之,哈希条件是一种方便、可读性高的查询方法,可以用于指定相等条件、范围条件和空值条件。在查询中使用哈希条件可以使代码更加简洁、易读。

NOT Conditions

在Active Record中,可以使用not方法来对查询条件取反。not方法用于将查询条件取反,可以在where方法中使用。

例如,我们可以使用以下条件来查询Product模型中不是价格在10到20之间的产品:

Product.where.not(price: 10..20)

这个查询中,我们使用了where.not方法来表示价格不在10到20之间的条件。使用where.not方法将这个条件作为参数传递给Product模型,即可执行查询。

not方法还可以用于对复杂条件进行取反,例如:

Product.where.not("name LIKE ?", "%widget%").where.not(price: 10..20)

这个查询中,我们使用了两个where.not方法来查询名称不包含“widget”且价格不在10到20之间的产品。第一个where.not方法使用字符串条件进行模糊查询,第二个where.not方法使用哈希条件表示价格不在10到20之间。使用where.not方法将这个条件作为参数传递给Product模型,即可执行查询。

需要注意的是,not方法只能对简单条件和复杂条件的组合进行取反,不能对复杂的逻辑条件进行取反。如果需要对复杂的逻辑条件进行取反,应该使用逻辑运算符(例如ANDORNOT)来组合条件。

总之,not方法是一种对查询条件取反的方法,可以用于简单条件和复杂条件的组合。在使用not方法时,应该注意条件的取反方式和逻辑关系,以避免出现查询错误。

OR Conditions

在Active Record中,可以使用or方法来对查询条件进行逻辑或(OR)运算。or方法用于将两个查询条件进行逻辑或运算,可以在where方法中使用。

例如,我们可以使用以下条件来查询Product模型中价格小于10或价格大于20的产品:

Product.where("price < 10").or(Product.where("price > 20"))

这个查询中,我们使用了where方法和or方法来查询价格小于10或价格大于20的产品。第一个where方法使用字符串条件查询价格小于10的产品,第二个where方法使用字符串条件查询价格大于20的产品。使用or方法将这两个查询条件进行逻辑或运算,即可得到价格小于10或价格大于20的产品列表。

or方法还可以和其他查询方法一起使用,例如:

Product.where("name LIKE ?", "%widget%").or(Product.where("price < 10"))

这个查询中,我们使用了where方法和or方法来查询名称包含“widget”或价格小于10的产品。第一个where方法使用字符串条件进行模糊查询,第二个where方法使用字符串条件查询价格小于10的产品。使用or方法将这两个查询条件进行逻辑或运算,即可得到名称包含“widget”或价格小于10的产品列表。

需要注意的是,or方法只能用于两个查询条件的逻辑或运算,不能用于多个查询条件的逻辑或运算。如果需要对多个查询条件进行逻辑或运算,应该使用where方法和逻辑运算符(例如OR)来组合条件。

总之,or方法是一种对查询条件进行逻辑或运算的方法,可以用于两个查询条件的组合。在使用or方法时,应该注意逻辑关系和条件的组合方式,以避免出现查询错误。

AND Conditions

在Active Record中,可以使用where方法对查询条件进行逻辑与(AND)运算。where方法用于将多个查询条件进行逻辑与运算,可以通过多次调用where方法来实现。

例如,我们可以使用以下条件来查询Product模型中名称包含“widget”且价格在10到20之间的产品:

Product.where("name LIKE ?", "%widget%").where(price: 10..20)

Product.where("name LIKE ?", "%widget%").where(price: 10..20)

这个查询中,我们使用了两次where方法来查询名称包含“widget”且价格在10到20之间的产品。第一个where方法使用字符串条件进行模糊查询,第二个where方法使用哈希条件查询价格在10到20之间的产品。使用两次where方法将这两个查询条件进行逻辑与运算,即可得到名称包含“widget”且价格在10到20之间的产品列表。

where方法可以和其他查询方法一起使用,例如:

Product.where("name LIKE ?", "%widget%").where.not(price: 10..20)

这个查询中,我们使用了两次where方法来查询名称包含“widget”且价格不在10到20之间的产品。第一个where方法使用字符串条件进行模糊查询,第二个where方法使用not方法将价格在10到20之间的条件取反。使用两次where方法将这两个查询条件进行逻辑与运算,即可得到名称包含“widget”且价格不在10到20之间的产品列表。

需要注意的是,where方法可以多次调用来实现多个查询条件的逻辑与运算。在使用where方法时,应该注意逻辑关系和条件的组合方式,以避免出现查询错误。

总之,where方法是一种对查询条件进行逻辑与运算的方法,可以通过多次调用来实现多个查询条件的组合。在使用where方法时,应该注意逻辑关系和条件的组合方式,以避免出现查询错误。

Ordering

在Active Record中,可以使用order方法来对查询结果进行排序。order方法用于按照指定的字段对查询结果进行排序,可以在allwherefind_by等查询方法中使用。

例如,我们可以使用以下条件来查询Product模型中价格从低到高排序的产品:

Product.order(price: :asc)

这个查询中,我们使用了order方法来对查询结果按照价格从低到高排序。使用哈希条件将排序字段和排序方式传递给order方法,即可对查询结果进行排序。

order方法还可以对多个字段进行排序,例如:

Product.order(price: :asc, created_at: :desc)

这个查询中,我们使用了order方法来对查询结果先按照价格从低到高排序,再按照创建时间从新到旧排序。使用哈希条件将排序字段和排序方式传递给order方法,即可对查询结果进行多字段排序。

需要注意的是,order方法只能对查询结果进行排序,不能对查询条件进行排序。如果需要对查询条件进行排序,应该使用where方法和排序字段来实现。

总之,order方法是一种对查询结果进行排序的方法,可以按照指定的字段和排序方式对查询结果进行排序。在使用order方法时,应该注意排序字段和排序方式的传递方式,以得到正确的排序结果。

Selecting Specific Fields

在Active Record中,可以使用select方法来选择查询结果中的特定字段。select方法用于从查询结果中选择指定的字段,可以在allwherefind_by等查询方法中使用。

例如,我们可以使用以下条件来查询Product模型中名称和价格字段的产品:

Product.select(:name, :price)

这个查询中,我们使用了select方法来选择名称和价格字段。使用符号或字符串传递要选择的字段名给select方法,即可从查询结果中选择指定的字段。

select方法还可以选择计算字段或使用别名,例如:

Product.select("name, price, price * 0.8 AS discounted_price")

这个查询中,我们使用了select方法来选择名称、价格和打折后价格(使用价格乘以0.8计算)。使用字符串传递要选择的字段名或计算表达式给select方法,即可从查询结果中选择指定的字段或计算字段。

需要注意的是,select方法只能选择查询结果中已有的字段或计算字段,不能选择不存在的字段。如果需要选择不存在的字段,应该使用select_raw方法和SQL语句来实现。

总之,select方法是一种从查询结果中选择特定字段的方法,可以选择已有的字段或计算字段,并使用别名来改变字段名。在使用select方法时,应该注意选择字段的名字和计算表达式的正确性,以得到正确的查询结果。

Limit and Offset

在Active Record中,可以使用limitoffset方法来限制查询结果的数量和偏移量。limit方法用于限制查询结果的数量,offset方法用于设置查询结果的偏移量,可以在allwherefind_by等查询方法中使用。

例如,我们可以使用以下条件来查询Product模型中前10个产品:

Product.limit(10)

这个查询中,我们使用了limit方法来限制查询结果的数量为10。使用整数传递要限制的数量给limit方法,即可对查询结果进行数量限制。

offset方法用于设置查询结果的偏移量,例如:

Product.offset(10).limit(10)

这个查询中,我们使用了offset方法来设置查询结果的偏移量为10,然后使用limit方法来限制查询结果的数量为10。使用整数传递要设置的偏移量给offset方法,即可对查询结果进行偏移量设置。

需要注意的是,offsetlimit方法的调用顺序非常重要。如果先调用limit方法再调用offset方法,偏移量会被忽略,数量限制会应用于整个查询结果。因此,在使用offsetlimit方法时,应该始终按照正确的顺序进行调用。

总之,limitoffset方法是一种限制查询结果数量和偏移量的方法,可以对查询结果进行分页和限制。在使用这些方法时,应该注意调用的顺序和传递的参数,以得到正确的查询结果。

Group

在Active Record中,可以使用group方法来对查询结果进行分组。group方法用于按照指定的字段对查询结果进行分组,可以在allwherefind_by等查询方法中使用。

例如,我们可以使用以下条件来查询Order模型中每个用户的总订单金额:

Order.select("user_id, sum(price) as total_price").group(:user_id)

这个查询中,我们使用了select方法来选择用户ID和总订单金额字段,并使用sum函数来计算每个用户的总订单金额。然后使用group方法来按照用户ID对查询结果进行分组。

group方法还可以按照多个字段进行分组,例如:

Order.select("user_id, product_id, sum(price) as total_price").group(:user_id, :product_id)

这个查询中,我们使用了select方法来选择用户ID、产品ID和总订单金额字段,并使用sum函数来计算每个用户和产品的总订单金额。然后使用group方法来按照用户ID和产品ID对查询结果进行分组。

需要注意的是,group方法只能对查询结果进行分组,不能对查询条件进行分组。如果需要对查询条件进行分组,应该使用having方法和SQL语句来实现。

总之,group方法是一种对查询结果进行分组的方法,可以按照指定的字段对查询结果进行分组,并使用聚合函数计算每个分组的值。在使用group方法时,应该注意选择分组的字段和聚合函数的正确性,以得到正确的查询结果。

Having

在Active Record中,可以使用having方法来对分组后的查询结果进行筛选。having方法用于在分组后对分组结果进行筛选,可以在group方法后使用。

例如,我们可以使用以下条件来查询Order模型中每个用户的总订单金额大于100的用户ID和总订单金额:

Order.select("user_id, sum(price) as total_price").group(:user_id).having("sum(price) > 100")

这个查询中,我们使用了select方法来选择用户ID和总订单金额字段,并使用sum函数来计算每个用户的总订单金额。然后使用group方法来按照用户ID对查询结果进行分组。最后使用having方法来筛选总订单金额大于100的用户。

having方法还可以使用多个筛选条件,例如:

Order.select("user_id, product_id, sum(price) as total_price").group(:user_id, :product_id).having("sum(price) > 100 and count(*) > 2")

这个查询中,我们使用了select方法来选择用户ID、产品ID和总订单金额字段,并使用sum函数来计算每个用户和产品的总订单金额。然后使用group方法来按照用户ID和产品ID对查询结果进行分组。最后使用having方法来筛选总订单金额大于100且订单数量大于2的用户和产品。

需要注意的是,having方法只能在group方法后使用,用于对分组结果进行筛选。如果需要对查询条件进行筛选,应该使用where方法。

总之,having方法是一种对分组后的查询结果进行筛选的方法,可以按照指定的条件对分组结果进行筛选。在使用having方法时,应该注意筛选条件的正确性,以得到正确的查询结果。

Overriding Conditions

unscope

在Active Record中,可以使用unscope方法来覆盖查询条件。unscope方法用于从查询中删除指定的查询条件,可以在whereordergroup等查询方法中使用。

例如,我们可以使用以下条件来查询Product模型中所有价格大于20的产品,并覆盖查询条件来查询所有产品:

Product.where("price > 20").unscope(:where)

这个查询中,我们使用了where方法来筛选价格大于20的产品,然后使用unscope方法来覆盖查询条件,从而查询所有产品。使用:where符号作为参数传递给unscope方法,即可删除where查询条件。

unscope方法还可以覆盖其他查询条件,例如:

Product.where("price > 20").order(name: :asc).unscope(:where, :order)

这个查询中,我们使用了where方法来筛选价格大于20的产品,然后使用order方法来按照名称升序对查询结果进行排序。最后使用unscope方法来覆盖查询条件和排序条件,从而查询所有产品并按照默认顺序排序。

需要注意的是,unscope方法会完全删除指定的查询条件,包括手动添加的查询条件和默认的查询条件。因此,在使用unscope方法时,应该注意查询条件的正确性,以避免删除错误的查询条件。

总之,unscope方法是一种覆盖查询条件的方法,可以从查询中删除指定的查询条件,以达到覆盖查询的目的。在使用unscope方法时,应该注意删除的查询条件的正确性,以得到正确的查询结果。

only

在Active Record中,可以使用only方法来限制查询结果中包含的字段。only方法用于选择要包含在查询结果中的字段,可以在select方法后使用。

例如,我们可以使用以下条件来查询Product模型中所有产品的名称和价格字段:

Product.select(:name, :price)

这个查询中,我们使用了select方法来选择要包含在查询结果中的字段,即名称和价格字段。查询结果中只包含这两个字段,其他字段将被忽略。

only方法还可以选择其他字段,例如:

Product.select(:name, :price).only(:name)

这个查询中,我们使用了select方法来选择要包含在查询结果中的字段,即名称和价格字段。然后使用only方法来限制查询结果中包含的字段,即只包含名称字段。价格字段将被忽略。

需要注意的是,only方法只能限制查询结果中包含的字段,不能选择排除的字段。如果需要排除指定的字段,应该使用select方法和except方法。

总之,only方法是一种限制查询结果中包含的字段的方法,可以选择要包含在查询结果中的字段,以达到查询所需字段的目的。在使用only方法时,应该注意选择的字段的正确性,以得到正确的查询结果。

reselect

在Active Record中,可以使用reselect方法来对查询结果进行重新选择。reselect方法用于重新选择要包含在查询结果中的字段,可以在select方法后使用。

例如,我们可以使用以下条件来查询Product模型中所有产品的名称和价格字段,并重新选择只包含名称字段:

Product.select(:name, :price).reselect(:name)

这个查询中,我们使用了select方法来选择要包含在查询结果中的字段,即名称和价格字段。然后使用reselect方法来重新选择要包含在查询结果中的字段,即只包含名称字段。价格字段将被忽略。

reselect方法还可以重新选择其他字段,例如:

Product.select(:name, :price).reselect(:name, :description)

这个查询中,我们使用了select方法来选择要包含在查询结果中的字段,即名称和价格字段。然后使用reselect方法来重新选择要包含在查询结果中的字段,即名称和描述字段。价格字段将被忽略。

需要注意的是,reselect方法会完全替换原来选择的字段,因此,如果需要保留原来选择的字段,应该将它们包含在重新选择的字段中。

总之,reselect方法是一种对查询结果进行重新选择的方法,可以重新选择要包含在查询结果中的字段,以达到重新选择字段的目的。在使用reselect方法时,应该注意重新选择的字段的正确性,以得到正确的查询结果。

reorder

在Active Record中,可以使用reorder方法来重新排序查询结果。reorder方法用于重新指定查询结果的排序方式,可以在order方法后使用。

例如,我们可以使用以下条件来查询Product模型中所有价格大于20的产品,并重新按照名称升序排序:

Product.where("price > 20").order(price: :desc).reorder(name: :asc)

这个查询中,我们使用了where方法来筛选价格大于20的产品,然后使用order方法来按照价格降序对查询结果进行排序。最后使用reorder方法来重新按照名称升序排序查询结果。

reorder方法还可以重新排序其他字段,例如:

Product.where("price > 20").order(price: :desc).reorder(price: :asc)

这个查询中,我们使用了where方法来筛选价格大于20的产品,然后使用order方法来按照价格降序对查询结果进行排序。最后使用reorder方法来重新按照价格升序排序查询结果。

需要注意的是,reorder方法会完全替换原来的排序方式,因此,如果需要在原来的排序方式基础上进行重新排序,应该将原来的排序条件包含在重新排序的条件中。

总之,reorder方法是一种对查询结果进行重新排序的方法,可以重新指定查询结果的排序方式,以达到重新排序的目的。在使用reorder方法时,应该注意重新排序的条件的正确性,以得到正确的查询结果。

reverse_order

在Active Record中,可以使用reverse_order方法来对查询结果进行反向排序。reverse_order方法用于对查询结果的排序方式进行反向排序,可以在order方法后使用。

例如,我们可以使用以下条件来查询Product模型中所有产品,并按照价格降序排序:

Product.order(price: :desc)

这个查询中,我们使用了order方法来按照价格降序对查询结果进行排序。

现在,如果我们想要对查询结果按照价格升序排序,可以使用reverse_order方法:

Product.order(price: :desc).reverse_order

这个查询中,我们使用了order方法来按照价格降序对查询结果进行排序,然后使用reverse_order方法来对排序方式进行反向排序,即按照价格升序排序。

需要注意的是,reverse_order方法只是对排序方式进行反向排序,不会改变原来的排序条件。如果需要重新指定排序条件,应该使用order方法。

总之,reverse_order方法是一种对查询结果进行反向排序的方法,可以对原来的排序方式进行反向排序,以达到反向排序的目的。在使用reverse_order方法时,应该注意原来的排序条件,以得到正确的查询结果。

rewhere

在Active Record中,可以使用rewhere方法来对查询结果进行重新筛选。rewhere方法用于重新指定查询结果的筛选条件,可以在where方法后使用。

例如,我们可以使用以下条件来查询Product模型中所有价格大于20的产品,并重新筛选价格大于30的产品:

Product.where("price > 20").rewhere("price > 30")

这个查询中,我们使用了where方法来筛选价格大于20的产品,然后使用rewhere方法来重新筛选价格大于30的产品。

需要注意的是,rewhere方法会完全替换原来的筛选条件,因此,如果需要在原来的筛选条件基础上进行重新筛选,应该将原来的筛选条件包含在重新筛选的条件中。

总之,rewhere方法是一种对查询结果进行重新筛选的方法,可以重新指定查询结果的筛选条件,以达到重新筛选的目的。在使用rewhere方法时,应该注意重新筛选条件的正确性,以得到正确的查询结果。

Null Relation

在Active Record中,"Null Relation"是一个空的关系对象,它表示在数据库中没有任何记录。它通常用作尚未定义的关系的占位符,或者作为构建更复杂查询的基础关系。

可以使用none方法创建一个空的关系对象,它返回同类型的空关系:

Product.none # 返回一个Product模型的空关系对象

空关系对象在大多数情况下像普通的关系对象一样,但是当查询时不会执行任何SQL查询。例如,对空关系对象调用to_acountany?方法将返回一个空数组或false:

Product.none.to_a # 返回 []
Product.none.count # 返回 0
Product.none.any? # 返回 false

空关系对象通常用作构建更复杂查询的起点,通过在它们上面链接其他方法。例如,我们可以定义一个范围,它返回所有价格低于10美元的产品,从一个空的关系对象开始,然后添加其他条件:

class Product < ApplicationRecord
  scope :cheap, -> { none.where('price < ?', 10) }
end

总之,在Active Record中,空关系对象是一个空的关系对象,它表示在数据库中没有任何记录。它通常用作尚未定义的关系的占位符,或者作为构建更复杂查询的基础关系。

Readonly Objects

在Active Record中,只读对象是从数据库检索出来的对象,可以像其他对象一样访问,但不能保存回数据库。这在需要防止对某些记录进行意外更新或强制执行只读权限的情况下很有用。

要将对象标记为只读,可以在对象或检索对象的关系上使用readonly!方法:

product = Product.find(1)
product.readonly! # 将对象标记为只读

products = Product.where(category: 'books')
products.readonly! # 将关系标记为只读

一旦对象或关系被标记为只读,任何尝试更新或删除它们的操作都会引发ActiveRecord::ReadOnlyRecord异常。例如,如果尝试像这样更新只读对象:

product = Product.find(1)
product.readonly!
product.update(name: 'New Name') # 抛出 ActiveRecord::ReadOnlyRecord 异常

您还可以使用readonly方法检索只读关系,而不修改底层记录:

products = Product.where(category: 'books').readonly

此外,您可以在模型关联中设置readonly选项,以强制执行只读权限:

class Order < ApplicationRecord
  has_many :line_items, readonly: true
end

在此示例中,任何尝试修改订单的行项目都会引发ActiveRecord::ReadOnlyRecord异常。

总之,在Active Record中,只读对象是可以像其他对象一样访问的对象,但不能保存回数据库。可以使用readonly!方法将它们标记为只读,任何尝试更新或删除它们的操作都会引发异常。此外,可以使用readonly方法和关联的readonly选项强制执行只读权限。

Locking Records for Update

Optimistic Locking

在Active Record中,乐观锁定是一种并发控制机制,它使用版本号或时间戳等标记来检测并发更新,并防止数据冲突或竞争条件。

在Active Record中,可以使用lock_version属性或updated_at属性来实现乐观锁定。当使用乐观锁定时,每个记录都有一个版本号或时间戳,用于跟踪记录的修改历史。当尝试更新记录时,Active Record会检查版本号或时间戳是否与之前检索的值相同。如果不同,说明记录已经被其他并发用户更新,此时会抛出ActiveRecord::StaleObjectError异常,防止数据冲突。

例如,以下代码使用lock_version属性实现乐观锁定。每次更新产品对象时,lock_version属性的值将自动增加,以便检测并发更新:

product = Product.find(1)
product.update(name: 'New Name') # lock_version automatically incremented

在此示例中,每次更新产品对象时,lock_version属性的值将自动增加,以便检测并发更新,如果发现其他并发用户已经更新了该记录,则会抛出ActiveRecord::StaleObjectError异常。

可以在模型中使用lock_optimistic方法来启用乐观锁定。例如,以下代码在Product模型中启用乐观锁定:

class Product < ApplicationRecord
  lock_optimistic
end

在此示例中,lock_optimistic方法启用了乐观锁定,使用updated_at属性作为版本号。

需要注意的是,乐观锁定并不能完全防止并发更新,因为在检查和更新之间仍然存在时间窗口,可能会导致竞争条件。因此,在使用乐观锁定时,需要谨慎处理并发更新的情况,例如使用重试机制或合并冲突的算法等。

总之,在Active Record中,可以使用乐观锁定来检测并发更新,使用lock_version属性或updated_at属性来跟踪记录的版本号或时间戳。可以在模型中使用lock_optimistic方法来启用乐观锁定。需要注意的是,乐观锁定并不能完全防止并发更新,需要谨慎处理并发更新的情况。

Pessimistic Locking

在Active Record中,悲观锁定是一种并发控制机制,它通过锁定记录来防止其他并发用户同时修改同一条记录。悲观锁定可以确保在更新期间不会发生数据冲突或竞争条件,但可能会影响应用程序的性能和响应时间。

在Active Record中,可以使用ActiveRecord::Base.transaction方法在事务中实现悲观锁定,以确保在更新期间不会发生并发冲突。例如,以下代码将获取一个产品对象并在事务中将其锁定,然后将其价格增加10美元:

Product.transaction do
  product = Product.find(1)
  product.lock! # 悲观锁定
  product.update(price: product.price + 10)
end

在此示例中,我们使用lock!方法将产品对象锁定,以确保在更新期间其他并发用户不能同时访问该记录。然后,我们使用update方法将产品价格增加10美元。

还可以使用with_lock方法来在记录级别上进行悲观锁定。例如,以下代码将获取一个产品对象并在记录级别上将其锁定,然后将其价格增加10美元:

product = Product.find(1)
product.with_lock do
  product.update(price: product.price + 10)
end

在此示例中,我们使用with_lock方法在记录级别上锁定产品对象,并且只有在锁定期间才能更新该对象。然后,我们使用update方法将产品价格增加10美元。

需要注意的是,悲观锁定可能会影响应用程序的性能和响应时间,因此应该谨慎使用。如果锁定的时间过长,可能会导致其他并发用户的请求超时或阻塞。

总之,在Active Record中,可以使用悲观锁定机制为更新操作锁定记录,以防止并发用户同时修改同一条记录。可以使用transaction方法在事务中锁定记录,也可以使用with_lock方法在记录级别上悲观锁定记录。需要注意的是,悲观锁定可能会影响应用程序的性能和响应时间,因此应该谨慎使用。

Joining Tables

在关系型数据库中,表之间可以通过JOIN操作进行连接,以便从多个表中检索相关数据。在Active Record中,可以使用joins方法来执行表连接操作,并使用select方法选择要检索的列。

以下是一个简单的例子,假设我们有两个表usersposts,每个用户可以发布多篇帖子:

class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
  belongs_to :user
end

我们可以使用joins方法将这两个表连接起来,并选择要检索的列:

User.joins(:posts).select('users.name, posts.title')

在此示例中,我们使用joins方法将usersposts表连接起来,并使用select方法选择users表中的name列和posts表中的title列。这将返回一个包含nametitle列的结果集,其中每个结果都是一个User对象和一个相关的Post对象。

还可以在joins方法中使用字符串或符号来指定连接类型(例如INNER JOINLEFT OUTER JOIN等),以及条件表达式来指定连接条件。例如,以下代码使用INNER JOIN连接usersposts表,并使用where方法指定连接条件:

User.joins('INNER JOIN posts ON users.id = posts.user_id').where('posts.published = ?', true)

在此示例中,我们使用joins方法和字符串来指定INNER JOIN连接类型和连接条件。然后,我们使用where方法指定了一个条件表达式,以筛选出已发布的帖子。

总之,在Active Record中,可以使用joins方法执行表连接操作,并使用select方法选择要检索的列。可以使用字符串或符号来指定连接类型和条件表达式,以控制连接的行为。

Eager Loading Associations

includes

在Active Record中,includes方法用于执行“eager loading”操作,以减少查询次数和提高性能。它可以同时加载主对象和其关联对象的数据,避免在每次访问关联对象时都执行一次查询操作。

includes方法可以接受一个或多个关联的名称,用于指定要加载的关联对象。例如,假设我们有一个User类,它与一个Post类相关联:

class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
  belongs_to :user
end

如果我们要加载一个用户及其所有帖子,我们可以使用includes方法来执行“eager loading”操作:

user = User.includes(:posts).find(1)

在此示例中,我们使用includes方法来加载与用户对象相关联的所有帖子。这将执行两个查询:一个查询用户,另一个查询该用户的所有帖子。然后,我们可以访问user.posts属性,以访问所有帖子对象,而不必再执行额外的查询。

需要注意的是,当使用includes方法时,如果没有使用references方法指定关联对象的表名,那么在执行查询时,Active Record可能会忽略关联对象的查询条件。这可能会导致在关联对象中返回未符合条件的数据。因此,在使用includes方法时,建议使用references方法以确保关联对象的查询条件得到正确的应用。

includes方法还可以接受一个块,在块中可以对关联对象进行进一步的操作。例如,以下代码将会加载所有文章的评论,并对每个评论进行排序:

Post.includes(:comments) do
  order('comments.created_at ASC')
end

在此示例中,我们使用includes方法加载所有文章的评论,并使用块对每个评论进行排序。这将执行两个查询:一个查询文章,另一个查询所有评论。然后,我们可以访问post.comments属性,以访问所有评论对象,并确保它们按照创建时间升序排列。

总之,在Active Record中,includes方法用于执行“eager loading”操作,以减少查询次数和提高性能。它可以同时加载主对象和其关联对象的数据,并可以接受一个或多个关联的名称。需要注意的是,在使用includes方法时,应该使用references方法指定关联对象的表名,以确保关联对象的查询条件得到正确的应用。

preload

在Active Record中,preload方法用于预加载关联对象的数据,从而提高查询性能。与includes方法不同的是,preload方法会分别执行主对象和关联对象的查询,而不是使用SQL的JOIN语句将它们一起加载。这意味着,在使用preload方法时,如果访问关联对象的属性,将不会触发额外的数据库查询,而是使用预加载的数据来获取这些属性的值。

preload方法可以接受一个或多个关联的名称,用于指定要预加载的关联对象。例如,假设我们有一个User类,它与一个Post类相关联:

class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
  belongs_to :user
end

如果我们要预加载一个用户及其所有帖子,我们可以使用preload方法来执行预加载操作:

user = User.preload(:posts).find(1)

在此示例中,我们使用preload方法预加载与用户对象相关联的所有帖子。这将执行两个查询:一个查询用户,另一个查询该用户的所有帖子。然后,我们可以访问user.posts属性,以访问所有帖子对象,而不必再执行额外的查询。

需要注意的是,与includes方法不同,preload方法不会将关联对象的数据合并到主对象中。因此,在使用preload方法时,访问关联对象的属性将会触发额外的查询。如果需要访问关联对象的属性,建议使用includes方法。

总之,在Active Record中,preload方法用于预加载关联对象的数据,从而提高查询性能。它可以接受一个或多个关联的名称,并会分别执行主对象和关联对象的查询。需要注意的是,在使用preload方法时,访问关联对象的属性将会触发额外的查询,因此建议使用includes方法。

eager_load

在Active Record中,eager_load方法用于执行“eager loading”操作,类似于includes方法,但不同之处在于它使用SQL的JOIN语句将主对象和关联对象的数据一起加载,从而提高查询性能。与preload方法不同的是,eager_load方法会将关联对象的数据合并到主对象中,因此,在访问关联对象的属性时,不会触发额外的数据库查询。

eager_load方法可以接受一个或多个关联的名称,用于指定要加载的关联对象。例如,假设我们有一个User类,它与一个Post类相关联:

class User < ApplicationRecord
  has_many :posts
end

class Post < ApplicationRecord
  belongs_to :user
end

如果我们要加载一个用户及其所有帖子,我们可以使用eager_load方法来执行“eager loading”操作:

user = User.eager_load(:posts).find(1)

在此示例中,我们使用eager_load方法执行与用户对象相关联的所有帖子的“eager loading”操作。这将执行一个JOIN查询,将用户和帖子的数据一起加载。然后,我们可以访问user.posts属性,以访问所有帖子对象,而不必再执行额外的查询。

需要注意的是,与includes方法不同,eager_load方法不会将关联对象的数据预加载到主对象中。因此,在使用eager_load方法时,如果访问关联对象的属性,将会触发额外的查询。如果需要访问关联对象的属性,建议使用preload方法或includes方法。

总之,在Active Record中,eager_load方法用于执行“eager loading”操作,以减少查询次数和提高性能。它可以接受一个或多个关联的名称,并使用SQL的JOIN语句将主对象和关联对象的数据一起加载。需要注意的是,在使用eager_load方法时,访问关联对象的属性将会触发额外的查询,因此建议使用preload方法或includes方法。

Scopes

Scopes是Active Record中的一种特殊方法,它用于定义查询条件,以便在查询数据库时重复使用。Scopes可以接受任意数量的参数,包括其他Scopes、Lambdas或其他可执行代码块,以便对查询结果进行进一步筛选和排序。

Scopes通常作为类方法定义在Active Record模型中。例如,假设我们有一个Product类,其中包含一个price属性和一个published属性。我们可以在该类中定义一个Scopes,以便在查询价格低于某个值的已发布产品时重复使用:

class Product < ApplicationRecord
  scope :published, -> { where(published: true) }
  scope :price_below, ->(price) { where('price < ?', price) }
end

在此示例中,我们定义了两个Scopes:publishedprice_belowpublished Scope将筛选出已发布的产品,而price_below Scope将筛选出价格低于指定价格的产品。

我们可以在查询时使用这些Scopes,例如:

cheap_published_products = Product.published.price_below(50)

在此示例中,我们查询已发布产品的价格低于50的所有产品,通过链式调用publishedprice_below Scopes。

需要注意的是,Scopes返回的是Active Record关系对象,而不是实际的查询结果。这意味着,我们可以在Scopes中继续使用其他查询方法,例如orderlimitgroup等,以进一步筛选和排序查询结果。

总之,Scopes是Active Record中的一种特殊方法,用于定义查询条件,以便在查询数据库时重复使用。Scopes可以接受任意数量的参数,包括其他Scopes、Lambdas或其他可执行代码块,以便对查询结果进行进一步筛选和排序。使用Scopes可以使代码更易于维护和重用,并且可以提高查询性能。

Enums

Enums是Active Record中的一个特性,它可以将某些属性的值映射为一个预定义的列表。使用Enums可以使代码更加清晰和可读,同时也可以避免在代码中使用魔法数字或字符串,从而减少出错的可能性。

在Active Record中,Enums可以通过在模型中定义一个enum方法来定义。例如,假设我们有一个Order类,其中包含一个status属性,可以取pendingprocessingcompleted三个值。我们可以在该类中定义一个Enum,以便将这些值映射为一个预定义的列表:

class Order < ApplicationRecord
  enum status: [:pending, :processing, :completed]
end

在此示例中,我们定义了一个名为status的Enum,它可以取三个值:pendingprocessingcompleted。我们可以通过调用类方法来获取这些值:

Order.statuses
# => {"pending" => 0, "processing" => 1, "completed" => 2}

同时,也可以通过调用实例方法来获取当前属性的值:

order = Order.first
order.status
# => "pending"

Enums还提供了一些方便的方法,例如status_namestatus_before_type_cast等,可以帮助我们更方便地处理属性的值。例如,我们可以使用status_name方法将属性的值转换为一个可读的字符串:

order = Order.first
order.status_name
# => "Pending"

需要注意的是,Enums的值是基于整数的,从0开始。因此,我们可以通过指定一个自定义的整数值来映射枚举值,例如:

class Order < ApplicationRecord
  enum status: { pending: 1, processing: 2, completed: 3 }
end

在此示例中,我们指定了自定义的整数值来映射枚举值。

总之,Enums是Active Record中的一个特性,它可以将某些属性的值映射为一个预定义的列表。使用Enums可以使代码更加清晰和可读,同时也可以避免在代码中使用魔法数字或字符串,从而减少出错的可能性。在Active Record中,我们可以通过在模型中定义一个enum方法来定义Enums,并使用方便的方法来处理属性的值。

Understanding Method Chaining

方法链是一种编程技巧,在单个语句中链接多个方法调用。在Ruby和许多其他面向对象编程语言中,方法链是通过让每个方法返回调用它的对象来实现的,允许在同一语句中对同一对象调用另一个方法。

方法链经常用于ActiveRecord,Ruby on Rails的ORM层,以简洁易读的方式构建数据库查询。例如,考虑以下代码:

users = User.where(active: true).order(name: :asc).limit(10)

在此代码中,whereorderlimit方法被链接在一起,以构建一个数据库查询,找到前10个按名称升序排序的活动用户。每个方法都在前一个方法的结果上调用,允许以清晰易读的方式构建复杂的数据库查询。

方法链也可以在其他编程上下文中使用,以简化代码并使其更易读。例如,考虑以下代码:

result = some_array.select(&:even?).map(&:to_s).join(',')

在此代码中,selectmapjoin方法被链接在一起,以选择数组中的偶数元素,将它们映射为字符串,并将它们连接成逗号分隔的字符串。&:even?&:to_s语法是传递调用even?to_s方法的块的简写形式,分别传递给selectmap方法。

方法链可以是简化代码和使其更易读的强大技术。但是,重要的是要谨慎使用它,不要在单个语句中链接太多的方法,因为这可能会使代码难以理解和调试。此外,需要注意方法链的性能影响,因为每个方法调用都可能增加开销并减慢程序的速度。

Find or Build a New Object

在Ruby on Rails中,有两个方法可以用来创建一个新的对象或者查找现有的对象:find_or_initialize_byfind_or_create_by

find_or_initialize_by方法用于基于给定的属性查找数据库中的现有记录,如果找不到匹配的记录,则使用这些属性初始化一个新的记录。例如,考虑以下代码:

user = User.find_or_initialize_by(email: "example@example.com")

在这个代码中,find_or_initialize_byUser模型中查找一个email为"example@example.com"的记录。如果找到匹配的记录,则返回该记录。否则,将使用email为"example@example.com"初始化一个新的User对象。

另一方面,find_or_create_by方法用于基于给定的属性查找数据库中的现有记录,如果找不到匹配的记录,则使用这些属性创建一个新的记录。例如,考虑以下代码:

user = User.find_or_create_by(email: "example@example.com")

在这个代码中,find_or_create_byUser模型中查找一个email为"example@example.com"的记录。如果找到匹配的记录,则返回该记录。否则,将创建一个新的User对象,并将其保存到数据库中,email为"example@example.com"。

这两个方法在不同的情况下都可以有用。当您想检查记录是否存在,但不想在不存在时创建新记录时,可以使用find_or_initialize_by。另一方面,当您想确保具有给定属性的记录存在,并且如果不存在则想创建一个时,可以使用find_or_create_by

需要注意的是,这两种方法都依赖于传递给它们的属性来查找或创建对象。因此,需要确保这些属性是唯一的,并且可以用于在数据库中唯一地标识对象,例如在数据库表上使用唯一索引或约束。

posted @ 2023-04-23 12:29  卓亦苇  阅读(16)  评论(0编辑  收藏  举报
回帖
    优雅殿下

    优雅殿下 (王者 段位)

    2017 积分 (2)粉丝 (47)源码

    小小码农,大大世界

     

    温馨提示

    亦奇源码

    最新会员