react前端项目

The author selected the Electronic Frontier Foundation to receive a donation as part of the Write for DOnations program.

作者选择了电子前沿基金会来接受捐款,这是Write for DOnations计划的一部分。

介绍 (Introduction)

Ruby on Rails is a popular server-side web application framework, with over 42,000 stars on GitHub at the time of writing this tutorial. It powers a lot of the popular applications that exist on the web today, like GitHub, Basecamp, SoundCloud, Airbnb, and Twitch. With its emphasis on programmer experience and the passionate community that has built up around it, Ruby on Rails will give you the tools you need to build and maintain your modern web application.

Ruby on Rails是一种流行的服务器端Web应用程序框架,在撰写本教程时, GitHub上已有42,000多颗星 。 它支持当今网络上许多流行的应用程序,例如GitHub , Basecamp , SoundCloud , Airbnb和Twitch 。 Ruby on Rails着重于程序员的经验并建立了热情的社区,它将为您提供构建和维护现代Web应用程序所需的工具。

React is a JavaScript library used to create front-end user interfaces. Backed by Facebook, it is one of the most popular front-end libraries used on the web today. React offers features like a virtual Document Object Model (DOM), component architecture, and state management, which make the process of front-end development more organized and efficient.

React是一个JavaScript库,用于创建前端用户界面。 在Facebook的支持下,它是当今网络上最受欢迎的前端库之一。 React提供了诸如虚拟文档对象模型(DOM) , 组件架构和状态管理之类的功能,这些功能使前端开发过程更加有条理和高效。

With the frontend of the web moving toward frameworks that are separate from the server-side code, combining the elegance of Rails with the efficiency of React will let you build powerful and modern applications informed by current trends. By using React to render components from within a Rails view instead of the Rails template engine, your application will benefit from the latest advancements in JavaScript and front-end development while still leveraging the expressiveness of Ruby on Rails.

随着Web的前端转向与服务器端代码分离的框架,将Rails的优雅与React的效率相结合将使您能够根据当前趋势构建功能强大且现代的应用程序。 通过使用React从Rails视图而不是Rails模板引擎中呈现组件,您的应用程序将受益于JavaScript和前端开发的最新进展,同时仍可利用Ruby on Rails的表现力。

In this tutorial, you will create a Ruby on Rails application that stores your favorite recipes then displays them with a React frontend. When you are finished, you will be able to create, view, and delete recipes using a React interface styled with Bootstrap:

在本教程中,您将创建一个Ruby on Rails应用程序,该应用程序存储您喜欢的食谱,然后将它们显示在React前端。 完成后,您将能够使用以Bootstrap样式化的React界面创建,查看和删除配方:

If you would like to take a look at the code for this application, check out the companion repository for this tutorial on the DigitalOcean Community GitHub.

如果您想看一下该应用程序的代码,请在DigitalOcean社区GitHub上查看本教程的配套存储库 。

先决条件 (Prerequisites)

To follow this tutorial, you need to have the following:

要遵循本教程,您需要具备以下条件:

  • Node.js and npm installed on your development machine. This tutorial uses Node.js version 10.16.0 and npm version 6.9.0. Node.js is a JavaScript run-time environment that allows you to run your code outside of the browser. It comes with a pre-installed Package Manager called npm, which lets you install and update packages. To install these on macOS or Ubuntu 18.04, follow the steps in How to Install Node.js and Create a Local Development Environment on macOS or the “Installing Using a PPA” section of How To Install Node.js on Ubuntu 18.04.

    开发机器上已安装Node.js和npm 。 本教程使用Node.js版本10.16.0和npm版本6.9.0。 Node.js是一个JavaScript运行时环境,可让您在浏览器之外运行代码。 它附带了一个预安装的软件包管理器npm ,它使您可以安装和更新软件包。 要将它们安装在macOS或Ubuntu 18.04上,请遵循如何在macOS上安装Node.js并创建本地开发环境中的步骤,或如何在Ubuntu 18.04上安装Node.js的“使用PPA安装”部分中的步骤 。

  • The Yarn package manager installed on your development machine, which will allow you to download the React framework. This tutorial was tested on version 1.16.0; to install this dependency, follow the official Yarn installation guide.

    开发机器上安装的Yarn软件包管理器,它将允许您下载React框架。 本教程已在1.16.0版上进行了测试; 要安装此依赖项,请遵循官方的Yarn安装指南 。

  • Installation of the Ruby on Rails framework. To get this, follow our guide on How to Install Ruby on Rails with rbenv on Ubuntu 18.04, or How To Install Ruby on Rails with rbenv on CentOS 7. If you would like to develop this application on macOS, please see this tutorial on How To Install Ruby on Rails with rbenv on macOS. This tutorial was tested on version 2.6.3 of Ruby and version 5.2.3 of Rails, so make sure to specify these versions during the installation process.

    Ruby on Rails框架的安装。 要获取此信息,请遵循我们的指南, 如何在Ubuntu 18.04上使用rbenv安装Ruby on Rails ,或如何在CentOS 7上使用rbenv安装Ruby on Rails 。 如果您想在macOS上开发此应用程序,请参阅有关如何在macOS上使用rbenv安装Ruby on Rails的教程。 本教程已在Ruby 2.6.3版和Rails 5.2.3版上进行了测试,因此请确保在安装过程中指定这些版本。

  • Installation of PostgreSQL, as shown in Steps 1 and 2 of our tutorial How To Use PostgreSQL with Your Ruby on Rails Application on Ubuntu 18.04 or How To Use PostgreSQL with Your Ruby on Rails Application on macOS. To follow this tutorial, use PostgreSQL version 10. If you are looking to develop this application on a different distribution of Linux or on another OS, see the official PostgreSQL downloads page. For more information on how to use PostgreSQL, see our How To Install and Use PostgreSQL tutorials.

    PostgreSQL安装,如本教程的步骤1和2中所示, 如何在Ubuntu 18.04上的Ruby on Rails应用程序中 使用PostgreSQL或在macOS上的Ruby on Rails应用程序中使用PostgreSQL 。 要遵循本教程,请使用PostgreSQL版本10。如果要在其他Linux发行版或其他OS上开发此应用程序,请参见PostgreSQL官方下载页面 。 有关如何使用PostgreSQL更多信息,请参见我们的“ 如何安装和使用PostgreSQL”教程。

第1步-创建新的Rails应用程序 (Step 1 — Creating a New Rails Application)

In this step, you will build your recipe application on the Rails application framework. First, you’ll create a new Rails application, which will be set up to work with React out of the box with little configuration.

在此步骤中,您将在Rails应用程序框架上构建您的配方应用程序。 首先,您将创建一个新的Rails应用程序,该应用程序无需任何配置即可直接与React一起使用。

Rails provides a number of scripts called generators that help in creating everything that’s necessary to build a modern web application. To see a full list of these commands and what they do, run the following command in your Terminal window:

Rails提供了许多称为生成器的脚本,它们可以帮助创建构建现代Web应用程序所需的一切。 要查看这些命令及其作用的完整列表,请在“终端”窗口中运行以下命令:

  • rails -h
    轨-h

This will yield a comprehensive list of options, which will allow you to set the parameters of your application. One of the commands listed is the new command, which creates a new Rails application.

这将产生一个完整的选项列表,使您可以设置应用程序的参数。 列出的命令之一是new命令,它创建一个新的Rails应用程序。

Now, you will create a new Rails application using the new generator. Run the following command in your Terminal window:

现在,您将使用new生成器创建一个新的Rails应用程序。 在“终端”窗口中运行以下命令:

  • rails new rails_react_recipe -d=postgresql -T --webpack=react --skip-coffee

    rails new rails_react_recipe -d = postgresql -T --webpack = react --skip-coffee

The preceding command creates a new Rails application in a directory named rails_react_recipe, installs the required Ruby and JavaScript dependencies, and configures Webpack. Let’s walk through the flags that are associated with this new generator command:

前面的命令在名为rails_react_recipe的目录中创建一个新的Rails应用程序,安装所需的Ruby和JavaScript依赖项,并配置Webpack。 让我们看一下与此new成器命令关联的标志:

  • The -d flag specifies the preferred database engine, which in this case is PostgreSQL.

    -d标志指定首选的数据库引擎,在这种情况下为PostgreSQL。

  • The -T flag instructs Rails to skip the generation of test files, since you won’t be writing tests for the purposes of this tutorial. This command is also suggested if you want to use a Ruby testing tool different from the one Rails provides.

    -T标志指示Rails跳过测试文件的生成,因为在本教程中您不会编写测试。 如果要使用与Rails提供的工具不同的Ruby测试工具,也建议使用此命令。

  • The --webpack instructs Rails to preconfigure for JavaScript with the webpack bundler, in this case specifically for a React application.

    --webpack指示Rails使用webpack捆绑程序为JavaScript进行预配置,在这种情况下,它专门针对React应用程序。

  • The --skip-coffee asks Rails not to set up CoffeeScript, which is not needed for this tutorial.

    --skip-coffee要求Rails不要设置CoffeeScript ,本教程不需要。

Once the command is done running, move into the rails_react_recipe directory, which is the root directory of your app:

命令运行完成后,移至rails_react_recipe目录,该目录是应用程序的根目录:

  • cd rails_react_recipe

    cd rails_react_recipe

Next, list out the contents of the directory:

接下来,列出目录的内容:

  • ls
    ls

This root directory has a number of auto-generated files and folders that make up the structure of a Rails application, including a package.json file containing the dependencies for a React application.

这个根目录有许多自动生成的文件和文件夹,这些文件和文件夹构成了Rails应用程序的结构,包括一个package.json文件,其中包含React应用程序的依赖项。

Now that you have successfully created a new Rails application, you are ready to hook it up to a database in the next step.

既然您已经成功创建了一个新的Rails应用程序,则可以在下一步中将其连接到数据库了。

步骤2 —设置数据库 (Step 2 — Setting Up the Database)

Before you run your new Rails application, you have to first connect it to a database. In this step, you’ll connect the newly created Rails application to a PostgreSQL database, so recipe data can be stored and fetched when needed.

在运行新的Rails应用程序之前,必须首先将其连接到数据库。 在此步骤中,您会将新创建的Rails应用程序连接到PostgreSQL数据库,以便可以在需要时存储和获取配方数据。

The database.yml file found in config/database.yml contains database details like database name for different development environments. Rails specifies a database name for the different development environments by appending an underscore (_) followed by the environment name to your app’s name. You can always change any environment database name to whatever you prefer.

config/database.yml中找到的database.yml文件包含数据库详细信息,例如不同开发环境的数据库名称。 Rails通过在应用程序名称后附加下划线( _ )和环境名称,从而为不同的开发环境指定数据库名称。 您始终可以将任何环境数据库名称更改为您喜欢的名称。

Note: At this point, you can alter config/database.yml to set up which PostgreSQL role you would like Rails to use to create your database. If you followed the Prerequisite How To Use PostgreSQL with Your Ruby on Rails Application and created a role that is secured by a password, you can follow the instructions in Step 4 for macOS or Ubuntu 18.04.

注意:此时,您可以更改config/database.yml来设置您希望Rails使用哪个PostgreSQL角色来创建数据库。 如果遵循先决条件如何在Ruby on Rails应用程序中使用PostgreSQL并创建了一个受密码保护的角色,则可以按照步骤4中针对macOS或Ubuntu 18.04的说明进行操作 。

