Skip to content

CS 169

Posted on:2022.05.13

TOC

Open TOC

Info

UCB CS169: software engineering

https://learning.edx.org/course/course-v1:BerkeleyX+CS169.1x+1T2022/home

CHIPS - Coding/Hands-on Integrated Projects

https://github.com/saasbook/courseware#chips-codinghands-on-integrated-projects-with-autograding

CHIPS 2.5: Ruby Intro

Info

https://github.com/VGalaxies/hw-ruby-intro

https://www.ruby-lang.org/zh_cn/

https://try.ruby-lang.org/

https://ruby-doc.org/

https://learnxinyminutes.com/docs/zh-cn/ruby-cn/

Intro

xerolinux 自带了 ruby / gem / rspec

RSpec - 单元测试框架

RubyGems - 包管理器

Bundler - 跟踪并安装所需的特定版本的 gem,以此来为 Ruby 项目提供一致的运行环境

$ gem install bundler

使用 Gemfile 管理 gems

VSCode 插件 - Ruby Solargraph

$ gem install solargraph

需要正确配置路径

gem install xxx 可执行文件路径为 /home/vgalaxy/.local/share/gem/ruby/3.0.0/bin/

注意 bundle 安装 gem 的路径在项目中

$ bundle config set --local path 'vendor/bundle'
$ bundle install

使用

$ bundle exec xxx

来运行 gem

Gems

下面介绍一些 gems

https://rubygems.org/

Test Driven Development

http://code.tutsplus.com/tutorials/testing-your-ruby-code-with-guard-rspec-pry—cms-19974

检测文件变化,自动运行测试

通过 guard init 生成 Guardfile

感觉不太灵敏

调试器

提供 REPL 环境

Behaviour Driven Development

类似 make

也可以通过 archlinux 安装

Rakefile

CHIPS 3.3: HTTP and URIs

https://github.com/saasbook/hw-http-intro

CHIPS 3.7: Create and Deploy a Simple SaaS app

Part 0

https://github.com/VGalaxies/simple-saas-app

区分开发和生产环境

https://dev.to/flippedcoding/difference-between-development-stage-and-production-d0p

without bundler

$ gem install rack
$ gem install sinatra
$ gem install rerun

setup

$ rackup --port 3000
$ rerun -- rackup --port 3000

access http://localhost:3000/

deploy to Heroku

https://dashboard.heroku.com/apps

$ yay -S heroku-cli
$ heroku login -i

需要额外添加 gem 'puma'

https://akshaykhot.com/couldnt-find-handler-error/

需要生成 Gemfile.lock

似乎提前安装了所需的 gems 后,就不会在项目中安装 gems 了

添加 Procfile

$ web: bundle exec rackup config.ru -p $PORT

部署

$ git push heroku master

Part 1

https://github.com/VGalaxies/hw-sinatra-saas-wordguesser

$ bundle exec autotest
$ http http://randomword.saasbook.info/RandomWord

延迟比 guard 低

TDD 体验很好

byebug 报错

LoadError:
cannot load such file -- irb
Did you mean? erb
drb

需要修改 Gemfile

gem 'byebug' #, '5.0.0'
gem 'irb'
gem 'rdoc'

感觉 Bundler 没有处理好依赖关系

Part 2

介绍了 RESTful API 的设计

Show game state, allow player to enter guess; may redirect to Win or Lose GET /show
Display form that can generate POST /create GET /new
Start new game; redirects to Show Game after changing state POST /create
Process guess; redirects to Show Game after changing state POST /guess
Show "you win" page with button to start new game GET /win
Show "you lose" page with button to start new game GET /lose

两个关注点

隐含的 MVC 模型

Part 3

http://sinatrarb.com/documentation.html

介绍了 app.rb

同时部署到 Heroku 上

https://afternoon-fortress-30623.herokuapp.com/

Part 4

介绍了 cucumber 的使用和 BDD

使用 Capybara 模拟的浏览器的行为,并检查服务器的响应

https://rubydoc.info/github/teamcapybara/capybara#using-capybara-with-cucumber

下面是一个 .feature 文件

Feature: start new game
As a player
So I can play Wordguesser
I want to start a new game
Scenario: I start a new game
Given I am on the home page
And I press "New Game"
Then I should see "Guess a letter"
And I press "New Game"
Then I should see "Guess a letter"

Feature: 为场景说明,不会被 cucumber 执行

Scenario: 描述了每一步的交互,其中

Given /^(?:|I )am on (.+)$/ do |page_name|
visit path_to(page_name)
end

还有一些值得注意的细节

对于 Part 4 而言,只需完成

  1. new.erb 中的 <form>
  2. app.rb 中的 post '/guess'

即可通过 4 个 features

$ cucumber features/guess.feature

仍然需要手动安装 gems

Part 5

完成 app.rb

  1. get '/show'
  2. get '/win'
  3. get '/lose'

即可通过全部 features

需要考虑到直接访问 GET /winGET /lose 的情形

Part 6

new techniques in the future

写完了才发现有 RubyMine

大无语……

Part 7

从本地获取随机单词

class LocalWordGenerator
def self.get_word
arr = IO.readlines("dict/words") # relative to root
return arr[Random.new.rand(0...arr.length)].strip
end
end

添加一个 GET 即可

get '/local' do
@game = WordGuesserGame.new(LocalWordGenerator.get_word)
redirect '/show'
end

当然只能在本地部署

CHIPS 4.3: ActiveRecord Basics

