kikeda1104's blog

備忘録・技術に関することを書いています。(webエンジニア)

Rails 5.0からRails 5.1へのアップデート(1/?)

こんにちは。時間の合間に作成しているサービスのRailsを5.0から5.1へアップデートしていまして その内容を書いています。rails upgrade guideが、まだ作成中で、issueを読んで対応していました。

テスト, jsの移行は、まだ終わっていなくて順次移行中です。

環境

Gemfile

Gemのversionをあげます。(環境によって異なります)

# Gemfile
gem 'rails', '5.1.0' # Use sqlite3 as the database for Active Record
gem 'puma', '~> 3.7'
gem 'webpacker'
gem 'coffee-rails', '~> 4.2'
  
group :development, :test do
 # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '~> 2.13.0'
  gem 'selenium-webdriver'
end

group :development do
  # Access an IRB console on exception pages or by using <%= console %> in views
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

参考: https://github.com/kikeda1104/9syoku/blob/master/Gemfile

bundle install or bundle update

./bin/rails app:update

rails 5.0でも使われていた、binファイルや設定ファイルを更新してくれる./bin/rails app:updateがありますのでこれを利用します。 config、routeが上書きされるので、差分をとって編集しましょう。

./bin/rails app:update

secret.ymlの暗号化

./bin/rails secrets:setup

f:id:kikeda1104:20170504161357p:plain