As earlier stated, Rails offers a lot of commands to make developing web applications easy. This includes commands to work with databases, such as create, drop, and reset. To create a database for your application, run the following command in your Terminal window:

如前所述,Rails提供了许多命令来简化Web应用程序的开发。 这包括用于数据库的命令,例如createdropreset 。 要为您的应用程序创建数据库,请在“终端”窗口中运行以下命令:

  • rails db:create
    rails db:create

This command creates a development and test database, yielding the following output:

该命令创建一个developmenttest数据库,产生以下输出:


Output
Created database 'rails_react_recipe_development'
Created database 'rails_react_recipe_test'

Now that the application is connected to a database, start the application by running the following command in you Terminal window:

现在,应用程序已连接到数据库,通过在“终端”窗口中运行以下命令来启动应用程序:

  • rails s --binding=127.0.0.1
    rails-绑定= 127.0.0.1

The s or server command fires up Puma, which is a web server distributed with Rails by default, and --binding=127.0.0.1 binds the server to your localhost.

sserver命令启动Puma ,这是默认情况下与Rails一起分发的Web服务器,并且--binding=127.0.0.1将服务器绑定到localhost

Once you run this command, your command prompt will disappear, and you will see the following output:

一旦运行此命令,命令提示符将消失,您将看到以下输出:


Output
=> Booting Puma
=> Rails 5.2.3 application starting in development
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.1 (ruby 2.6.3-p62), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
Use Ctrl-C to stop

To see your application, open a browser window and navigate to http://localhost:3000. You will see the Rails default welcome page:

要查看您的应用程序,请打开浏览器窗口,然后浏览至http://localhost:3000 。 您将看到Rails的默认欢迎页面:

This means that you have properly set up your Rails application.

这意味着您已经正确设置了Rails应用程序。

To stop the web server at anytime, press CTRL+C in the Terminal window where the server is running. Go ahead and do this now; you will get a goodbye message from Puma:

要随时停止Web服务器,请在运行服务器的“终端”窗口中按CTRL+C 快去做吧; 您将收到Puma的再见消息:


Output
^C- Gracefully stopping, waiting for requests to finish
=== puma shutdown: 2019-07-31 14:21:24 -0400 ===
- Goodbye!
Exiting

Your prompt will then reappear.

您的提示将再次出现。

You have successfully set up a database for your food recipe application. In the next step, you will install all the extra JavaScript dependencies you need to put together your React frontend.

您已成功为食品食谱应用程序建立了数据库。 在下一步中,您将安装将React前端放在一起所需的所有其他JavaScript依赖项。

第3步-安装前端依赖项 (Step 3 — Installing Frontend Dependencies)

In this step, you will install the JavaScript dependencies needed on the frontend of your food recipe application. They include:

在此步骤中,您将在食品食谱应用程序的前端安装所需JavaScript依赖项。 它们包括:

  • React Router, for handling navigation in a React application.

    React Router ,用于在React应用程序中处理导航。

  • Bootstrap, for styling your front-end components.

    Bootstrap ,用于设置前端组件的样式。

  • jQuery and Popper, for working with Bootstrap.

    jQuery和Popper ,用于使用Bootstrap。

Run the following command in your Terminal window to install these packages with the Yarn package manager:

在“终端”窗口中运行以下命令,以使用Yarn软件包管理器安装这些软件包:

  • yarn add react-router-dom bootstrap jquery popper.js
    纱线添加react-router-dom引导jquery popper.js

This command uses Yarn to install the specified packages and adds them to the package.json file. To verify this, take a look at the package.json file located in the root directory of the project:

此命令使用Yarn安装指定的软件包,并将其添加到package.json文件。 为了验证这一点,请查看位于项目根目录中的package.json文件:

  • nano package.json
    纳米package.json

You’ll see the installed packages listed under the dependencies key:

您会在dependencies项项下看到已安装的软件包:

~/rails_react_recipe/package.json
〜/ rails_react_recipe / package.json
{"name": "rails_react_recipe","private": true,"dependencies": {"@babel/preset-react": "^7.0.0","@rails/webpacker": "^4.0.7","babel-plugin-transform-react-remove-prop-types": "^0.4.24","bootstrap": "^4.3.1","jquery": "^3.4.1","popper.js": "^1.15.0","prop-types": "^15.7.2","react": "^16.8.6","react-dom": "^16.8.6","react-router-dom": "^5.0.1"},"devDependencies": {"webpack-dev-server": "^3.7.2"}
}

You have installed a few front-end dependencies for your application. Next, you’ll set up a homepage for your food recipe application.

您已经为应用程序安装了一些前端依赖项。 接下来,您将为食品食谱应用程序设置一个主页。

第4步-设置主页 (Step 4 — Setting Up the Homepage)

With all the required dependencies installed, in this step you will create a homepage for the application. The homepage will serve as the landing page when users first visit the application.

安装了所有必需的依赖项之后,在此步骤中,您将为应用程序创建一个主页。 用户首次访问该应用程序时,该主页将用作登录页面。

Rails follows the Model-View-Controller architectural pattern for applications. In the MVC pattern, a controller’s purpose is to receive specific requests and pass them along to the appropriate model or view. Right now the application displays the Rails welcome page when the root URL is loaded in the browser. To change this, you will create a controller and view for the homepage and match it to a route.

Rails遵循针对应用程序的Model-View-Controller架构模式。 在MVC模式中,控制器的目的是接收特定的请求,并将其传递给适当的模型或视图。 现在,当根URL加载到浏览器中时,应用程序将显示Rails欢迎页面。 要更改此设置,您将创建一个控制器并查看主页,并将其与路线匹配。

Rails provides a controller generator for creating a controller. The controller generator receives a controller name, along with a matching action. For more on this, check out the official Rails documentation.

Rails提供了用于创建控制器的controller生成器。 controller生成器将接收控制器名称以及匹配操作。 有关更多信息,请查看官方的Rails文档 。

This tutorial will call the controller Homepage. Run the following command in your Terminal window to create a Homepage controller with an index action.

本教程将称为控制器Homepage 。 在“终端”窗口中运行以下命令,以创建带有index操作的主页控制器。

  • rails g controller Homepage index

    rails g controller 主页索引

Note: On Linux, if you run into the error FATAL: Listen error: unable to monitor directories for changes., this is due to a system limit on the number of files your machine can monitor for changes. Run the following command to fix it:

注意:在Linux上,如果遇到错误FATAL: Listen error: unable to monitor directories for changes. ,这是由于系统限制了您的机器可以监视更改的文件数量。 运行以下命令对其进行修复:

  • echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
    回声fs.inotify.max_user_watches = 524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

This will permanently increase the amount of directories that you can monitor with Listen to 524288. You can change this again by running the same command and replacing 524288 with your desired number.

这将永久增加您可以使用“ Listen 524288监视的目录数量。 您可以通过运行相同的命令并用所需的号码替换524288再次更改此设置。

Running this command generates the following files:

运行此命令将生成以下文件:

  • A homepage_controller.rb file for receiving all homepage-related requests. This file contains the index action you specified in the command.

    一个homepage_controller.rb文件,用于接收所有与主页相关的请求。 该文件包含您在命令中指定的index操作。

  • A homepage.js file for adding any JavaScript behavior related to the Homepage controller.

    一个homepage.js文件,用于添加与Homepage控制器相关的任何JavaScript行为。

  • A homepage.scss file for adding styles related to the Homepage controller.

    一个homepage.scss文件,用于添加与Homepage控制器相关的样式。

  • A homepage_helper.rb file for adding helper methods related to the Homepage controller.

    一个homepage_helper.rb文件,用于添加与Homepage控制器相关的帮助器方法。

  • An index.html.erb file which is the view page for rendering anything related to the homepage.

    index.html.erb文件,它是用于呈现与主页相关的任何内容的视图页面。

Apart from these new pages created by running the Rails command, Rails also updates your routes file which is located at config/routes.rb. It adds a get route for your homepage which you will modify as your root route.

除了通过运行Rails命令创建的这些新页面之外,Rails还更新位于config/routes.rb路由文件。 它为您的主页添加了一条get路径,您将其修改为根路径。

A root route in Rails specifies what will show up when users visit the root URL of your application. In this case, you want your users to see your homepage. Open the routes file located at config/routes.rb in your favorite editor:

Rails中的根路由指定当用户访问应用程序的根URL时将显示的内容。 在这种情况下,您希望用户看到您的主页。 在您喜欢的编辑器中打开位于config/routes.rb的路由文件:

  • nano config/routes.rb
    纳米配置/ routes.rb

Inside this file, replace get 'homepage/index' with root 'homepage#index' so that the file looks like the following:

在此文件内,将get 'homepage/index'替换为root 'homepage#index'以使该文件如下所示:

~/rails_react_recipe/config/routes.rb
〜/ rails_react_recipe / config / routes.rb
Rails.application.routes.draw doroot 'homepage#index'# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

This modification instructs Rails to map requests to the root of the application to the index action of the Homepage controller, which in turn renders whatever is in the index.html.erb file located at app/views/homepage/index.html.erb on to the browser.

此修改指示Rails将请求从应用程序的根目录映射到Homepage控制器的index操作,该控制器依次呈现位于app/views/homepage/index.html.erb上的index.html.erb文件中的内容。浏览器。

To verify that this is working, start your application:

要验证它是否正常运行,请启动您的应用程序:

  • rails s --binding=127.0.0.1
    导轨s --binding = 127.0.0.1

Opening the application in the browser, you will see a new landing page for your application:

在浏览器中打开应用程序,您将看到一个新的应用程序登录页面:

Once you have verified that your application is working, press CTRL+C to stop the server.

确认应用程序正常运行后,按CTRL+C停止服务器。

Next, open up the ~/rails_react_recipe/app/views/homepage/index.html.erb file, remove the code inside the file, then save the file as empty. By doing this, you will ensure that the contents of index.html.erb do not interfere with the React rendering of your frontend.

接下来,打开~/rails_react_recipe/app/views/homepage/index.html.erb文件,删除文件中的代码,然后将文件另存为空。 这样,您将确保index.html.erb的内容不会干扰前端的React渲染。

Now that you have set up your homepage for your application, you can move to the next section, where you will configure the frontend of your application to use React.

现在,您已经为应用程序设置了主页,可以转到下一部分,在该部分中,您将配置应用程序的前端以使用React。

第5步—将React配置为Rails前端 (Step 5 — Configuring React as Your Rails Frontend)

In this step, you will configure Rails to use React on the frontend of the application, instead of its template engine. This will allow you to take advantage of React rendering to create a more visually appealing homepage.

在此步骤中,您将配置Rails在应用程序的前端而不是其模板引擎上使用React。 这将使您能够利用React渲染来创建更具视觉吸引力的主页。

Rails, with the help of the Webpacker gem, bundles all your JavaScript code into packs. These can be found in the packs directory at app/javascript/packs. You can link these packs in Rails views using the javascript_pack_tag helper, and you can link stylesheets imported into the packs using the stylesheet_pack_tag helper. To create an entry point to your React environment, you will add one of these packs to your application layout.

Rails在Webpacker gem的帮助下,将所有JavaScript代码打包pack中 。 这些可以在app/javascript/packs的packs目录中找到。 您可以使用javascript_pack_tag帮助器在Rails视图中链接这些包,也可以使用stylesheet_pack_tag帮助器来链接导入到包中的stylesheet_pack_tag 。 要创建您的React环境的入口点,您将其中一个包添加到您的应用程序布局中。

