FormObject実装(1回目)
form_objectの実装を進める際に、gem virtus
を使って実装を進めたので、何度かに分けて書いていきます。
環境
gemの導入
Gemfile
に追加します。
# Gemfile ... gem 'virtus' ...
$ bundle install
app/forms
app/forms
のディレクトリを作成します。
mkdir app/forms
config/initialze/forms.rb
を作成して,下記を追加します。
# forms.rb # require_dependecy 'parent_class' Dir.glob('app/forms/**/*.rb') do |f| require_dependency(Rails.roots.join(f)) end
formクラス作成します。
## vim app/forms/offer_form.rb class OfferForm include Virtus.model include ActiveModel::Model attr_accessor :errors attribute :id, Integer attribute :title, String attribute :description, String attribute :main_image, String attribute :side_image_left, String attribute :side_image_right, String attribute :status, Integer, default: 0 attribute :type_of_job_id, Integer attribute :company_id, Integer attribute :adoption_of_shape_id, Integer attribute :confirming, Boolean, default: false attribute :type_of_job_id, Integer attribute job_of_offer, JobOffer validates :title, :status, :type_of_job_id, :adoption_of_shape_id, presence: true def persisted? job_offer.persisted? end ... end
virtus
のREADME
に記載されていて、1:多の関係にある構造も実現できますので、accepts_nested_attributes
を利用せずに
実装をこちらに変更することができます。
余談ですが、Optimistic locking
を実装する上で、accepts_nested_attributesでは、実現できない(仕様が通れば...)ケースがあり
formObjectを通して、lock_version
をコントロールする設計を実現するのに、virtus
を採用(しようとした)経緯がありました。
補足: 誤解を与えそうな表現になっていたので、訂正。virtusは、属性の型、タイプキャスト、初期値を実現します。 virtusを使わずに、form objectを作成することもできます。
今回は、ここまでです。
参考
GitHub - solnic/virtus: Attributes on Steroids for Plain Old Ruby Objects
肥大化したActiveRecordモデルをリファクタリングする7つの方法(翻訳)
Rails4でFormオブジェクトを作る際に気をつける3つのポイント|江の島エンジニアBlog
postgre sql サーバの起動方法(Cent7.x)
自宅のrailsアプリでは、posgreを使っていてサービス起動/停止する際にコマンドを忘れないためのメモです。
環境
- Cent OS 7.x
- PostgreSQL 9.2.15
起動方法
インストール方法により、サービス名も変わります。
# systemctl start postgresql
株式会社リューノスを退職しました。
SNSに投稿するには長文になっていたので、 重複しますが報告と退職の理由について書いていきます。
正確には記事の投稿している日は、まだ在籍中で、1月10日で退職します。
報告
最終日なのに15時位まで、コードを書き、マージまでして、17時過ぎても質問され続けていて疲れもしましたが、まあ、最終日も貢献することができ嬉しく思っています。
挨拶の後には、お花とプレゼントまで頂いて、退職することを感じつつ、嬉しい一日になりました。 (帰り際に牛乳をこぼすという不手際がなければ。。)
今の会社では、2年間、在籍することになり、在籍中に若手の教育と開発全般で、活躍できたと自負しています。関係者の皆さまありがとうございました。
今後は、フリーで働きながら、エンジニアのキャリアを積んでいきます。
退職ついて
実は退職の理由に、後ろめたい理由があるんじゃないかと疑われる方がいました。その方は、心配してくれて聞いてくれたんだと思っています(優しい方なので)
上司に退職の意思を伝える際に、きみは影響力があるからと言われて (正直そんな自覚はなかったのですが) 開発メンバーに対して積極的は発言や行動(もくもく会、プライベートのイベント)は控えていました。 できるだけ、まわりや若手の意見を優先して採用して欲しかったですし、そうすることで 退職するインパクトが下がると思っていたからです。
上に書いた事をやっていることと、社内で親しい方々に、私からでなく、所属している部長から退職1ヶ月前に私が退職する旨を発表する形になってしまったことが原因だと思っています。 私からはまだ言わないで欲しいと言われていることもあり、そちらを優先していましたが。別の方から退職を伝えるのではなく、私から直接伝えておけば、印象が違ったなと反省しています。
実際に退職理由はこれですと一言で言えなくなっていまして。 上司に退職する際に伝えた言葉は、マネジメントと開発を両立するのは、難しいので、開発に専念したい旨を伝えていました。それだけではなく、お金の面、経験の面、時間の面で より自分にとってより良い環境や条件で働きたいと願望があり、そちらを優先した次第です。
エンジニアとして、まだまだ技術力も知識を伸ばしていきたいです。 承認欲求も強くあります。若手の教育係としての仕事もしていましたが、若手の成長を優先してばかりいれるほど大人でもなかったので 自分にとって成長できる幅が大きい、自分にとって好条件かつ効率の良い生き方を優先したいというのが今回の退職理由です。
以上です。だらだら書きましたが、改めて関係者の方々、暖く見送っていただきありがとうございました。
仕事のご依頼は下記のメールアドレスにお願いいたします。
ActiveRecord::Base.establishによる複数回切り替え時の不具合
掲題の件、テスト書いていることで発見できまして、APIリファレンスを読むと割と合点はいきましたが 間違えなどあれば、指摘いただきたいです。
環境 - rails 5.0.0.1(puma) - ruby 2.3.1
ActiveRecord::Base.establish
を何度も読み出すことで、query
を発行した際に事前に切り替えたDBではなく
切り替え前のDBのままquery
を実行する不具合がテストで見つかりましてその改善策です。
def change_schema ApplicationRecord.remove_connection ActiveRecord::Base.establish YAML.load_file(Rails.root.join('config', 'database.yml')[Rails.env] end
clear_all_connection!
などを実行しても、上手くリセットされていなかったので、remove_connection
からconnection
をdisconnect
で切断してから
動作するようになりました。
テストは恩恵も大きいし、テストを定常的に書くのも重要ですね。
参考
Strong parameterで受け取れなかったparameter
Strong Parameterを使っていて、取れないケースあたってしまい、後回しにしたので、気になっている。 Workaroundや、Hackな対応をする予定なので、明日また書き直す。
{ hoge: { id: '1', name: '2', age: '29', '1': { ... }, '2': { ... } } }
class HogeController < ApplicationController private def set_params # 間違っています。 params.reuqire(:hoge).permit(:id, :name, :age, '1', '2') end end
ここで、まず無理でした。数値をシンボル化できず、Strong Parameterでセットできないため。
... def set_params params.require(:hoge).permit(:id, :name, :age).to_h.merge((1..2).each_with_object({}) { |i, h| h[i.to_s] = params[:hoge][i.to_s] }) end ...
- また明日、書き直します。
参考
http://api.rubyonrails.org/classes/ActionController/Parameters.html
Module名を切り出してclass_nameを取り出す。
Qiitaに同じ内容の記事が丁度あって悲しい気分になったが、コードを書いていたので記録として残しておく。
本ブログは、備忘録という面もありますので、読まれている方は気分を害さないでいただきたいです。
動的にSQLのテーブル、カラムの別名を定義したくなり、クラス名とメソッド名を利用していたが、
app/service
配下に置かれている訳でもなく、app/service/hoge/...rb
という形でclass
が置かれているので、直接class
をそのまま取るわけにもいかないので、即興で書いていた。
サンプルコード
module Hoge class BaseService def initialize end def class_name self.class.to_s.split('::').last.underscore end end end
動作
$ Hoge::BaseService.new.class_name => "base_service"
以上になります。
参考
Factory Methodの利用(Ruby)
Rubyによるデザインパターン
という本を借りたまま、持っているのですが、たまたま実務でFactory Method
パターンを利用するのに
良い機会が得られたので、本を思い出しながら設計/実装を進めました。周りを気にせず、実装に集中できるのは楽しいですね。
前提
Rails 5系
Ruby 2.3系
準備
class BaseService def test_method end end class UserService < BaseService def test_method puts "UserServiceから呼び出しています。" end end class MemberService < BaseService def test_method puts "MemberServiceから呼び出しています。" end end
呼出側
class SelectClass def initialize(option) @service = "#{option.capitalize}Service".constantize.new # Rails end def service_call @service.test_method end end
動作確認
SelectClass.new("user").test_method -> puts "UserServiceから呼び出しています。" SelectClass.new("member").test_method -> puts "MemberServiceから呼び出しています。"
参考
フィーチャーテスト(Rails + Rspec + Capybara)
追記: コメントをいただきまして、一部内容を更新しています。
今朝、rspec 3.5リリースの記事*1をrspec
の公式HPから確認した所
Rails 5 では、 assigns と assert_template が soft deprecated になりました。
という内容が書いてあり、今までcontroller spec
に書いていたテストの一部をrequest spec
に移すことになりそうです。
controller spec
でテストする内容は、responseとレコードの新規/更新/削除になるのかと思います。
assigns
が使えず*2、インスタンス変数にアクセスできなくなっているため、
controller spec
でテストするのではなく、別のspec
でテストを書くことになりそうです。
本題に戻ります。
自作アプリにフィーチャーテストを追加しまして、まとめていきます。
model, controllerと、poltergeist
の準備についても省いています。
Gemfile
# Gemfile ... group :development, :test do ... gem 'factory_girl_rails' end ... group :development do gem 'capybara' gem 'poltergeist' gem 'launchy' gem 'database_cleaner' # 一部削除しました。 gem 'simplecov', require: false end
simplecov
は、テストカバレッジを見るgem
になるので、外しても良いです。
simplecov
を利用する上で必要な設定は引き続きで、記事にのせています。
利用する場合、html
が作成されますが、rails s
からアクセスできる場所にファイルが保存されないため、
ruby
からwebrick
を起動するスクリプトを書くと使い易くなります。
spec/rails_helper.rb
rails_helperに追加していきます。
# rails_helper.rb ... require 'simplecov' SimpleCov.start('rails') do add_filter '/vendor/' end require 'capybara/rspec' require 'cabtpara/poltergeist' Capybara.javascript_driver = :poltergeist Rspec.configure do |config| ... # sample # RSpec.configure do |c| # c.before(:each) { } # :each 全てのテストスイート中のそれぞれのexampleの前に実行される # c.before(:all) { } # :all それぞれのトップレベルのグループの最初のexampleの前に実行される # c.before(:suite) { } # :suite 全てのspecファイルがロードされたあと、最初のspecが実行される前に一度だけ実行される #end # exampleは、eachのalias # contextは、allのalias # https://github.com/DatabaseCleaner/database_cleaner#rspec-with-capybara-example config.before(:suite) do if config.use_transactional_fixtures? raise(<<-MSG) Delete line `config.use_transactional_fixtures = true` from rails_helper.rb (or set it to false) to prevent uncommitted transactions being used in JavaScript-dependent specs. During testing, the app-under-test that the browser driver connects to uses a different database connection to the database connection used by the spec. The app's database connection would not be able to access uncommitted transaction data setup over the spec's database connection. MSG end load Rails.root.join('db', 'seeds.rb') DatabaseCleaner.clean_with, :truncation, { except: %w(master_tables) } end config.before(:each) do DatabaseCleaner.strategy = :transaction end config.before(:each, type: :feature) do driver_shares_db_connection_with_specs = Capybara.current_driver == :rack_test if !driver_shares_db_connection_with_specs DatabaseCleaner.strategy = :truncation end end config.before(:each) do DatabaseCleaner.start end config.append_after(:each) do load Rails.root.join('db', 'seeds.rb') DatabaseCleaner.clean end ... config.include LoginMacro end
factories
最低限のfactoryを準備します。
# spec/factories/users.rb FactoryGirl.define do factory :user do email: 'user@example.com' password 'secret' password_confirmation 'secret' end end
spec/support/login_macro.rb
# spec/support/login_macro.rb module LoginMacro ... def sign_in(user) visit admin_sessions_new_path fill_in 'user_email', with: user.email fill_in 'user_password', with: 'secret' click_on 'Submit' end end
spec/support/shared_db_connection.rb
コメントをいただきまして、最新のdatabase_cleanerを利用する場合、不要になっています。
feature
featureテストを追加します。
# spec/features/user_login_spec.rb require 'rails_helper' Rspec.feature 'Authenticate', type: :feature do scenario 'login user', js: true do user = create(:user) sign_in(user) expect(page).to have_content user.company.name end end
追記:
scenario
に追加されているオプションjs: true
により、javascript
を動作させることができます。
テスト実行
$ bundle exec rspec spec/features/admin_login_spec.rb . Finished in 7.05 seconds (files took 2.76 seconds to load) 1 example, 0 failures Coverage report generated for RSpec to /home/user_name/application_name/coverage. 130 / 448 LOC (29.02%) covered.
Coverage...
は、simplecov
が出力するログです。
以上になります。
参考
Feature spec - Feature specs - RSpec Rails - RSpec - Relish
使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」 - Qiita
CentOS6にyumでphantomjs 2.1.1をインストール - Qiita
[Ruby] よく使うRspecのレシピ集(Rspec3.3) | Developers.IO
*1:http://rspec.info/ja/blog/2016/07/rspec-3-5-has-been-released/
*2:gemを入れることで利用することはできるが新規アプリでは非推奨
Rspec callbackのテスト(rails)
callback(before_actionなど)で利用するメソッドのテスト方法を書いていきます。
Controller
class DashBoardController < ApplicationController before_action :set_user def index # ... end private def set_user @user = User.where(id: session[:user_id]) end end
spec
今回は指定しませんが、infer_base_class_for_anonymous_controllers = false
にすると
Anonymous(匿名)クラスのデフォルト親クラスが、ApplicationController
になります。
rspec
DashBoardController#index
のコードを上書きして、callback
により呼ばれるメソッドの処理のみに集中できるように変えます。
RSpec.describe DashBoardController, type: :controller do describe 'GET #index' do # ... end describe '#set_user' do controller do def index render text: 'success' end end it '@userにユーザ情報がある時' do get :index expect(assigns[:user].present?).to be_truthy end it '@userにユーザ情報がない時' do get :index, session: { user_id: nil } expect(assigns[:user].blank?).to be_truthy end end end
anonymous controller - Controller specs - RSpec Rails - RSpec - Relish
認証機能のユニットテスト(controller)
今日は、自作のアプリでユニットテストを作っていたので、このあたりを書いていきたいと思います。
自作アプリでは認証のgemは使っていないんですが、記事のためにsorcery
を使って書いていきます。
- gemの導入手順は省いています。
準備
# Gemfile gem 'sorcery' group :development, :test do ... gem 'rspec-rails' gem 'factory_girl_rails' end group :test do gem 'capybara' gem 'database-clearer' gem 'rails-controller-testing' # render_templateを利用するため end
controller
# admin/sessions_controller.rb class Admin::SessionsController < Admin::BaseController def new end def create user = login( user_params[:email], user_params[:password], user_params[:password_confirmation] ) if user redirect_to dashboard_path, notice: 'ログインに成功しました' else redirect_to root_path, alert: '認証に失敗しました' end end ... def user_params params.require(:user).permit(User.column_names) end private :user_params end
# admin/dashboard_controller.rb class DashBoardController < Admin::BaseController before_action :require_login def index end end
models
# app/models/user.rb class User < ApplicationRecord end
spec
# spec/rails_helper.rb Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } # コメントアウトから戻す Rspec.configure do |config| ... # sorceryのwikiより config.include Sorcery::TestHelpers::Rails::Controller, type: :controller config.include Sorcery::TestHelpers::Rails::Integration, type: :feature config.include Capybara::DSL # FactoryGirlの記載を省くために、追加 config.include FactoryGirl::Syntax::Methods ... end
spec/factories/user.rb
FactoryGirl.define do factory :user do email 'gankai1104@gmail.com` password 'secret' password_confirmation 'secret' ... end end
spec/controllers/admin/sessions_controller_spec.rb
require 'rails_helper' Rspec.describe Admin::SesssionsController, type: :controller do describe 'GET #new' do it 'ログイン画面へのアクセス時' do get :new expect(:response).to render_template :new end end describe 'POST #create' do it 'ログイン成功時' do create(:user) post :create, { user: attributes_for(:user) } } expect(response).to redirect_to dashboard_path end it 'ログイン失敗時' do post :create, { user: attributes_for(:user) } } expect(:response).to render_template :new end end ... end
spec/controllers/admin/dashboard_controller.rb
RSpec.describe Admin::DashboardController, type: :controller do describe 'GET #index' do it 'ログイン後の時' do login_user(create(:user)) get :index expect(response).to render_template :new end it 'ログイン前の時' do get :index expect(response).to redirect_to login_path end end end
ここまでです。
FactoryGirlのtrait
, association
, Rspecの anonymous controller
を利用したので、次はそれを書きます。
参考
Testing Rails · NoamB/sorcery Wiki · GitHub
Cucumber + CapybaraでUserAgentを設定してテストを行う - tech-kazuhisa's blog