secrets.yml.keyが作成され、gitignoreに追加されます。 secrets.yml.encを編集したい場合は、`./bin/rails secrets:edit'です。

f:id:kikeda1104:20170504161633p:plain 上のメッセージが出たら、EDITOR=vi bin/rails secrets:edit EDITORは、好みで変えてください。

productionのsecret_key_baseをこちらに設定して保存します。

webpacker

これは、新規だった場合に、rails new rail_app --webpack –webpackオプションを指定すると、自動でwebpacker gemがGemfile bundle installによりgemがインストールされます。

./bin/rails -T 一部省略

f:id:kikeda1104:20170504162456p:plain

namespaceにwebpackerとついているのは、webpacker gemが導入されることにより追加されるタスクです。 vueを含めてみます。

./bin/rails webpacker:install:vue

f:id:kikeda1104:20170504163358p:plain

migrationファイル

ActiveRecord::Migration => ActiveRecord::Migration[5.0 or 5.1]に修正

./bin/rails db:drop RAILS_ENV=production && ./bin/rails db:create RAILS_ENV=production && ./bin/rails db:migrate RAILS_ENV=production

RAILS_ENV=production ./bin/rails assets:precompile

puma起動

RAILS_ENV=production ./bin/rails s

まとめ

気が向いたら、細かい改善点を更新していきます。

近況

前職の会社を辞めてから、フリーランス(個人事業主)として働き始めて コードを書く機会が増えてきたので、知見を少しずつブログに書きためようかと思います。 友人の紹介による仕事ですが、色々と新しい経験を積める環境にあって感謝しています。

参考

A Guide for Upgrading Ruby on Rails — Ruby on Rails Guides

rails/5_1_release_notes.md at 39a2e1465e15d1da56ba1f4ed14fd38740bd86d4 · rails/rails · GitHub

circle.ymlの設定(bunlderのみ)

github上で、ソースを管理しており、プロジェクトでCIを利用しているが、Circle CIbunlderversionlocalと一致しない(lockfile)ことで、warningのメッセージが上がっていたので、circle.ymlを作成しました。

circle.yml

rootディレクトリに、circle.ymlを追加する。

# circle.yml
machine:
  ruby:
    version:
      2.3.3
  
dependencies:
  pre:
    - gem install bundler --pre

まずは、最小限の設定のみです。

参考

Sample circle.yml file - CircleCI

FormObject実装(2回目)

前回からの引き続き。viewとcontrollerの実装を進めていきます。

前回まで

FormObject実装(1回目) - kikeda1104's blog

controllerの実装

# controller/sample_forms_controller.rb

class SampleFormsController < ApplicationController
  def new
    @offer_form = OfferForm.new
  end
  
  def create
    @offer_form = OfferForm.new(job_offer_form_params)

    if @offer_form.save
      redirect_to :create, notice: '保存に成功しました'
    else
      render :new
    end
  end

  private
  
  def job_offer_form_params
    params.require(:job_offer_form).permit(JobOfferForm.attributes.keys)
  end
end

viewsの実装

続いて、viewsのフォームを作成します。

# views/sample_forms/new.html.haml
# ... 省略 : bootstrap利用しています。cssの定義については省略します

- form_for(@offer_form, polymorpic_path(@offer_form.job_offer)) do |f|
  # polymorpic_path([:admin, @offer_form.job_offer])も可能
  %fieldset
    %legend フォーム
    .form-group
      = f.label :title, class: 'col-md-2 control-label'
        col-md-10
        = f.text_field :title, placeholder: '例: ', class: 'form-control'
    .form-group
      = f.label :description, class: 'col-md-2 control-lable'
      .col-md-10
        = f.text_area :description, class: 'form-control'

    .form-acitons
      .row
        .col-md-12
          %button.btn.btn-default{ type: 'submit' }
            戻る
          %button.btn.btn-default{ type: 'submit' }
            %i.fa.fa-save
              確定    

ここまでです。次は、1回目の記事のサンプルコードを更新してから、form_objectsの実装をより進めていきます。

参考

http://blog.enogineer.com/2014/12/02/rails-form-object/

ActionDispatch::Routing::PolymorphicRoutes

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

virtusREADMEに記載されていて、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

Railsで導入してよかったデザインパターンと各クラスの役割について - masato_hiのブログ

ActiveRecord::Locking::Optimistic

postgre sql サーバの起動方法(Cent7.x)

自宅のrailsアプリでは、posgreを使っていてサービス起動/停止する際にコマンドを忘れないためのメモです。

環境

起動方法

インストール方法により、サービス名も変わります。

# systemctl start postgresql

株式会社リューノスを退職しました。

SNSに投稿するには長文になっていたので、 重複しますが報告と退職の理由について書いていきます。

正確には記事の投稿している日は、まだ在籍中で、1月10日で退職します。

報告

最終日なのに15時位まで、コードを書き、マージまでして、17時過ぎても質問され続けていて疲れもしましたが、まあ、最終日も貢献することができ嬉しく思っています。

挨拶の後には、お花とプレゼントまで頂いて、退職することを感じつつ、嬉しい一日になりました。 (帰り際に牛乳をこぼすという不手際がなければ。。)

今の会社では、2年間、在籍することになり、在籍中に若手の教育と開発全般で、活躍できたと自負しています。関係者の皆さまありがとうございました。

今後は、フリーで働きながら、エンジニアのキャリアを積んでいきます。

退職ついて

実は退職の理由に、後ろめたい理由があるんじゃないかと疑われる方がいました。その方は、心配してくれて聞いてくれたんだと思っています(優しい方なので)

上司に退職の意思を伝える際に、きみは影響力があるからと言われて (正直そんな自覚はなかったのですが) 開発メンバーに対して積極的は発言や行動(もくもく会、プライベートのイベント)は控えていました。 できるだけ、まわりや若手の意見を優先して採用して欲しかったですし、そうすることで 退職するインパクトが下がると思っていたからです。

上に書いた事をやっていることと、社内で親しい方々に、私からでなく、所属している部長から退職1ヶ月前に私が退職する旨を発表する形になってしまったことが原因だと思っています。 私からはまだ言わないで欲しいと言われていることもあり、そちらを優先していましたが。別の方から退職を伝えるのではなく、私から直接伝えておけば、印象が違ったなと反省しています。

実際に退職理由はこれですと一言で言えなくなっていまして。 上司に退職する際に伝えた言葉は、マネジメントと開発を両立するのは、難しいので、開発に専念したい旨を伝えていました。それだけではなく、お金の面、経験の面、時間の面で より自分にとってより良い環境や条件で働きたいと願望があり、そちらを優先した次第です。

エンジニアとして、まだまだ技術力も知識を伸ばしていきたいです。 承認欲求も強くあります。若手の教育係としての仕事もしていましたが、若手の成長を優先してばかりいれるほど大人でもなかったので 自分にとって成長できる幅が大きい、自分にとって好条件かつ効率の良い生き方を優先したいというのが今回の退職理由です。

以上です。だらだら書きましたが、改めて関係者の方々、暖く見送っていただきありがとうございました。

仕事のご依頼は下記のメールアドレスにお願いいたします。

gankai1104@gmail.com

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からconnectiondisconnectで切断してから 動作するようになりました。

テストは恩恵も大きいし、テストを定常的に書くのも重要ですね。

参考

ActiveRecord::ConnectionAdapters::ConnectionHandler

https://github.com/rails/rails/blob/473473d7f73a43b8d1e4e604327998c5250dff3c/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L891

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"

以上になります。

参考

名前空間を取り除いたクラス名を取得する - Qiita

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から呼び出しています。"

参考

const_get (Module) - Rubyリファレンス

constantize (ActiveSupport::Inflector) - APIdock