First, rename the ~/rails_react_recipe/app/javascript/packs/hello_react.jsx file to ~/rails_react_recipe/app/javascript/packs/Index.jsx.

首先,重命名~/rails_react_recipe/app/javascript/packs/hello_react.jsx文件到~/rails_react_recipe/app/javascript/packs/Index.jsx

  • mv ~/rails_react_recipe/app/javascript/packs/hello_react.jsx ~/rails_react_recipe/app/javascript/packs/Index.jsx
    mv〜/ rails_react_recipe / app / javascript / packs / hello_react.jsx〜/ rails_react_recipe / app / javascript / packs / Index.jsx

After renaming the file, open application.html.erb, the application layout file:

重命名文件后,打开application.html.erb ,应用程序布局文件:

  • nano ~/rails_react_recipe/app/views/layouts/application.html.erb
    纳米〜/ rails_react_recipe / app / views / layouts / application.html.erb

Add the following highlighted lines of code at the end of the head tag in the application layout file:

在应用程序布局文件的head标记的末尾添加以下突出显示的代码行:

~/rails_react_recipe/app/views/layouts/application.html.erb
〜/ rails_react_recipe / app / views / layouts / application.html.erb
<!DOCTYPE html>
<html><head><title>RailsReactRecipe</title><%= csrf_meta_tags %><%= csp_meta_tag %><%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %><%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><%= javascript_pack_tag 'Index' %></head><body><%= yield %></body>
</html>

Adding the JavaScript pack to your application’s header makes all your JavaScript code available and executes the code in your Index.jsx file on the page whenever you run the app. Along with the JavaScript pack, you also added a meta viewport tag to control the dimensions and scaling of pages on your application.

将JavaScript包添加到应用程序的标头中后,您可以运行所有JavaScript代码,并在页面上的Index.jsx文件中执行该代码。 除JavaScript包外,还添加了meta viewport标记以控制应用程序中页面的尺寸和缩放。

Save and exit the file.

保存并退出文件。

Now that your entry file is loaded onto the page, create a React component for your homepage. Start by creating a components directory in the app/javascript directory:

现在您的输入文件已加载到页面上,为您的主页创建一个React组件。 首先在app/javascript目录中创建一个components目录:

  • mkdir ~/rails_react_recipe/app/javascript/components
    mkdir〜/ rails_react_recipe / app / javascript / components

The components directory will house the component for the homepage, along with other React components in the application. The homepage will contain some text and a call to action button to view all recipes.

components目录将包含主页的组件以及应用程序中的其他React组件。 主页将包含一些文本和一个号召性用语按钮,以查看所有食谱。

In your editor, create a Home.jsx file in the components directory:

在编辑器中,在components目录中创建一个Home.jsx文件:

  • nano ~/rails_react_recipe/app/javascript/components/Home.jsx
    纳米〜/ rails_react_recipe / app / javascript / components / Home.jsx

Add the following code to the file:

将以下代码添加到文件中:

~/rails_react_recipe/app/javascript/components/Home.jsx
〜/ rails_react_recipe / app / javascript / components / Home.jsx
import React from "react";
import { Link } from "react-router-dom";export default () => (<div className="vw-100 vh-100 primary-color d-flex align-items-center justify-content-center"><div className="jumbotron jumbotron-fluid bg-transparent"><div className="container secondary-color"><h1 className="display-4">Food Recipes</h1><p className="lead">A curated list of recipes for the best homemade meal and delicacies.</p><hr className="my-4" /><Linkto="/recipes"className="btn btn-lg custom-button"role="button">View Recipes</Link></div></div></div>
);

In this code, you imported React and also the Link component from React Router. The Link component creates a hyperlink to navigate from one page to another. You then created and exported a functional component containing some Markup language for your homepage, styled with Bootstrap classes.

在此代码中,您从React Router中导入了React以及Link组件。 Link组件创建一个超链接以从一个页面导航到另一页面。 然后,您创建并导出了一个功能组件,该组件包含用于首页的某些标记语言的功能部件,并使用Bootstrap类设置了样式。

With your Home component in place, you will now set up routing using React Router. Create a routes directory in the app/javascript directory:

放置好Home组件后,您现在将使用React Router设置路由。 在app/javascript目录中创建一个routes目录:

  • mkdir ~/rails_react_recipe/app/javascript/routes
    mkdir〜/ rails_react_recipe / app / javascript / routes

The routes directory will contain a few routes with their corresponding components. Whenever any specified route is loaded, it will render its corresponding component to the browser.

routes目录将包含一些路由及其相应的组件。 每当加载任何指定的路由时,它将把其相应的组件呈现给浏览器。

In the routes directory, create an Index.jsx file:

routes目录中,创建一个Index.jsx文件:

  • nano ~/rails_react_recipe/app/javascript/routes/Index.jsx
    纳米〜/ rails_react_recipe / app / javascript / routes / Index.jsx

Add the following code to it:

向其添加以下代码:

~/rails_react_recipe/app/javascript/routes/Index.jsx
〜/ rails_react_recipe / app / javascript / routes / Index.jsx
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "../components/Home";export default (<Router><Switch><Route path="/" exact component={Home} /></Switch></Router>
);

In this Index.jsx route file, you imported a couple of modules: the React module that allows us to use React, and the BrowserRouter, Route, and Switch modules from React Router, which together help us navigate from one route to another. Lastly, you imported your Home component, which will be rendered whenever a request matches the root (/) route. Whenever you want to add more pages to your application, all you need to do is declare a route in this file and match it to the component you want to render for that page.

在这个Index.jsx路由文件中,您导入了两个模块:允许我们使用React的React模块,以及来自React Router的BrowserRouterRouteSwitch模块,它们一起帮助我们从一条路线导航到另一条路线。 最后,您导入了Home组件,该组件将在请求与根( / )路由匹配时呈现。 每当您想向应用程序中添加更多页面时,您所需要做的就是在此文件中声明一条路由,并将其与您要为该页面呈现的组件进行匹配。

Save and exit the file.

保存并退出文件。

You have now successfully set up routing using React Router. For React to be aware of the available routes and use them, the routes have to be available at the entry point to the application. To achieve this, you will render your routes in a component that React will render in your entry file.

现在,您已经使用React Router成功设置了路由。 为了使React知道可用的路由并使用它们,这些路由必须在应用程序的入口点可用。 为了实现这一点,您将在React中将在您的入口文件中呈现的组件中呈现路线。

Create an App.jsx file in the app/javascript/components directory:

app/javascript/components目录中创建一个App.jsx文件:

  • nano ~/rails_react_recipe/app/javascript/components/App.jsx
    纳米〜/ rails_react_recipe / app / javascript / components / App.jsx

Add the following code into the App.jsx file:

将以下代码添加到App.jsx文件中:

~/rails_react_recipe/app/javascript/components/App.jsx
〜/ rails_react_recipe / app / javascript / components / App.jsx
import React from "react";
import Routes from "../routes/Index";export default props => <>{Routes}</>;

In the App.jsx file, you imported React and the route files you just created. You then exported a component that renders the routes within fragments. This component will be rendered at the entry point of the aplication, thereby making the routes available whenever the application is loaded.

App.jsx文件中,您导入了React和刚创建的路由文件。 然后,您导出了一个在片段中呈现路线的组件。 该组件将在应用程序的入口处呈现,从而使路由在应用程序加载时可用。

Now that you have your App.jsx set up, it’s time to render it in your entry file. Open the entry Index.jsx file:

现在您已经设置了App.jsx ,是时候在输入文件中呈现它了。 打开条目Index.jsx文件:

  • nano ~/rails_react_recipe/app/javascript/packs/Index.jsx
    纳米〜/ rails_react_recipe / app / javascript / packs / Index.jsx

Replace the code there with the following code:

用以下代码替换那里的代码:

~/rails_react_recipe/app/javascript/packs/Index.jsx
〜/ rails_react_recipe / app / javascript / packs / Index.jsx
import React from "react";
import { render } from "react-dom";
import 'bootstrap/dist/css/bootstrap.min.css';
import $ from 'jquery';
import Popper from 'popper.js';
import 'bootstrap/dist/js/bootstrap.bundle.min';
import App from "../components/App";document.addEventListener("DOMContentLoaded", () => {render(<App />,document.body.appendChild(document.createElement("div")));
});

In this code snippet, you imported React, the render method from ReactDOM, Bootstrap, jQuery, Popper.js, and your App component. Using ReactDOM’s render method, you rendered your App component in a div element, which was appended to the body of the page. Whenever the application is loaded, React will render the content of the App component inside the div element on the page.

在此代码段中,您导入了React,ReactDOM,Bootstrap,jQuery,Popper.js和您的App组件的render方法。 使用ReactDOM的render方法,您将您的App组件呈现在div元素中,该元素已附加到页面主体中。 每当加载应用程序时,React都会在页面的div元素内呈现App组件的内容。

Save and exit the file.

保存并退出文件。

Finally, add some CSS styles to your homepage.

最后,在您的首页中添加一些CSS样式。

Open up your application.css in your ~/rails_react_recipe/app/assets/stylesheets directory:

~/rails_react_recipe/app/assets/stylesheets目录中打开application.css

  • nano ~/rails_react_recipe/app/assets/stylesheets/application.css
    纳米〜/ rails_react_recipe / app / assets / stylesheets / application.css

Next, replace the contents of the application.css file with the follow code:

接下来,用以下代码替换application.css文件的内容:

~/rails_react_recipe/app/assets/stylesheets/application.css
〜/ rails_react_recipe / app / assets / stylesheets / application.css
.bg_primary-color {background-color: #FFFFFF;
}
.primary-color {background-color: #FFFFFF;
}
.bg_secondary-color {background-color: #293241;
}
.secondary-color {color: #293241;
}
.custom-button.btn {background-color: #293241;color: #FFF;border: none;
}
.custom-button.btn:hover {color: #FFF !important;border: none;
}
.hero {width: 100vw;height: 50vh;
}
.hero img {object-fit: cover;object-position: top;height: 100%;width: 100%;
}
.overlay {height: 100%;width: 100%;opacity: 0.4;
}

This creates the framework for a hero image, or a large web banner on the front page of your website, that you will add later. Additionally, this styles the button that the user will use to enter the application.

这将为英雄图像或网站首页上的大型网络横幅创建框架,稍后将添加该框架。 此外,这将设置用户用来输入应用程序的按钮的样式。

With your CSS styles in place, save and exit the file. Next, restart the web server for your application, then reload the application in your browser. You will see a brand new homepage:

放置好CSS样式后,保存并退出文件。 接下来,为您的应用程序重新启动Web服务器,然后在浏览器中重新加载该应用程序。 您会看到一个全新的主页:

In this step, you configured your application so that it uses React as its frontend. In the next section, you will create models and controllers that will allow you to create, read, update, and delete recipes.

在此步骤中,您配置了应用程序,以使其使用React作为其前端。 在下一节中,您将创建模型和控制器,这些模型和控制器将允许您创建,读取,更新和删除配方。

第6步-创建配方控制器和模型 (Step 6 — Creating the Recipe Controller and Model)

Now that you have set up a React frontend for your application, in this step you’ll create a Recipe model and controller. The recipe model will represent the database table that will hold information about the user’s recipes while the controller will receive and handle requests to create, read, update, or delete recipes. When a user requests a recipe, the recipe controller receives this request and passes it to the recipe model, which retrieves the requested data from the database. The model then returns the recipe data as a response to the controller. Finally, this information is displayed in the browser.