https://github.com/VGalaxies/hw-activerecord-practice

ActiveRecord 本质上就是一个 ORM 框架

类似 GORM,只不过语法更加简单

相同的特点

RTFM

注意对数据库进行修改的测试最后会进行事务的回滚

所以实际上数据并不会被修改

CHIPS 4.5: Rails Routes

https://github.com/VGalaxies/hw-activerecord-practice

RTFM

没有实际的编码练习

可视化 Rails Routes 的结果

config/routes.rb 中编写 Rails Routes

例如

resources :photos

会产生如下路由

HTTP VerbPathController#ActionUsed for
GET/photosphotos#indexdisplay a list of all photos
GET/photos/newphotos#newreturn an HTML form for creating a new photo
POST/photosphotos#createcreate a new photo
GET/photos/:idphotos#showdisplay a specific photo
GET/photos/:id/editphotos#editreturn an HTML form for editing a photo
PATCH/PUT/photos/:idphotos#updateupdate a specific photo
DELETE/photos/:idphotos#destroydelete a specific photo

CHIPS 4.7: Wordguesser on Rails

https://github.com/VGalaxies/hw-rails-wordguesser

框架从 Sinatra 变成了 Rails

仍然没有实际的编码练习

参考 https://github.com/saasbook/hw-rails-wordguesser/pull/22

重命名 hangperson_game.rbword_guesser_game.rb

$ rails server

下面是对 Code Comprehension Questions 的解答

Q2.1. Where in the Rails app directory structure is the code corresponding to the WordGuesserGame model?

app/models/word_guesser_game.rb

Q2.2. In what file is the code that most closely corresponds to the logic in the Sinatra apps’ app.rb file that handles incoming user actions?

app/controllers/game_controller.rb

Q2.3. What class contains that code?

class GameController < ApplicationController

Q2.4. From what other class (which is part of the Rails framework) does that class inherit?

class ApplicationController < ActionController::Base

Q2.5. In what directory is the code corresponding to the Sinatra app’s views (new.erb, show.erb, etc.)?

app/views

Q2.6. The filename suffixes for these views are different in Rails than they were in the Sinatra app. What information does the rightmost suffix of the filename (e.g.: in foobar.abc.xyz, the suffix .xyz) tell you about the file contents?

ERB – Ruby Templating

Q2.7. What information does the other suffix tell you about what Rails is being asked to do with the file?

Content-Typetext/html

Q2.8. In what file is the information in the Rails app that maps routes (e.g. GET /new) to controller actions?

config/routes.rb

Q2.9. What is the role of the :as => 'name' option in the route declarations of config/routes.rb? (Hint: look at the views.)

一是对应 app/controllers/game_controller.rb 中的 game_path / win_game_path/ lose_game_path

二是对应 app/views 中的 form_tag,如 guess_path / create_game_path

Q3.1. In the Sinatra version, before do...end and after do...end blocks are used for session management. What is the closest equivalent in this Rails app, and in what file do we find the code that does it?

def get_game_from_session
@game = WordGuesserGame.new('')
if !session[:game].blank?
@game = YAML.load(session[:game])
end
end
def store_game_in_session
session[:game] = @game.to_yaml
end

Q3.2. A popular serialization format for exchanging data between Web apps is JSON. Why wouldn’t it work to use JSON instead of YAML? (Hint: try replacing YAML.load() with JSON.parse() and .to_yaml with .to_json to do this test. You will have to clear out your cookies associated with localhost:3000, or restart your browser with a new Incognito/Private Browsing window, in order to clear out the session[]. Based on the error messages you get when trying to use JSON serialization, you should be able to explain why YAML serialization works in this case but JSON doesn’t.)

报错信息如下

undefined method `check_win_or_lose' for {"word"=>"digestion", "guesses"=>"", "wrong_guesses"=>""}:Hash
app/controllers/game_controller.rb:33:in `show'
def show
status = @game.check_win_or_lose
redirect_to win_game_path if status == :win
redirect_to lose_game_path if status == :lose
end

只有数据没有方法

Q4.1. In the Sinatra version, each controller action ends with either redirect (which as you can see becomes redirect_to in Rails) to redirect the player to another action, or erb to render a view. Why are there no explicit calls corresponding to erb in the Rails version? (Hint: Based on the code in the app, can you discern the Convention-over-Configuration rule that is at work here?)

如果没有 redirect_to,则默认 render 方法名对应的 view

show - show.html.erb

这也解释了 new 方法为空也能工作的原因

def new
end

Q4.2. In the Sinatra version, we directly coded an HTML form using the <form> tag, whereas in the Rails version we are using a Rails method form_tag, even though it would be perfectly legal to use raw HTML <form> tags in Rails. Can you think of a reason Rails might introduce this “level of indirection”?

封装,可以参考

Q4.3. How are form elements such as text fields and buttons handled in Rails? (Again, raw HTML would be legal, but what’s the motivation behind the way Rails does it?)

submit_tag - 提交按钮

还有 label_tagtext_field_tag,不过框架代码使用了 raw HTML

Q4.4. In the Sinatra version, the show, win and lose views re-use the code in the new view that offers a button for starting a new game. What Rails mechanism allows those views to be re-used in the Rails version?

<%= render :template => 'game/new' %>

render,可以参考

Q5.1. What is a qualitative explanation for why the Cucumber scenarios and step definitions didn’t need to be modified at all to work equally well with the Sinatra or Rails versions of the app?

$ rake cucumber

因为 API 接口没有变化