【RSpec】Feature specを調べた・書いた
どう便利
書くことで、実装しなければいけないことが明確になります。
また、仕様からコードに落とし込むので、あいまいな点が早い段階でわかります。
さらに、後から参画するエンジニアにとって「あ、こんな機能あるんだ」という感じで、みんなハッピーです。
書いた
まず、specファイルを用意します。
spec/features/xxxxx_spec.rb
ファイルの末尾に`_spec`がついていれば良いです。
これからFeature specを書きますという宣言をします。
require 'rails_helper' RSpec.feature '物件', :type => :feature do end
次にどのようなユーザーの操作をシミュレートするか書きます。
scenarioはitに相当します。
require 'rails_helper' RSpec.feature '物件', :type => :feature do # 追加 scenario '編集ボタンを押したときに既存のデータが入力されている' do end end
次にどのユーザーがどのURLにアクセスするか書きます
require 'rails_helper' RSpec.feature '物件', :type => :feature do scenario '編集ボタンを押したときに既存のデータが入力されている' do # 追加 visit edit_room_path end end
`edit_room`は`rails routes`を実行するとわかります。
edit_room_pathはpath内に:idを要求してくるので、編集するオブジェクトを作成・取得します。
require 'rails_helper' RSpec.feature '物件', :type => :feature do before do # ユーザーの作成 Room.create(id: 1, name: 'ほげハイツ', price: 50000, address: '渋谷区神泉', building_age: 32, note: '備考') end # ユーザーの取得 let(:room) { Room.find(1) } scenario '編集ボタンを押したときに既存のデータが入力されている' do # 引数にRoomオブジェクトを渡す visit edit_room_path room end end
beforeの部分はbackgroundとも書けます。むしろそちらの方が良いです。
これでデータを編集するページにアクセスできるようになりました。
あとは期待する結果を書きます。
require 'rails_helper' RSpec.feature '物件', :type => :feature do before do Room.create(id: 1, name: 'ほげハイツ', price: 50000, address: '渋谷区神泉', building_age: 32, note: '備考') end let(:room) { Room.find(1) } scenario '編集ボタンを押したときに既存のデータが入力されている' do visit edit_room_path room # 追加 expect(page).to have_field '物件名', with: 'ほげハイツ' expect(page).to have_field '賃料', with: 50000 expect(page).to have_field '住所', with: '渋谷区神泉' expect(page).to have_field '築年数', with: 32 expect(page).to have_field '備考', with: '備考' end end
let は given とも書けます。givenの方が良いです。
expect(page).to have_field '物件名', with: 'ほげハイツ'
はpage内に、「物件名」というラベルがあり、そのinputタグのvalue属性には'ほげハイツ'が入力されている
という意味になります。
次はFactoryGirlでテストに使用する共通のデータを書きます。
【Rails】RSpecでフォームから登録したデータをテストする
1 Gemfileに追記する
2017年5月29日時点ではこんな感じ。
WARNINGが出るけどとりあえず無視する。
group :development, :test do gem 'rspec-rails', '~> 3.5' gem 'factory_girl_rails', '~> 4.2.1' end group :test do gem 'faker', '~> 1.1.2' gem 'capybara', '~> 2.2.0' gem 'database_cleaner', '~> 1.0.1' gem 'launchy', '~> 2.3.0' gem 'selenium-webdriver', '~>2.45.0' end
2 rspec-railsのドキュメント通りに進める
着実に、公式ドキュメント通りに進めましょう。
GitHub - rspec/rspec-rails: RSpec for Rails-3+
3 データをPOSTする処理を書く
post :create, params: { room: { name: '神泉ハイツ', price: '40000', address: '渋谷区神泉', building_age: 12, note: '備考' } }
4 インサートしたデータを取得して確認する
expect(Room.last.name).to eq '神泉ハイツ'
他にもいい方法がありそうだけど、とりあえずここまで!
【Rails】カラムのデータ型を変える
動機
何も考えずにテーブルを設計してしまい、カラムのデータ型を変更したい。
やること
Userテーブルのageカラムのデータ型をstringからintegerにしたい。
1 migrationファイルを用意します
rails g migration xxxxxxx <- 適切なクラス名
2 migrationファイルにupメソッドとdownメソッドを定義します
upメソッドに変更後の内容、downメソッドに変更前の内容を書きます。
rails db:migrate
でupメソッドの変更が実行されます。
rails db:rollback
でdownメソッドの変更が実行されます。
class ChangeAgeOnUsers < ActiveRecord::Migration[5.0] def up change_table :users do |t| t.change :age, :integer end end def down change_table :users do |t| t.change :age, :string end end end
3 rails db:migrateを実行する
migrationファイルのupメソッドの内容が反映されているか、確認します。
sqlite> .schema users CREATE TABLE "users" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "age" integer, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL );
念のため、rails db:rollbackしてみて、ロールバックが期待通りに動作するか、確認します。
sqlite> .schema users CREATE TABLE "users" ( "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar, "age" varchar, "created_at" datetime NOT NULL, "updated_at" datetime NOT NULL );
いい感じですね。
【Rails】プロジェクトの初期データを用意する
動機
自分のプロジェクトをgit cloneした際に、今まで使っていたDBのデータがなくてつらさを感じた。
なんとかして、用意しておきたい。
1 seeds.rbファイルを用意する
db/seeds.rbを作成します。
当環境ではrails newした際に作成されていました。
2 seeds.rbファイルに初期データを記述する
User.create( name: "John", email: john@example.com ) User.create( name: "Henry" email: henry@example.com )
3 rails db:seed を実行する
エラーがあれば、親切なエラーメッセージが発生します。
【Ruby】Ruby BitsからRubyらしいコードを学ぶ - Expressions編 -
PythonからRubyに転向しようとしている私ですが、Pythonを始めた時にも"Pythonic"な書き方に悩まされました。
というわけで、CodeSchoolさんのRubyBitsからRubyらしいコードの書き方を学んでいきます。
1. if式でネガティブなコンディションをtrueとする場合はunlessを使う
flag = false if !flag puts "condition true" else puts "condition false" end
これくらいなら読みにくくありませんが、condition部分は常にtrueになっていてほしいものです。
なので、unlessを使います。
flag = false unless flag puts "condition true" else puts "condition false" end
少し読みやすくなったような気がします。
2.conditions(if/unless)を使用する際、1行で書けるなら1行で書く
flag = false unless flag puts "condition true" end
このように1行で書けるそうなものは1行で書きます。
flag = false puts "condition true" unless flag
行数は減りましたが、若干読みづらい気がします。
3.if式のconditionにnilを使わない
RubyのnilはPythonでのNoneだと認識しています。
input = "" if input != nil puts "condition true" else puts "condition false" end
nilを使わないようにします。
input = "" if input puts "condition true" else puts "condition false" end
読みやすくなりましたね。
4.ショートサーキットを利用した変数代入
# この前処理でユーザーからの入力を受け取る可能性があることが前提 user_input ||= "" if user_input puts user_input else puts "please your input" end
なかなか使いどころが難しそうです。
5.変数の代入に、if式を利用する
Rubyのifは式なので、値を返します。その性質を利用してを変数の代入に利用します
flag = true if flag result = "condition true" else result = "condition false" end puts result
これはこうなります。
flag = true result = if flag "condition true" else "condition false" end puts result
resultへの代入が1つの式で済んでいるので、スマートになった気がします。
6.メソッド内でreturnをわざわざ明記しない
これはうっかりやってしまいそうです。
flag = true def get_condition(flag) return_value = "condition init" if flag return_value = "condition true" else return_value = "condition false" end return return_value end puts get_condition(flag)
これはこうなります。
flag = true def get_condition(flag) if flag "condition true" else "condition false" end end puts get_condition(flag)
すっきりして読みやすくなりました。
7.ショートサーキットを利用して、コードを短くする
A || B はAがtrueならAを返して、そうでなければ Bを返します。
その性質を利用するとコードが短くなります。
adventure_target = "hoge island" def result_adventure(treasure_target) result = adventure_result(treasure_target) if result result else "treasure not found" end end puts result_adventure(adventure_target)
adventure_target = "hoge island" def result_adventure(treasure_target) adventure_result(treasure_target) || "treasure not found" end puts result_adventure(adventure_target)
行数は減りましたが、またもや少し読みづらくなったような…
【Rails】カスタムバリデーションを設定する
動機
「xxxを入力してください」というエラーメッセージが切ない。
UXを向上させるため、人間味のあるエラーメッセージを設定したい。
どうするのか
こんな感じのバリデーションを設定していたとします。
class User < ApplicationRecord validates :name, presence: true validates :age, presence: true, numericality { only_integer: true, greater_than_or_equal_to: 0 } end
カスタムバリデーションを行うメソッドを追加します
class User < ApplicationRecord validates :name, presence: true #validates :age, # presence: true, # numericality { only_integer: true, greater_than_or_equal_to: 0 } # カスタムバリデーションを設定 validate :validate_age # カスタムバリデーションを定義 def validate_age @errors[:age] = "年齢には0以上の数字を入力してください" if age < 0 end end
実際にはこんな感じになります。
https://github.com/tarunama/rails-exam/pull/2/files
【Rails】Model.newにparamsを渡してしまうのは危険
こんなコードは危ない
# controllers/users_controller.rb def create @user = User.new(params[:user]) ... end
ユーザー情報すべてを渡してしまっているため、
管理者フラグを含めるカラムが存在する場合、
リクエストにadmin=1相当のデータを含めることで、
意図しないユーザーに意図しない権限を渡してしまう。
どう防ぐのか
Strong Parametersで意図的に、リクエストのどのデータを渡すか明示する。
# controllers/users_controller.rb def create # Strong ParametersをUser.newに渡している @user = User.new(user_params) ... end # 外部から変更されたくないのでprivateメソッドにする private def user_params params.require(:users).permit(:name, :email) end