现在您已经为应用程序设置了一个React前端,在这一步中,您将创建一个Recipe模型和控制器。 配方模型将代表数据库表,该表将保存有关用户配方的信息,而控制器将接收并处理创建,读取,更新或删除配方的请求。 当用户请求配方时,配方控制器将接收此请求,并将其传递给配方模型,该模型将从数据库中检索所请求的数据。 然后,模型将配方数据作为对控制器的响应返回。 最后,此信息显示在浏览器中。

Start by creating a Recipe model by using the generate model subcommand provided by Rails and by specifying the name of the model along with its columns and data types. Run the following command in your Terminal window to create a Recipe model:

首先使用Rails提供的generate model子命令创建一个Recipe模型,并指定模型名称及其列和数据类型。 在“终端”窗口中运行以下命令以创建Recipe模型:

  • rails generate model Recipe name:string ingredients:text instruction:text image:string
    rails生成模型配方名称:字符串成分:文本指令:文本图像:字符串

The preceding command instructs Rails to create a Recipe model together with a name column of type string, an ingredients and instruction column of type text, and an image column of type string. This tutorial has named the model Recipe, because by convention models in Rails use a singular name while their corresponding database tables use a plural name.

前面的命令指示Rails创建一个Recipe模型,以及一个string类型的name列, text类型的ingredientsinstruction列以及string类型的image列。 本教程将模型命名为Recipe ,因为按照惯例,Rails中的模型使用单数名称,而其对应的数据库表使用复数名称。

Running the generate model command creates two files:

运行generate model命令将创建两个文件:

  • A recipe.rb file that holds all the model related logic.

    一个保存所有与模型相关的逻辑的recipe.rb文件。

  • A 20190407161357_create_recipes.rb file (the number at the beginning of the file may differ depending on the date when you run the command). This is a migration file that contains the instruction for creating the database structure.

    20190407161357 _create_recipes.rb文件(文件开头的数字可能会有所不同,具体取决于运行命令的日期)。 这是一个迁移文件,其中包含用于创建数据库结构的指令。

Next, edit the recipe model file to ensure that only valid data is saved to the database. You can achieve this by adding some database validation to your model. Open your recipe model located at app/models/recipe.rb:

接下来,编辑配方模型文件以确保仅将有效数据保存到数据库。 您可以通过在模型中添加一些数据库验证来实现。 打开位于app/models/recipe.rb配方模型:

  • nano ~/rails_react_recipe/app/models/recipe.rb
    纳米〜/ rails_react_recipe / app / models / recipe.rb

Add the following highlighted lines of code to the file:

将以下突出显示的代码行添加到文件中:

class Recipe < ApplicationRecordvalidates :name, presence: truevalidates :ingredients, presence: truevalidates :instruction, presence: true
end

In this code, you added model validation which checks for the presence of a name, ingredients, and instruction field. Without the presence of these three fields, a recipe is invalid and won’t be saved to the database.

在此代码中,您添加了模型验证,该模型验证检查nameingredientsinstruction字段的存在。 如果没有这三个字段,则配方无效,并且不会保存到数据库中。

Save and quit the file.

保存并退出文件。

For Rails to create the recipes table in your database, you have to run a migration, which in Rails is a way to make changes to your database programmatically. To make sure that the migration works with the database you set up, it is necessary to make changes to the 20190407161357_create_recipes.rb file.

为了使Rails在数据库中创建recipes表,您必须运行一次迁移 ,这是在Rails中以编程方式更改数据库的一种方法。 为确保迁移可与您设置的数据库一起使用,必须对20190407161357 _create_recipes.rb文件进行更改。

Open this file in your editor:

在编辑器中打开此文件:

  • nano ~/rails_react_recipe/db/migrate/20190407161357_create_recipes.rb

    纳米〜/ rails_react_recipe / db / migrate / 20190407161357 _create_recipes.rb

Add the following highlighted lines, so that the file looks like this:

添加以下突出显示的行,以便文件如下所示:

db/migrate/20190407161357_create_recipes.rb
db / migrate / 20190407161357_create_recipes.rb
class CreateRecipes < ActiveRecord::Migration[5.2]def changecreate_table :recipes do |t|t.string :name, null: falset.text :ingredients, null: falset.text :instruction, null: falset.string :image, default: 'https://raw.githubusercontent.com/do-community/react_rails_recipe/master/app/assets/images/Sammy_Meal.jpg't.timestampsendend
end

This migration file contains a Ruby class with a change method, and a command to create a table called recipes along with the columns and their data types. You also updated 20190407161357_create_recipes.rb with a NOT NULL constraint on the name, ingredients, and instruction columns by adding null: false, ensuring that these columns have a value before changing the database. Finally, you added a default image URL for your image column; this could be another URL if you wanted to use a different image.

该迁移文件包含一个带change方法的Ruby类,以及一个用于创建名为recipes的表以及各列及其数据类型的命令。 您还通过添加null: falsenameingredientsinstruction列上使用NOT NULL约束更新了20190407161357 _create_recipes.rb ,确保在更改数据库之前这些列具有值。 最后,为图像列添加了默认图像URL。 如果要使用其他图像,则可以是另一个URL。

With these changes, save and exit the file. You’re now ready to run your migration and actually create your table. In your Terminal window, run the following command:

进行这些更改后,保存并退出文件。 现在,您可以运行迁移并实际创建表了。 在“终端”窗口中,运行以下命令:

  • rails db:migrate
    rails db:migrate

Here you used the database migrate command, which executes the instructions in your migration file. Once the command runs successfully, you will receive an output similar to the following:

在这里,您使用了数据库迁移命令,该命令执行迁移文件中的指令。 命令成功运行后,您将收到类似于以下内容的输出:


Output
== 20190407161357 CreateRecipes: migrating ====================================
-- create_table(:recipes)-> 0.0140s
== 20190407161357 CreateRecipes: migrated (0.0141s) ===========================

With your recipe model in place, create your recipes controller and add the logic for creating, reading, and deleting recipes. In your Terminal window, run the following command:

放置好食谱模型后,创建食谱控制器,并添加用于创建,读取和删除食谱的逻辑。 在“终端”窗口中,运行以下命令:

  • rails generate controller api/v1/Recipes index create show destroy -j=false -y=false --skip-template-engine --no-helper
    rails生成控制器api / v1 / Recipes索引create show destroy -j = false -y = false --skip-template-engine --no-helper

In this command, you created a Recipes controller in an api/v1 directory with an index, create, show, and destroy action. The index action will handle fetching all your recipes, the create action will be responsible for creating new recipes, the show action will fetch a single recipe, and the destroy action will hold the logic for deleting a recipe.

在此命令中,您在api/v1目录中使用indexcreateshowdestroy操作创建了Recipes控制器。 index操作将处理获取所有配方的操作, create操作将负责创建新配方的操作, show操作将获取单个配方的操作, destroy操作将保留删除配方的逻辑。

You also passed some flags to make the controller more lightweight, including:

您还传递了一些标志来使控制器更轻便,包括:

  • -j=false which instructs Rails to skip generating associated JavaScript files.

    -j=false ,指示Rails跳过生成关联JavaScript文件。

  • -y=false which instructs Rails to skip generating associated stylesheet files.

    -y=false ,指示Rails跳过生成关联的样式表文件。

  • --skip-template-engine, which instructs Rails to skip generating Rails view files, since React is handling your front-end needs.

    --skip-template-engine ,它指示Rails跳过生成Rails视图文件,因为React正在处理您的前端需求。

  • --no-helper, which instructs Rails to skip generating a helper file for your controller.

    --no-helper ,它指示Rails跳过为您的控制器生成帮助文件的过程。

Running the command also updated your routes file with a route for each action in the Recipes controller. To use these routes, make changes to your config/routes.rb file.

运行该命令还会使用“ Recipes控制器中每个动作的路线更新您的路线文件。 要使用这些路由,请对config/routes.rb文件进行更改。

Open up the routes file in your text editor:

在文本编辑器中打开路线文件:

  • nano ~/rails_react_recipe/config/routes.rb
    纳米〜/ rails_react_recipe / config / routes.rb

Once it is open, update it to look like the following code, altering or adding the highlighted lines:

打开后,将其更新为以下代码,以更改或添加突出显示的行:

~/rails_react_recipe/config/routes.rb
〜/ rails_react_recipe / config / routes.rb
Rails.application.routes.draw donamespace :api donamespace :v1 doget 'recipes/index'post 'recipes/create'get '/show/:id', to: 'recipes#show'delete '/destroy/:id', to: 'recipes#destroy'endendroot 'homepage#index'get '/*path' => 'homepage#index'# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

In this route file, you modified the HTTP verb of the create and destroy routes so that it can post and delete data. You also modified the routes for the show and destroy action by adding an :id parameter into the route. :id will hold the identification number of the recipe you want to read or delete.

在此路由文件中,您修改了createdestroy路由的HTTP动词,以便它可以postdelete数据。 您还通过在路由中添加:id参数来修改了showdestroy操作的路由。 :id将保存您要读取或删除的配方的标识号。

You also added a catch all route with get '/*path' that will direct any other request that doesn’t match the existing routes to the index action of the homepage controller. This way, the routing on the frontend will handle requests that are not related to creating, reading, or deleting recipes.

您还使用get '/*path'添加了一条全部捕获路由,它将所有与现有路由不匹配的其他请求定向到homepage控制器的index操作。 这样,前端上的路由将处理与创建,读取或删除配方无关的请求。

Save and exit the file.

保存并退出文件。

To see a list of routes available in your application, run the following command in your Terminal window:

要查看您的应用程序中可用的路由列表,请在“终端”窗口中运行以下命令:

  • rails routes
    铁轨路线

Running this command displays a list of URI patterns, verbs, and matching controllers or actions for your project.

运行此命令将显示URI模式,动词以及项目的匹配控制器或动作的列表。

Next, add the logic for getting all recipes at once. Rails uses the ActiveRecord library to handle database-related tasks like this. ActiveRecord connects classes to relational database tables and provides a rich API for working with them.

接下来,添加一次获取所有配方的逻辑。 Rails使用ActiveRecord库来处理类似数据库的任务。 ActiveRecord将类连接到关系数据库表,并提供了用于处理它们的丰富API。

To get all recipes, you’ll use ActiveRecord to query the recipes table and fetch all the recipes that exist in the database.

要获取所有配方,您将使用ActiveRecord查询配方表并获取数据库中存在的所有配方。

Open the recipes_controller.rb file with the following command:

使用以下命令打开recipes_controller.rb文件:

  • nano ~/rails_react_recipe/app/controllers/api/v1/recipes_controller.rb
    纳米〜/ rails_react_recipe / app / controllers / api / v1 / recipes_controller.rb

Add the following highlighted lines of code to the recipes controller:

将以下突出显示的代码行添加到配方控制器:

~/rails_react_recipe/app/controllers/api/v1/recipes_controller.rb
〜/ rails_react_recipe / app / controllers / api / v1 / recipes_controller.rb
class Api::V1::RecipesController < ApplicationControllerdef indexrecipe = Recipe.all.order(created_at: :desc)render json: recipeenddef createenddef showenddef destroyend
end

