Rails model

创建模型(Model)

在MVC开发中, model 指使用对应数据的Ruby类,也就是Models通过一个名为Active Record的Rails功能来和应用程序的数据库交互

要定义一个model,需要使用model generator:

生成model(数据库访问)
bin/rails generate model Tweet title:string body:text

model 命名冲突

警告

需要注意, model 的名字不能和Rails的应用名字相同!!!

我最初不知道这个Rails不允许 model 名字(数据库连接)和应用名字相同,也是就是我的项目名设置为 tweet ,然后我尝试创建Model命名也是 Tweet :

错误 使用了和应用项目同名的model命名
bin/rails generate model Tweet title:string body:text

此时报错显示 Tweet 名字是保留字或者已经在应用中使用了。Why?

通过 grep -R -i tweet * 可以看到在 config/application.rb 有一个冲突命名 module :

config/application.rb 内部包含了和应用名相同的 module 配置
require_relative "boot"

require "rails/all"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Tweet
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 7.1

    # Please, add to the `ignore` list any other `lib` subdirectories that do
    # not contain `.rb` files, or that should not be reloaded or eager loaded.
    # Common ones are `templates`, `generators`, or `middleware`, for example.
    config.autoload_lib(ignore: %w(assets tasks))

    # Configuration for the application, engines, and railties goes here.
    #
    # These settings can be overridden in specific environments using the files
    # in config/environments, which are processed later.
    #
    # config.time_zone = "Central Time (US & Canada)"
    # config.eager_load_paths << Rails.root.join("extras")
  end
end

原因是创建名为 tweet 的应用程序时,这个关键字 tweet 就是模块(module)命名保留,后续就不能作为模型(model)使用了。

为了避免这样的冲突,可以修改使用其他模型(model)名,或者采用更好的应用模块(module)命名。例如,在命名应用程序是,如果考虑到 tweet 会作为model命名(数据库表名),那么可以采用 tweets 作为应用名,既表示复数又能够避免冲突。

我重新构建了项目,命名项目名为 tweets 来重走一遍流程(毕竟刚开始工作量不多),后续吸取教训,在理解和遵循社区最佳实践基础上,合理命名。

  • 在修正了应用名 tweets 之后,重新执行一次定义model的命令(同上):

生成model(数据库访问)
bin/rails generate model Tweet title:string body:text

此时就能够正常运行,输出信息如下:

生成model(数据库访问)输出信息
      invoke  active_record
      create    db/migrate/20240507031631_create_tweets.rb
      create    app/models/tweet.rb
      invoke    test_unit
      create      test/models/tweet_test.rb
      create      test/fixtures/tweets.yml

上面的输出信息中显示生成了两个文件:

  • db/migrate/20240507031631_create_tweets.rb 数据库迁移文件

  • app/models/tweet.rb 模型文件

数据库迁移

  • db/migrate/20240507031631_create_tweets.rb 是Ruby编写的数据库抽象:

迁移数据库的文件
class CreateTweets < ActiveRecord::Migration[7.1]
  def change
    create_table :tweets do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end
end

注意到,这里我虽然创建的是 Tweet model,但是在数据库表中建立的却是 tweets

  • 在这个 tweets 表中,默认创建了一个自增主键(auto-incrementing primary key) id ,会自动从 id 1 开始增加

  • 创建表的部分,定义了 titlebody 两列,类型分别是 stringtext

  • 最后一行定义表是 t.timestamps ,这个方法实际上定义了两个附加列 created_atupdated_at

  • 执行数据库迁移指令:

数据库迁移
bin/rails db:migrate

输出信息显示了创建一个 tweets 表:

数据库迁移输出显示创建了 tweets
== 20240507031631 CreateTweets: migrating =====================================
-- create_table(:tweets)
   -> 0.0215s
== 20240507031631 CreateTweets: migrated (0.0216s) ============================

数据库交互

Rails提供了一个非常 神奇 的数据库交互方法,也就是控制台提供 irb 交互能够以对象方式操作数据库。下面是一个在数据库表 tweets 中插入一行记录并检查的交互过程

  • 加载Rails控制台:

Rails控制台
bin/rails console

此时提示:

Rails控制台提示
Loading development environment (Rails 7.1.3.2)
irb(main):001>
  • 输入以下命令,初始化一个Rails的 tweet 对象:

Rails初始化一个tweet对象
tweet = Tweet.new(title: "Hello Rails", body: "I am on Rails!")

此时会输出生成了对象,但是需要注意这个对象仅仅初始化,还没有真正保存到数据库

Rails初始化一个tweet对象输出信息
=> 
#<Tweet:0x00000001108a64c8
...
irb(main):002>
  • 输入保存命令:

Rails保存一个tweet对象
tweet.save

保存成功的信息输出

Rails保存一个tweet对象
  TRANSACTION (0.1ms)  begin transaction
  Tweet Create (9.3ms)  INSERT INTO "tweets" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?) RETURNING "id"  [["title", "Hello Rails"], ["body", "I am on Rails!"], ["created_at", "2024-05-07 07:52:58.316455"], ["updated_at", "2024-05-07 07:52:58.316455"]]
  TRANSACTION (0.4ms)  commit transaction
=> true
irb(main):003>
  • 查看和打印非常简单,就是直接调用对象:

Rails打印一个tweet对象
tweet

输出信息如下

Rails打印一个tweet对象时输出
=> 
#<Tweet:0x00000001108a64c8
 id: 1,
 title: "Hello Rails",
 body: "I am on Rails!",
 created_at: Tue, 07 May 2024 07:52:58.316455000 UTC +00:00,
 updated_at: Tue, 07 May 2024 07:52:58.316455000 UTC +00:00>
irb(main):004>

非常简单的方法,我们就得到了一个插入数据库的记录

  • 可以通过以下命令来查找记录,这里举id为1的查询:

查找id为1的记录
Tweet.find(1)

输出如下:

查找id为1的记录输出
  Tweet Load (1.0ms)  SELECT "tweets".* FROM "tweets" WHERE "tweets"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> 
#<Tweet:0x0000000110aca3d0
 id: 1,
 title: "Hello Rails",
 body: "I am on Rails!",
 created_at: Tue, 07 May 2024 07:52:58.316455000 UTC +00:00,
 updated_at: Tue, 07 May 2024 07:52:58.316455000 UTC +00:00>
irb(main):005>
  • 打印所有记录:

查找所有记录
Tweet.all

数据库数据List

在完成了上文的数据库交互数据添加之后,对于WEB开发来说,如何在页面上展示数据(模拟用户发布tweet之后的页面显示),成为基础功能。简单的实现也是通过经典的MVC实现:

  • 首先修订 app/controllers/tweets_controller.rb 控制器:

修订 app/controllers/tweets_controller.rb 添加索引index动作以便从数据库提取所有tweet
class TweetsController < ApplicationController
  def index
    @tweets = Tweet.all
  end
end
  • 然后对应修改 app/views/tweets/index.html.erb 视图模版:

app/views/tweets/index.html.erb 视图
<h1>Tweets</h1>

<ul>
  <% @tweets.each do |tweet| %>
    <li>
      <%= tweet.title %>
    </li>
  <% end %>
</ul>

<p>Find me in app/views/tweets/index.html.erb</p>

这里视图模版中使用了 HTML 和 ERB 混合:

  • ERB 是将Ruby代码前乳文档的模版系统

  • <% %> 表示 解析包含的Ruby代码

  • <%= %> 表示 解析包含的Ruby代码 并且将返回值输出

通过使用ERB,剋将常规Ruby程序嵌入ERB标记中,实现很多功能。需要注意的是,应该尽量保持ERB的tag内容短小,以方便代码阅读。

备注

数据库演示数据也可以通过 SQLite命令行交互 手工插入,这里我尝试了 SQLite current_timestamp 函数 插入案例增加了记录,这样就可以继续列表

上述简单的案例,就可以输出一个动态的列表展示在WEB页面

参考