In your index action, using the all method provided by ActiveRecord, you get all the recipes in your database. Using the order method, you order them in descending order by their created date. This way, you have the newest recipes first. Lastly, you send your list of recipes as a JSON response with render.

index操作中,使用ActiveRecord提供的all方法,可以在数据库中获取所有配方。 使用order方法,您可以按创建日期的降序对它们进行排序。 这样,您就可以拥有最新的食谱。 最后,您使用render发送食谱列表作为JSON响应。

Next, add the logic for creating new recipes. As with fetching all recipes, you’ll rely on ActiveRecord to validate and save the provided recipe details. Update your recipe controller with the following highlighted lines of code:

接下来,添加用于创建新配方的逻辑。 与获取所有食谱一样,您将依靠ActiveRecord来验证并保存所提供的食谱详细信息。 使用以下突出显示的代码行更新您的配方控制器:

~/rails_react_recipe/app/controllers/api/v1/recipes_controller.rb
〜/ rails_react_recipe / app / controllers / api / v1 / recipes_controller.rb
class Api::V1::RecipesController < ApplicationControllerdef indexrecipe = Recipe.all.order(created_at: :desc)render json: recipeenddef createrecipe = Recipe.create!(recipe_params)if reciperender json: recipeelserender json: recipe.errorsendenddef showenddef destroyendprivatedef recipe_paramsparams.permit(:name, :image, :ingredients, :instruction)end
end

In the create action, you use ActiveRecord’s create method to create a new recipe. The create method has the ability to assign all controller parameters provided into the model at once. This makes it easy to create records, but also opens the possibility of malicious use. This can be prevented by using a feature provided by Rails known as strong parameters. This way, parameters can’t be assigned unless they’ve been whitelisted. In your code, you passed a recipe_params parameter to the create method. The recipe_params is a private method where you whitelisted your controller parameters to prevent wrong or malicious content from getting into your database. In this case, you are permitting a name, image, ingredients, and instruction parameter for valid use of the create method.

create动作中,使用ActiveRecord的create方法来创建新配方。 create方法可以一次分配提供给模型的所有控制器参数。 这使创建记录变得容易,但也带来了恶意使用的可能性。 这可以通过使用Rails提供的称为“ 强参数”的功能来防止。 这样,除非将其列入白名单,否则无法分配参数。 在您的代码中,您将一个recipe_params参数传递给了create方法。 recipe_params是一个private方法,您在其中将控制器参数列入了白名单,以防止错误或恶意的内容进入数据库。 在这种情况下,您允许nameimageingredientsinstruction参数有效使用create方法。

Your recipe controller can now read and create recipes. All that’s left is the logic for reading and deleting a single recipe. Update your recipes controller with the following code:

您的配方控制器现在可以读取和创建配方。 剩下的就是读取和删除单个配方的逻辑。 使用以下代码更新您的食谱控制器:

~/rails_react_recipe/app/controllers/api/v1/recipes_controller.rb
〜/ rails_react_recipe / app / controllers / api / v1 / recipes_controller.rb
class Api::V1::RecipesController < ApplicationControllerdef indexrecipe = Recipe.all.order(created_at: :desc)render json: recipeenddef createrecipe = Recipe.create!(recipe_params)if reciperender json: recipeelserender json: recipe.errorsendenddef showif reciperender json: recipeelserender json: recipe.errorsendenddef destroyrecipe&.destroyrender json: { message: 'Recipe deleted!' }endprivatedef recipe_paramsparams.permit(:name, :image, :ingredients, :instruction)enddef recipe@recipe ||= Recipe.find(params[:id])end
end

In the new lines of code, you created a private recipe method. The recipe method uses ActiveRecord’s find method to find a recipe whose idmatches the id provided in the params and assigns it to an instance variable @recipe. In the show action, you checked if a recipe is returned by the recipe method and sent it as a JSON response, or sent an error if it was not.

在新的代码行中,您创建了一个私有recipe方法。 该recipe方法使用的ActiveRecord的find方法找到一个方案,其id的匹配id中所提供的params ,并为其分配实例变量@recipe 。 在show操作中,您检查了食谱方法是否返回了recipe ,并将其作为JSON响应发送,如果不是,则发送错误。

In the destroy action, you did something similar using Ruby’s safe navigation operator &., which avoids nil errors when calling a method. This let’s you delete a recipe only if it exists, then send a message as a response.

destroy操作中,您使用Ruby的安全导航运算符&.进行了类似的操作&. ,这样可以避免在调用方法时出现nil错误。 这样,您就可以仅在配方存在的情况下将其删除,然后发送消息作为响应。

Now that you have finished making these changes to recipes_controller.rb, save the file and exit your text editor.

现在,您已经完成了对recipes_controller.rb这些更改,保存文件并退出文本编辑器。

In this step, you created a model and controller for your recipes. You’ve written all the logic needed to work with recipes on the backend. In the next section, you’ll create components to view your recipes.

在此步骤中,您为食谱创建了一个模型和控制器。 您已经在后端上编写了使用配方所需的所有逻辑。 在下一部分中,您将创建组件来查看您的食谱。

第7步-查看食谱 (Step 7 — Viewing Recipes)

In this section, you will create components for viewing recipes. First you’ll create a page where you can view all existing recipes, and then another to view individual recipes.

在本部分中,您将创建用于查看配方的组件。 首先,您将创建一个页面,您可以在其中查看所有现有食谱,然后再一个页面来查看各个食谱。

You’ll start off by creating a page to view all recipes. However, before you can do this, you need recipes to work with, since your database is currently empty. Rails affords us the opportunity to create seed data for your application.

您将首先创建一个页面来查看所有食谱。 但是,在执行此操作之前,您需要使用配方,因为您的数据库当前为空。 Rails为我们提供了为您的应用程序创建种子数据的机会。

Open up the seed file seeds.rb to edit:

打开种子文件seeds.rb进行编辑:

  • nano ~/rails_react_recipe/db/seeds.rb
    纳米〜/ rails_react_recipe / db / seeds.rb

Replace the contents of this seed file with the following code:

用以下代码替换该种子文件的内容:

~/rails_react_recipe/db/seeds.rb
〜/ rails_react_recipe / db / seeds.rb
9.times do |i|Recipe.create(name: "Recipe #{i + 1}",ingredients: '227g tub clotted cream, 25g butter, 1 tsp cornflour,100g parmesan, grated nutmeg, 250g fresh fettuccine or tagliatelle, snipped chives or chopped parsley to serve (optional)',instruction: 'In a medium saucepan, stir the clotted cream, butter, and cornflour over a low-ish heat and bring to a low simmer. Turn off the heat and keep warm.')
end

In this code, you are using a loop to instruct Rails to create nine recipes with a name, ingredients, and instruction. Save and exit the file.

在此代码中,您使用循环来指示Rails创建具有nameingredientsinstruction九个配方。 保存并退出文件。

To seed the database with this data, run the following command in your Terminal window:

要使用此数据播种数据库,请在“终端”窗口中运行以下命令:

  • rails db:seed
    rails db:seed

Running this command adds nine recipes to your database. Now you can fetch them and render them on the frontend.

运行此命令将九个配方添加到您的数据库。 现在,您可以获取它们并将其呈现在前端。

The component to view all recipes will make a HTTP request to the index action in the RecipesController to get a list of all recipes. These recipes will then be displayed in cards on the page.

查看所有配方的组件将向RecipesControllerindex操作发出HTTP请求,以获取所有配方的列表。 然后,这些食谱将显示在页面上的卡片中。

Create a Recipes.jsx file in the app/javascript/components directory:

app/javascript/components目录中创建一个Recipes.jsx文件:

  • nano ~/rails_react_recipe/app/javascript/components/Recipes.jsx
    纳米〜/ rails_react_recipe / app / javascript / components / Recipes.jsx

Once the file is open, import the React and Link modules into it by adding the following lines:

打开文件后,通过添加以下行将React和Link模块导入其中:

~/rails_react_recipe/app/javascript/components/Recipes.jsx
〜/ rails_react_recipe / app / javascript / components / Recipes.jsx
import React from "react";
import { Link } from "react-router-dom";

Next, create a Recipes class that extends the React.Component class. Add the following highlighted code to create a React component that extends React.Component:

接下来,创建一个Recipes扩展类React.Component类。 添加以下突出显示的代码来创建扩展React.Component的React组件:

~/rails_react_recipe/app/javascript/components/Recipes.jsx
〜/ rails_react_recipe / app / javascript / components / Recipes.jsx
import React from "react";
import { Link } from "react-router-dom";class Recipes extends React.Component {constructor(props) {super(props);this.state = {recipes: []};}}
export default Recipes;

Inside the constructor, we are initializing a state object that holds the state of your recipes, which on initialization is an empty array ([]).

在构造函数内部,我们正在初始化一个状态对象,该对象保存食谱的状态,该对象在初始化时是一个空数组( [] )。

Next, add a componentDidMount method in the Recipe class. The componentDidMount method is a React lifecycle method that is called immediately after a component is mounted. In this lifecycle method, you will make a call to fetch all your recipes. To do this, add the following lines:

接下来,在Recipe类中添加componentDidMount方法。 componentDidMount方法是一个React生命周期方法,在安装组件后立即调用。 在此生命周期方法中,您将调用以获取所有食谱。 为此,请添加以下行:

~/rails_react_recipe/app/javascript/components/Recipes.jsx
〜/ rails_react_recipe / app / javascript / components / Recipes.jsx
import React from "react";
import { Link } from "react-router-dom";class Recipes extends React.Component {constructor(props) {super(props);this.state = {recipes: []};}componentDidMount() {const url = "/api/v1/recipes/index";fetch(url).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(response => this.setState({ recipes: response })).catch(() => this.props.history.push("/"));}}
export default Recipes;

In your componentDidMount method, you made an HTTP call to fetch all recipes using the Fetch API. If the response is successful, the application saves the array of recipes to the recipe state. If there’s an error, it will redirect the user to the homepage.

componentDidMount方法中,您进行了HTTP调用,以使用Fetch API提取所有配方。 如果响应成功,则应用程序将配方数组保存为配方状态。 If there's an error, it will redirect the user to the homepage.

Finally, add a render method in the Recipe class. The render method holds the React elements that will be evaluated and displayed on the browser page when a component is rendered. In this case, the render method will render cards of recipes from the component state. Add the following highlighted lines to Recipes.jsx:

Finally, add a render method in the Recipe class. The render method holds the React elements that will be evaluated and displayed on the browser page when a component is rendered. In this case, the render method will render cards of recipes from the component state. Add the following highlighted lines to Recipes.jsx :

~/rails_react_recipe/app/javascript/components/Recipes.jsx
~/rails_react_recipe/app/javascript/components/Recipes.jsx
import React from "react";
import { Link } from "react-router-dom";class Recipes extends React.Component {constructor(props) {super(props);this.state = {recipes: []};}componentDidMount() {const url = "/api/v1/recipes/index";fetch(url).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(response => this.setState({ recipes: response })).catch(() => this.props.history.push("/"));}render() {const { recipes } = this.state;const allRecipes = recipes.map((recipe, index) => (<div key={index} className="col-md-6 col-lg-4"><div className="card mb-4"><imgsrc={recipe.image}className="card-img-top"alt={`${recipe.name} image`}/><div className="card-body"><h5 className="card-title">{recipe.name}</h5><Link to={`/recipe/${recipe.id}`} className="btn custom-button">View Recipe</Link></div></div></div>));const noRecipe = (<div className="vw-100 vh-50 d-flex align-items-center justify-content-center"><h4>No recipes yet. Why not <Link to="/new_recipe">create one</Link></h4></div>);return (<><section className="jumbotron jumbotron-fluid text-center"><div className="container py-5"><h1 className="display-4">Recipes for every occasion</h1><p className="lead text-muted">We’ve pulled together our most popular recipes, our latestadditions, and our editor’s picks, so there’s sure to be somethingtempting for you to try.</p></div></section><div className="py-5"><main className="container"><div className="text-right mb-3"><Link to="/recipe" className="btn custom-button">Create New Recipe</Link></div><div className="row">{recipes.length > 0 ? allRecipes : noRecipe}</div><Link to="/" className="btn btn-link">Home</Link></main></div></>);}
}
export default Recipes;

Save and exit Recipes.jsx.

Save and exit Recipes.jsx .

Now that you have created a component to display all the recipes, the next step is to create a route for it. Open the front-end route file located at app/javascript/routes/Index.jsx:

Now that you have created a component to display all the recipes, the next step is to create a route for it. Open the front-end route file located at app/javascript/routes/Index.jsx :

  • nano app/javascript/routes/Index.jsx
    nano app/javascript/routes/Index.jsx

Add the following highlighted lines to the file:

将以下突出显示的行添加到文件中:

~/rails_react_recipe/app/javascript/routes/Index.jsx
~/rails_react_recipe/app/javascript/routes/Index.jsx
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "../components/Home";
import Recipes from "../components/Recipes";export default (<Router><Switch><Route path="/" exact component={Home} /><Route path="/recipes" exact component={Recipes} /></Switch></Router>
);

Save and exit the file.

保存并退出文件。

At this point, it’s a good idea to verify that your code is working correctly. As you did before, use the following command to start your server:

At this point, it's a good idea to verify that your code is working correctly. As you did before, use the following command to start your server:

  • rails s --binding=127.0.0.1
    rails s --binding=127.0.0.1

Go ahead and open the app in your browser. By clicking the View Recipe button on the homepage, you will see a display with your seed recipes:

Go ahead and open the app in your browser. By clicking the View Recipe button on the homepage, you will see a display with your seed recipes:

Use CTRL+C in your Terminal window to stop the server and get your prompt back.

Use CTRL+C in your Terminal window to stop the server and get your prompt back.

Now that you can view all the recipes that exist in your application, it’s time to create a second component to view individual recipes. Create a Recipe.jsx file in the app/javascript/components directory:

Now that you can view all the recipes that exist in your application, it's time to create a second component to view individual recipes. Create a Recipe.jsx file in the app/javascript/components directory:

  • nano app/javascript/components/Recipe.jsx
    nano app/javascript/components/Recipe.jsx

As with the Recipes component, import the React and Link modules by adding the following lines:

As with the Recipes component, import the React and Link modules by adding the following lines:

~/rails_react_recipe/app/javascript/components/Recipe.jsx
~/rails_react_recipe/app/javascript/components/Recipe.jsx
import React from "react";
import { Link } from "react-router-dom";

Next create a Recipe class that extends React.Component class by adding the highlighted lines of code:

Next create a Recipe class that extends React.Component class by adding the highlighted lines of code:

~/rails_react_recipe/app/javascript/components/Recipe.jsx
~/rails_react_recipe/app/javascript/components/Recipe.jsx
import React from "react";
import { Link } from "react-router-dom";class Recipe extends React.Component {constructor(props) {super(props);this.state = { recipe: { ingredients: "" } };this.addHtmlEntities = this.addHtmlEntities.bind(this);}
}export default Recipe;

Like with your Recipes component, in the constructor, you initialized a state object that holds the state of a recipe. You also bound an addHtmlEntities method to this so it can be accessible within the component. The addHtmlEntities method will be used to replace character entities with HTML entities in the component.

Like with your Recipes component, in the constructor, you initialized a state object that holds the state of a recipe. You also bound an addHtmlEntities method to this so it can be accessible within the component. The addHtmlEntities method will be used to replace character entities with HTML entities in the component.

In order to find a particular recipe, your application needs the id of the recipe. This means your Recipe component expects an id param. You can access this via the props passed into the component.

In order to find a particular recipe, your application needs the id of the recipe. This means your Recipe component expects an id param . You can access this via the props passed into the component.

Next, add a componentDidMount method where you will access the id param from the match key of the props object. Once you get the id, you will then make an HTTP request to fetch the recipe. Add the following highlighted lines to your file:

Next, add a componentDidMount method where you will access the id param from the match key of the props object. Once you get the id , you will then make an HTTP request to fetch the recipe. 将以下突出显示的行添加到您的文件中:

~/rails_react_recipe/app/javascript/components/Recipe.jsx
~/rails_react_recipe/app/javascript/components/Recipe.jsx
import React from "react";
import { Link } from "react-router-dom";class Recipe extends React.Component {constructor(props) {super(props);this.state = { recipe: { ingredients: "" } };this.addHtmlEntities = this.addHtmlEntities.bind(this);}componentDidMount() {const {match: {params: { id }}} = this.props;const url = `/api/v1/show/${id}`;fetch(url).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(response => this.setState({ recipe: response })).catch(() => this.props.history.push("/recipes"));}}export default Recipe;

In the componentDidMount method, using object destructuring, you get the id param from the props object, then using the Fetch API, you make a HTTP request to fetch the recipe that owns the id and save it to the component state using the setState method. If the recipe does not exist, the app redirects the user to the recipes page.

In the componentDidMount method, using object destructuring , you get the id param from the props object, then using the Fetch API, you make a HTTP request to fetch the recipe that owns the id and save it to the component state using the setState method. If the recipe does not exist, the app redirects the user to the recipes page.

Now add the addHtmlEntities method, which takes a string and replaces all escaped opening and closing brackets with their HTML entities. This will help us convert whatever escaped character was saved in your recipe instruction:

Now add the addHtmlEntities method, which takes a string and replaces all escaped opening and closing brackets with their HTML entities. This will help us convert whatever escaped character was saved in your recipe instruction:

~/rails_react_recipe/app/javascript/components/Recipe.jsx
~/rails_react_recipe/app/javascript/components/Recipe.jsx
import React from "react";
import { Link } from "react-router-dom";class Recipe extends React.Component {constructor(props) {super(props);this.state = { recipe: { ingredients: "" } };this.addHtmlEntities = this.addHtmlEntities.bind(this);}componentDidMount() {const {match: {params: { id }}} = this.props;const url = `/api/v1/show/${id}`;fetch(url).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(response => this.setState({ recipe: response })).catch(() => this.props.history.push("/recipes"));}addHtmlEntities(str) {return String(str).replace(/&lt;/g, "<").replace(/&gt;/g, ">");}
}export default Recipe;

Finally, add a render method that gets the recipe from the state and renders it on the page. To do this, add the following highlighted lines:

Finally, add a render method that gets the recipe from the state and renders it on the page. To do this, add the following highlighted lines:

~/rails_react_recipe/app/javascript/components/Recipe.jsx
~/rails_react_recipe/app/javascript/components/Recipe.jsx
import React from "react";
import { Link } from "react-router-dom";class Recipe extends React.Component {constructor(props) {super(props);this.state = { recipe: { ingredients: "" } };this.addHtmlEntities = this.addHtmlEntities.bind(this);}componentDidMount() {const {match: {params: { id }}} = this.props;const url = `/api/v1/show/${id}`;fetch(url).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(response => this.setState({ recipe: response })).catch(() => this.props.history.push("/recipes"));}addHtmlEntities(str) {return String(str).replace(/&lt;/g, "<").replace(/&gt;/g, ">");}render() {const { recipe } = this.state;let ingredientList = "No ingredients available";if (recipe.ingredients.length > 0) {ingredientList = recipe.ingredients.split(",").map((ingredient, index) => (<li key={index} className="list-group-item">{ingredient}</li>));}const recipeInstruction = this.addHtmlEntities(recipe.instruction);return (<div className=""><div className="hero position-relative d-flex align-items-center justify-content-center"><imgsrc={recipe.image}alt={`${recipe.name} image`}className="img-fluid position-absolute"/><div className="overlay bg-dark position-absolute" /><h1 className="display-4 position-relative text-white">{recipe.name}</h1></div><div className="container py-5"><div className="row"><div className="col-sm-12 col-lg-3"><ul className="list-group"><h5 className="mb-2">Ingredients</h5>{ingredientList}</ul></div><div className="col-sm-12 col-lg-7"><h5 className="mb-2">Preparation Instructions</h5><divdangerouslySetInnerHTML={{__html: `${recipeInstruction}`}}/></div><div className="col-sm-12 col-lg-2"><button type="button" className="btn btn-danger">Delete Recipe</button></div></div><Link to="/recipes" className="btn btn-link">Back to recipes</Link></div></div>);}}export default Recipe;

In this render method, you split your comma separated ingredients into an array and mapped over it, creating a list of ingredients. If there are no ingredients, the app displays a message that says No ingredients available. It also displays the recipe image as a hero image, adds a delete recipe button next to the recipe instruction, and adds a button that links back to the recipes page.

In this render method, you split your comma separated ingredients into an array and mapped over it, creating a list of ingredients. If there are no ingredients, the app displays a message that says No ingredients available . It also displays the recipe image as a hero image, adds a delete recipe button next to the recipe instruction, and adds a button that links back to the recipes page.

Save and exit the file.

保存并退出文件。

To view the Recipe component on a page, add it to your routes file. Open your route file to edit:

To view the Recipe component on a page, add it to your routes file. Open your route file to edit:

  • nano app/javascript/routes/Index.jsx
    nano app/javascript/routes/Index.jsx

Now, add the following highlighted lines to the file:

Now, add the following highlighted lines to the file:

~/rails_react_recipe/app/javascript/routes/Index.jsx
~/rails_react_recipe/app/javascript/routes/Index.jsx
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "../components/Home";
import Recipes from "../components/Recipes";
import Recipe from "../components/Recipe";export default (<Router><Switch><Route path="/" exact component={Home} /><Route path="/recipes" exact component={Recipes} /><Route path="/recipe/:id" exact component={Recipe} /></Switch></Router>
);

In this route file, you imported your Recipe component and added a route for it. Its route has an :id param that will be replaced by the id of the recipe you want to view.

In this route file, you imported your Recipe component and added a route for it. Its route has an :id param that will be replaced by the id of the recipe you want to view.

Use the rails s command to start your server again, then visit http://localhost:3000 in your browser. Click the View Recipes button to navigate to the recipes page. On the recipes page, view any recipe by clicking its View Recipe button. You will be greeted with a page populated with the data from your database:

Use the rails s command to start your server again, then visit http://localhost:3000 in your browser. Click the View Recipes button to navigate to the recipes page. On the recipes page, view any recipe by clicking its View Recipe button. You will be greeted with a page populated with the data from your database:

In this section, you added nine recipes to your database and created components to view these recipes, both individually and as a collection. In the next section, you will add a component to create recipes.

In this section, you added nine recipes to your database and created components to view these recipes, both individually and as a collection. In the next section, you will add a component to create recipes.

Step 8 — Creating Recipes (Step 8 — Creating Recipes)

The next step to having a usable food recipe application is the ability to create new recipes. In this step, you will create a component for creating recipes. This component will contain a form for collecting the required recipe details from the user and will make a request to the create action in the Recipe controller to save the recipe data.

The next step to having a usable food recipe application is the ability to create new recipes. In this step, you will create a component for creating recipes. This component will contain a form for collecting the required recipe details from the user and will make a request to the create action in the Recipe controller to save the recipe data.

Create a NewRecipe.jsx file in the app/javascript/components directory:

Create a NewRecipe.jsx file in the app/javascript/components directory:

  • nano app/javascript/components/NewRecipe.jsx
    nano app/javascript/components/NewRecipe.jsx

In the new file, import the React and Link modules you have used so far in other components:

In the new file, import the React and Link modules you have used so far in other components:

~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
import React from "react";
import { Link } from "react-router-dom";

Next create a NewRecipe class that extends React.Component class. Add the following highlighted code to create a React component that extends react.Component:

Next create a NewRecipe class that extends React.Component class. Add the following highlighted code to create a React component that extends react.Component :

~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
import React from "react";
import { Link } from "react-router-dom";class NewRecipe extends React.Component {constructor(props) {super(props);this.state = {name: "",ingredients: "",instruction: ""};this.onChange = this.onChange.bind(this);this.onSubmit = this.onSubmit.bind(this);this.stripHtmlEntities = this.stripHtmlEntities.bind(this);}
}export default NewRecipe;

In the NewRecipe component’s constructor, you initialized your state object with empty name, ingredients, and instruction fields. These are the fields you need to create a valid recipe. You also have three methods; onChange, onSubmit, and stripHtmlEntities, which you bound to this. These methods will handle updating the state, form submissions, and converting special characters (like <) into their escaped/encoded values (like &lt;), respectively.

In the NewRecipe component's constructor, you initialized your state object with empty name , ingredients , and instruction fields. These are the fields you need to create a valid recipe. You also have three methods; onChange , onSubmit , and stripHtmlEntities , which you bound to this . These methods will handle updating the state, form submissions, and converting special characters (like < ) into their escaped/encoded values (like &lt; ), respectively.

Next, create the stripHtmlEntities method itself by adding the highlighted lines to the NewRecipe component:

Next, create the stripHtmlEntities method itself by adding the highlighted lines to the NewRecipe component:

~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
class NewRecipe extends React.Component {constructor(props) {super(props);this.state = {name: "",ingredients: "",instruction: ""};this.onChange = this.onChange.bind(this);this.onSubmit = this.onSubmit.bind(this);this.stripHtmlEntities = this.stripHtmlEntities.bind(this);}stripHtmlEntities(str) {return String(str).replace(/</g, "&lt;").replace(/>/g, "&gt;");}}export default NewRecipe;

In the stripHtmlEntities method, you’re replacing the < and > characters with their escaped value. This way you’re not storing raw HTML in your database.

In the stripHtmlEntities method, you're replacing the < and > characters with their escaped value. This way you're not storing raw HTML in your database.

Next add the onChange and onSubmit methods to the NewRecipe component to handle editing and submission of the form:

Next add the onChange and onSubmit methods to the NewRecipe component to handle editing and submission of the form:

~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
class NewRecipe extends React.Component {constructor(props) {super(props);this.state = {name: "",ingredients: "",instruction: ""};this.onChange = this.onChange.bind(this);this.onSubmit = this.onSubmit.bind(this);this.stripHtmlEntities = this.stripHtmlEntities.bind(this);}stripHtmlEntities(str) {return String(str).replace(/</g, "&lt;").replace(/>/g, "&gt;");}onChange(event) {this.setState({ [event.target.name]: event.target.value });}onSubmit(event) {event.preventDefault();const url = "/api/v1/recipes/create";const { name, ingredients, instruction } = this.state;if (name.length == 0 || ingredients.length == 0 || instruction.length == 0)return;const body = {name,ingredients,instruction: instruction.replace(/\n/g, "<br> <br>")};const token = document.querySelector('meta[name="csrf-token"]').content;fetch(url, {method: "POST",headers: {"X-CSRF-Token": token,"Content-Type": "application/json"},body: JSON.stringify(body)}).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(response => this.props.history.push(`/recipe/${response.id}`)).catch(error => console.log(error.message));}}export default NewRecipe;

In the onChange method, you used the ES6 computed property names to set the value of every user input to its corresponding key in your state. In the onSubmit method, you checked that none of the required inputs are empty. You then build an object that contains the parameters required by the recipe controller to create a new recipe. Using regular expression, you replace every new line character in the instruction with a break tag, so you can retain the text format entered by the user.

In the onChange method, you used the ES6 computed property names to set the value of every user input to its corresponding key in your state. In the onSubmit method, you checked that none of the required inputs are empty. You then build an object that contains the parameters required by the recipe controller to create a new recipe. Using regular expression , you replace every new line character in the instruction with a break tag, so you can retain the text format entered by the user.

To protect against Cross-Site Request Forgery (CSRF) attacks, Rails attaches a CSRF security token to the HTML document. This token is required whenever a non-GET request is made. With the token constant in the preceding code, your application verifies the token on the server and throws an exception if the security token doesn’t match what is expected. In the onSubmit method, the application retrieves the CSRF token embedded in your HTML document by Rails and makes a HTTP request with a JSON string. If the recipe is successfully created, the application redirects the user to the recipe page where they can view their newly created recipe.

To protect against Cross-Site Request Forgery (CSRF) attacks, Rails attaches a CSRF security token to the HTML document. This token is required whenever a non- GET request is made. With the token constant in the preceding code, your application verifies the token on the server and throws an exception if the security token doesn't match what is expected. In the onSubmit method, the application retrieves the CSRF token embedded in your HTML document by Rails and makes a HTTP request with a JSON string. If the recipe is successfully created, the application redirects the user to the recipe page where they can view their newly created recipe.

Lastly, add a render method that renders a form for the user to enter the details for the recipe the user wishes to create:

Lastly, add a render method that renders a form for the user to enter the details for the recipe the user wishes to create:

~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
~/rails_react_recipe/app/javascript/components/NewRecipe.jsx
class NewRecipe extends React.Component {constructor(props) {super(props);this.state = {name: "",ingredients: "",instruction: ""};this.onChange = this.onChange.bind(this);this.onSubmit = this.onSubmit.bind(this);this.stripHtmlEntities = this.stripHtmlEntities.bind(this);}stripHtmlEntities(str) {return String(str).replace(/</g, "&lt;").replace(/>/g, "&gt;");}onChange(event) {this.setState({ [event.target.name]: event.target.value });}onSubmit(event) {event.preventDefault();const url = "/api/v1/recipes/create";const { name, ingredients, instruction } = this.state;if (name.length == 0 || ingredients.length == 0 || instruction.length == 0)return;const body = {name,ingredients,instruction: instruction.replace(/\n/g, "<br> <br>")};const token = document.querySelector('meta[name="csrf-token"]').content;fetch(url, {method: "POST",headers: {"X-CSRF-Token": token,"Content-Type": "application/json"},body: JSON.stringify(body)}).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(response => this.props.history.push(`/recipe/${response.id}`)).catch(error => console.log(error.message));}render() {return (<div className="container mt-5"><div className="row"><div className="col-sm-12 col-lg-6 offset-lg-3"><h1 className="font-weight-normal mb-5">Add a new recipe to our awesome recipe collection.</h1><form onSubmit={this.onSubmit}><div className="form-group"><label htmlFor="recipeName">Recipe name</label><inputtype="text"name="name"id="recipeName"className="form-control"requiredonChange={this.onChange}/></div><div className="form-group"><label htmlFor="recipeIngredients">Ingredients</label><inputtype="text"name="ingredients"id="recipeIngredients"className="form-control"requiredonChange={this.onChange}/><small id="ingredientsHelp" className="form-text text-muted">Separate each ingredient with a comma.</small></div><label htmlFor="instruction">Preparation Instructions</label><textareaclassName="form-control"id="instruction"name="instruction"rows="5"requiredonChange={this.onChange}/><button type="submit" className="btn custom-button mt-3">Create Recipe</button><Link to="/recipes" className="btn btn-link mt-3">Back to recipes</Link></form></div></div></div>);}}export default NewRecipe;

In the render method, you have a form that contains three input fields; one for the recipeName, recipeIngredients, and instruction. Each input field has an onChange event handler that calls the onChange method. Also, there’s an onSubmit event handler on the submit button that calls the onSubmit method which then submits the form data.

In the render method, you have a form that contains three input fields; one for the recipeName , recipeIngredients , and instruction . Each input field has an onChange event handler that calls the onChange method. Also, there's an onSubmit event handler on the submit button that calls the onSubmit method which then submits the form data.

Save and exit the file.

保存并退出文件。

To access this component in the browser, update your route file with its route:

To access this component in the browser, update your route file with its route:

  • nano app/javascript/routes/Index.jsx
    nano app/javascript/routes/Index.jsx

Update your route file to include these highlighted lines:

Update your route file to include these highlighted lines:

~/rails_react_recipe/app/javascript/routes/Index.jsx
~/rails_react_recipe/app/javascript/routes/Index.jsx
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import Home from "../components/Home";
import Recipes from "../components/Recipes";
import Recipe from "../components/Recipe";
import NewRecipe from "../components/NewRecipe";export default (<Router><Switch><Route path="/" exact component={Home} /><Route path="/recipes" exact component={Recipes} /><Route path="/recipe/:id" exact component={Recipe} /><Route path="/recipe" exact component={NewRecipe} /></Switch></Router>
);

With the route in place, save and exit your file. Restart your development server and visit http://localhost:3000 in your browser. Navigate to the recipes page and click the Create New Recipe button. You will find a page with a form to add recipes to your database:

With the route in place, save and exit your file. Restart your development server and visit http://localhost:3000 in your browser. Navigate to the recipes page and click the Create New Recipe button. You will find a page with a form to add recipes to your database:

Enter the required recipe details and click the Create Recipe button; you will see the newly created recipe on the page.

Enter the required recipe details and click the Create Recipe button; you will see the newly created recipe on the page.

In this step, you brought your food recipe application to life by adding the ability to create recipes. In the next step, you’ll add the functionality to delete recipes.

In this step, you brought your food recipe application to life by adding the ability to create recipes. In the next step, you'll add the functionality to delete recipes.

Step 9 — Deleting Recipes (Step 9 — Deleting Recipes)

In this section, you will modify your Recipe component to be able to delete recipes.

In this section, you will modify your Recipe component to be able to delete recipes.

When you click the delete button on the recipe page, the application will send a request to delete a recipe from the database. To do this, open up your Recipe.jsx file:

When you click the delete button on the recipe page, the application will send a request to delete a recipe from the database. To do this, open up your Recipe.jsx file:

  • nano app/javascript/components/Recipe.jsx
    nano app/javascript/components/Recipe.jsx

In the constructor of the Recipe component, bind this to the deleteRecipe method:

In the constructor of the Recipe component, bind this to the deleteRecipe method:

~/rails_react_recipe/app/javascript/components/Recipe.jsx
~/rails_react_recipe/app/javascript/components/Recipe.jsx
class Recipe extends React.Component {constructor(props) {super(props);this.state = { recipe: { ingredients: "" } };this.addHtmlEntities = this.addHtmlEntities.bind(this);this.deleteRecipe = this.deleteRecipe.bind(this);}
...

Now add a deleteRecipe method to the Recipe component:

Now add a deleteRecipe method to the Recipe component:

~/rails_react_recipe/app/javascript/components/Recipe.jsx
~/rails_react_recipe/app/javascript/components/Recipe.jsx
class Recipe extends React.Component {constructor(props) {super(props);this.state = { recipe: { ingredients: "" } };this.addHtmlEntities = this.addHtmlEntities.bind(this);this.deleteRecipe = this.deleteRecipe.bind(this);}componentDidMount() {const {match: {params: { id }}} = this.props;const url = `/api/v1/show/${id}`;fetch(url).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(response => this.setState({ recipe: response })).catch(() => this.props.history.push("/recipes"));}addHtmlEntities(str) {return String(str).replace(/&lt;/g, "<").replace(/&gt;/g, ">");}deleteRecipe() {const {match: {params: { id }}} = this.props;const url = `/api/v1/destroy/${id}`;const token = document.querySelector('meta[name="csrf-token"]').content;fetch(url, {method: "DELETE",headers: {"X-CSRF-Token": token,"Content-Type": "application/json"}}).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(() => this.props.history.push("/recipes")).catch(error => console.log(error.message));}render() {const { recipe } = this.state;let ingredientList = "No ingredients available";
...

In the deleteRecipe method, you get the id of the recipe to be deleted, then build your url and grab the CSRF token. Next, you make a DELETE request to the Recipes controller to delete the recipe. If the recipe is successfully deleted, the application redirects the user to the recipes page.

In the deleteRecipe method, you get the id of the recipe to be deleted, then build your url and grab the CSRF token. Next, you make a DELETE request to the Recipes controller to delete the recipe. If the recipe is successfully deleted, the application redirects the user to the recipes page.

To run the code in the deleteRecipe method whenever the delete button is clicked, pass it as the click event handler to the button. Add an onClick event to the delete button in the render method:

To run the code in the deleteRecipe method whenever the delete button is clicked, pass it as the click event handler to the button. Add an onClick event to the delete button in the render method:

~/rails_react_recipe/app/javascript/components/Recipe.jsx
~/rails_react_recipe/app/javascript/components/Recipe.jsx
...
return (<div className=""><div className="hero position-relative d-flex align-items-center justify-content-center"><imgsrc={recipe.image}alt={`${recipe.name} image`}className="img-fluid position-absolute"/><div className="overlay bg-dark position-absolute" /><h1 className="display-4 position-relative text-white">{recipe.name}</h1></div><div className="container py-5"><div className="row"><div className="col-sm-12 col-lg-3"><ul className="list-group"><h5 className="mb-2">Ingredients</h5>{ingredientList}</ul></div><div className="col-sm-12 col-lg-7"><h5 className="mb-2">Preparation Instructions</h5><divdangerouslySetInnerHTML={{__html: `${recipeInstruction}`}}/></div><div className="col-sm-12 col-lg-2"><button type="button" className="btn btn-danger" onClick={this.deleteRecipe}>Delete Recipe</button></div></div><Link to="/recipes" className="btn btn-link">Back to recipes</Link></div></div>
);
...

At this point in the tutorial, your complete Recipe.jsx file will look like this:

At this point in the tutorial, your complete Recipe.jsx file will look like this:

~/rails_react_recipe/app/javascript/components/Recipe.jsx
~/rails_react_recipe/app/javascript/components/Recipe.jsx
import React from "react";
import { Link } from "react-router-dom";class Recipe extends React.Component {constructor(props) {super(props);this.state = { recipe: { ingredients: "" } };this.addHtmlEntities = this.addHtmlEntities.bind(this);this.deleteRecipe = this.deleteRecipe.bind(this);}addHtmlEntities(str) {return String(str).replace(/&lt;/g, "<").replace(/&gt;/g, ">");}componentDidMount() {const {match: {params: { id }}} = this.props;const url = `/api/v1/show/${id}`;fetch(url).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(response => this.setState({ recipe: response })).catch(() => this.props.history.push("/recipes"));}deleteRecipe() {const {match: {params: { id }}} = this.props;const url = `/api/v1/destroy/${id}`;const token = document.querySelector('meta[name="csrf-token"]').content;fetch(url, {method: "DELETE",headers: {"X-CSRF-Token": token,"Content-Type": "application/json"}}).then(response => {if (response.ok) {return response.json();}throw new Error("Network response was not ok.");}).then(() => this.props.history.push("/recipes")).catch(error => console.log(error.message));}render() {const { recipe } = this.state;let ingredientList = "No ingredients available";if (recipe.ingredients.length > 0) {ingredientList = recipe.ingredients.split(",").map((ingredient, index) => (<li key={index} className="list-group-item">{ingredient}</li>));}const recipeInstruction = this.addHtmlEntities(recipe.instruction);return (<div className=""><div className="hero position-relative d-flex align-items-center justify-content-center"><imgsrc={recipe.image}alt={`${recipe.name} image`}className="img-fluid position-absolute"/><div className="overlay bg-dark position-absolute" /><h1 className="display-4 position-relative text-white">{recipe.name}</h1></div><div className="container py-5"><div className="row"><div className="col-sm-12 col-lg-3"><ul className="list-group"><h5 className="mb-2">Ingredients</h5>{ingredientList}</ul></div><div className="col-sm-12 col-lg-7"><h5 className="mb-2">Preparation Instructions</h5><divdangerouslySetInnerHTML={{__html: `${recipeInstruction}`}}/></div><div className="col-sm-12 col-lg-2"><button type="button" className="btn btn-danger" onClick={this.deleteRecipe}>Delete Recipe</button></div></div><Link to="/recipes" className="btn btn-link">Back to recipes</Link></div></div>);}
}export default Recipe;

Save and exit the file.

保存并退出文件。

Restart the application server and navigate to the homepage. Click the View Recipes button to view all existing recipes, view any individual recipe, and click the Delete Recipe button on the page to delete the article. You will be redirected to the recipes page, and the deleted recipe will no longer exists.

Restart the application server and navigate to the homepage. Click the View Recipes button to view all existing recipes, view any individual recipe, and click the Delete Recipe button on the page to delete the article. You will be redirected to the recipes page, and the deleted recipe will no longer exists.

With the delete button working, you now have a fully functional recipe application!

With the delete button working, you now have a fully functional recipe application!

结论 (Conclusion)

In this tutorial, you created a food recipe application with Ruby on Rails and a React frontend, using PostgreSQL as your database and Bootstrap for styling. If you’d like to run through more Ruby on Rails content, take a look at our Securing Communications in a Three-tier Rails Application Using SSH Tunnels tutorial, or head to our How To Code in Ruby series to refresh your Ruby skills. To dive deeper into React, try out our How To Display Data from the DigitalOcean API with React article.

In this tutorial, you created a food recipe application with Ruby on Rails and a React frontend, using PostgreSQL as your database and Bootstrap for styling. If you'd like to run through more Ruby on Rails content, take a look at our Securing Communications in a Three-tier Rails Application Using SSH Tunnels tutorial, or head to our How To Code in Ruby series to refresh your Ruby skills. To dive deeper into React, try out our How To Display Data from the DigitalOcean API with React article.

翻译自: https://www.digitalocean.com/community/tutorials/how-to-set-up-a-ruby-on-rails-project-with-a-react-frontend

react前端项目

react前端项目_如何使用React前端设置Ruby on Rails项目相关推荐

  1. Java web小项目_个人主页(1)—— 云环境搭建与项目部署

    摘自:Java web小项目_个人主页(1)-- 云环境搭建与项目部署 作者:丶PURSUING 发布时间: 2021-03-26 23:59:39 网址:https://blog.csdn.net/ ...

  2. react在线文件_在线教育大前端架构演进之路

    前段时间,本人有幸于在深圳GMTC大前端架构演进专场进行分享.其后应叶冉编辑邀请,总结了此次分享的演讲稿<腾讯在线教育大前端架构演进之路>.首先做一下自我介绍.我是来自腾讯的工程师 hai ...

  3. react前端开发_是的,React正在接管前端开发。 问题是为什么。

    react前端开发 by Samer Buna 通过Samer Buna 是的,React正在接管前端开发. 问题是为什么. (Yes, React is taking over front-end ...

  4. react本地储存_如何在React项目中利用本地存储

    react本地储存 以及为什么要这么做. 本地存储是现代Web浏览器固有的Web API. 它允许网站/应用程序在浏览器中存储数据(简单和有限),从而使数据在以后的浏览器会话中可用. 在开始学习本教程 ...

  5. react java编程_快速上手React编程 PDF 下载

    资料目录: 第1章  初积React  3 1.1  什么是React  4 1.2 React解决的问题  5 1.3  使用React的好处  6 1.3.1  简单性  6 1.3.2  速度和 ...

  6. react jest测试_如何使用React测试库和Jest开始测试React应用

    react jest测试 Testing is often seen as a tedious process. It's extra code you have to write, and in s ...

  7. react实战课程_在使用React一年后,我学到的最重要的课程

    react实战课程 by Tomas Eglinskas 由Tomas Eglinskas 在使用React一年后,我学到的最重要的课程 (The most important lessons I'v ...

  8. react hooks使用_如何使用React Hooks和Context API构建简单的PokémonWeb App

    react hooks使用 After seven years of full stack development using Ruby, Python, and vanilla JavaScript ...

  9. react hooks使用_何时使用React Suspense和React Hooks

    react hooks使用 React Suspense对Monad就像钩子对应用符号一样 (React Suspense is to a Monad as Hooks are to Applicat ...

最新文章

  1. NC45实现二叉树先序、中序和后序遍历
  2. [译] How to NOT React:React 中常见的反模式与陷阱
  3. java堆设置成多少合适_jvm~xmx设置多少合适
  4. linux 8051 编译,[编译] 3、在Linux下搭建51单片机的开发烧写环境(makefile版)
  5. eureka自我保护时间_SpringCloud Eureka自我保护机制
  6. 福州java培训哪里好_南通java培训哪家好
  7. nyoj 456 邮票分你一半【01背包】
  8. 一行代码蒸发了 ¥6,447,277,680 人民币!
  9. amoeba安装与简单使用(一)
  10. 金字塔 2020-12-29
  11. 网络工具之PacketTracer8安装
  12. kubectl命令补全
  13. 用vivo手机拍照一定要先打开这个设置,不然白浪费这么强大的手机
  14. php 安装、使用sphinx
  15. 国科大学习资料--人工智能原理与算法-第四次作业解析(学长整理)
  16. PHP 命令行模式实战之cli+mysql 模拟队列批量发送邮件(在Linux环境下PHP 异步执行脚本发送事件通知消息实际案例)...
  17. 44道JavaScript送命题
  18. 安卓逆向工程--针对授权key方式的破解
  19. 实现Media config的切换,使得Loki-100G-5S-2P测试板卡可以链接在50GbE模式下进行流量测试
  20. RecyclerView的万能分割线

热门文章

  1. Mac Pro 触摸板按压失效(没有按压回弹效果)
  2. 数值法求解最优控制问题(四)——伪谱法
  3. 英语不好学不好编程?程序员记忆单词专属诀窍,效果简直要逆天
  4. 基于主轴变换的医学图像倾斜校正
  5. 量子计算机1003无标题,量子计算机研究
  6. 信用风险建模 in Python 系列 7 - ASRF 模型
  7. aistudio解压zip
  8. 小程序转码机器人-微信小程序转二维码
  9. 基于FFmpeg+rtsp读取摄像头实时图像
  10. 国产数据库OpenGauss--内存优化表(MOT)实践