一から勉強させてください

最下級エンジニアが日々の学びをアウトプットしていくだけのブログです

Rubyのインスタンス変数、クラス変数、クラスインスタンス変数

Ruby のクラスには

があって何かとややこしい。。

特にクラス変数とクラスインスタンス変数の違いについてはいまいち理解しきれていなかったので、今回は頭の整理もかねてメモです。

それぞれの特徴について書いてみたいと思います。

インスタンス変数

オブジェクト固有の状態を保持するための変数。@をつける。

class Foo
  attr_accessor :instance_foo

  def initialize
    @instance_foo = 'instance_foo!'
  end
end

Foo.new.instance_foo #=> instance_foo!

外部からアクセスするにはFoo#nameFoo#name=を定義する必要があるが、attr_accessorを使えば自動的に定義してくれる。

もしくはinstance_variable_getとかinstance_variable_setとか使う。

クラス変数

クラスとそのサブクラスの定義中、クラスメソッド、インスタンスメソッドで共有できる変数。

@@をつける。

class ParentFoo
  @@class_foo = 'class_foo!'

  def self.say
    @@class_foo
  end
end

class ChildFoo < ParentFoo
  def say
    @@class_foo
  end
end

ParentFoo.say #=> class_foo!
ChildFoo.say #=> class_foo!
ChildFoo.new.say #=> class_foo!

今回はsayメソッドを用意してるけど、class_variable_getとかclass_variable_setとかで外部からアクセスもできる。

Rails を使ってるならcattr_accessorattr_accessor的にクラス変数を外部に公開したりもできるはず。

クラスインスタンス変数

Ruby ではすべてがオブジェクトなので、クラスもClassクラスのオブジェクト。つまりクラスオブジェクト自体もインスタンス変数を持つことができる。これがクラスインスタンス変数。

クラス定義式、クラスメソッドなど、selfがクラスを指すコンテキスト内で定義可能。

class ParentFoo
  @class_instance_foo = 'class_instance_foo!'

  def say
    @class_instance_foo
  end

  def self.say
    @class_instance_foo
  end
end

class ChildFoo < ParentFoo ; end


ParentFoo.say #=> 'class_instance_foo!'
ParentFoo.new.say #=> nil
ChildFoo.say #=> nil
ChildFoo.new.say #=> nil

クラス変数との明確な違いは以下の2点かなと思われる。

まとめ

それぞれの特徴を把握して、明確な意図を持って使い分けていきたいですね。

今回は以下の書籍を参考にしました。

Railsでfakerのバージョンを上げたらspecが壊滅したけど設定で乗り越えたメモ

最近、Rails プロジェクト内で使用しているfakerのバージョンを最新(v1.4.3)に上げたら、今まで通っていた spec が壊滅したので、その際のメモです。

状況把握

こちらの記事に記述されているものと全く同じ状況に陥ったと思われます。

プロジェクト内の locale はデフォルトでI18n.locale = :jaにしていたので faker の locale も:jaにセットされ、その結果Faker::Internet.emailFaker::Internet.urlが壊れたのだと思います。(前はv1.1.2ぐらいだったのですが、faker の locale 設定まわり変わったんですかね。。)

対策

プロジェクト内でfactory_girlも使用していたので、config/initializers/factory_girl.rbを用意して

if defined? Faker
  Faker::Config.locale = :en
end

のようにしました。

わざわざfactory_girl.rbに設定している理由はfactory_girl

$ rails c
[1] pry(main)> FactoryGirl.create :user
以下略

のように develop 環境でも使うことを想定しているからです。

spec/factories/users.rbなどの factory の中で

FactoryGirl.define do
  factory :user do
    name { Faker::Name.name }
    email { Faker::Internet.url }
  end
end

みたいにガンガン faker 使ってたので、rails cで毎回、Faker::Config.locale = :enを呼ばないといけないのもダルいし。。

もし test 環境だけでいいやーって感じなら、spec/support/faker.rbなどに

RSpec.configure do |config|
  config.before(:suite) { Faker::Config.locale = :en }
end

とかでもいいんじゃないかと思います。

まとめ

テスト落ちまくって心臓に悪かったけど、おかげでバージョンの違いで使えなかった faker のメソッドが使えるようになりました。

同様の悩みを抱えている方のお役に立てれば幸いです。

参考

git-new-workdirを導入してGitのブランチを複数同時に扱えるようにした

今、僕の仕事環境では「エンジニアそれぞれが作業用ブランチを切って開発を進め、Github に pull-req を出してレビューもらって、それが通ったらマージされる」、といったよくありがちなフローで開発を進めています。

で、今までは自分が開発をしている最中に同僚から pull-req が飛んできてレビューをしなくてはならなくなったとき、自分の作業を一旦git stashなり、git commitなりしてキリがいい状態にしてから、git checkoutしてレビュー、、のようなことをやっていました。

ですが、最近同僚にgit-new-workdirなるものを使えばそんなだるいことしなくていいよーって教えてもらったのでさっそく導入!!今回はその導入メモです。

git-new-workdir コマンドを使えるようにする

僕はhomebrewで git を導入してるのですが、git-new-workdir自体はすでに/usr/local/share/git-core/contrib/workdir/git-new-workdirにありました。

ただ参考記事を見る感じだと、コマンドはパスを通すなり、シンボリックリンクを貼るなりしないと使えないようなので今回は

$ ln -s /usr/local/share/git-core/contrib/workdir/git-new-workdir /usr/local/bin/git-new-workdir

して使えるようにしました。これで

$ git-new-workdir

usage: /usr/local/share/git-core/contrib/workdir/git-new-workdir <repository> <new_workdir> [<branch>]

って出るようになりました!

ちなみにgit-new-workdirがない人はこことかから落としたりすれば大丈夫そうです。

実際に使ってみる

$ git checkout git@github.com:project_name.git(てきとーなproject)

$ git-new-workdir project_name for_develop
$ git-new-workdir project_name for_review

これで開発用のブランチで開発をしつつ、pull-req レビュー用のブランチでレビューできる環境ができました!

$ tmux new -s for_develop
$ tmux new -s for_review

みたいにして tmux で切り替えて管理できるようにするといいかもですね!

ちなみに.gitignoreに指定されているディレクトリ、ファイルはgit-new-workdirでつくったディレクトリには反映されていないのでそこは注意。

まとめ

git-new-workdir便利!!他にもここで紹介されているようにgit-contribには便利なツールがいっぱいあるみたいなので、また試していきたいです。

参考

bundle openでgemのディレクトリを手軽にエディタから確認する

gem のソースって今まで基本 Github から確認していたのですが、実はエディタから手軽に確認できる方法がありました。地味に知らなかったです。

こちらの記事を参考にさせていただきました。

エディタの設定

環境変数EDITORにあらかじめどのエディタを開くのか設定しておく必要がある。自分の場合は vim を使うので、.zshrcに以下のように書いてあります。

export EDITOR=/usr/local/bin/vim

gem のディレクトリを開く

あとはbundlerさえ入っていれば準備 OK.

例えばdeviseを開く時は

bundle open devise

で devise のディレクトリが vim で開ける。

まとめ

最近 Rails の開発が続く日々ですが、こういう地味に知らないことがまだまだ沢山あるので習得していきたいです。

参考

Rails4 + devise + paranoiaで論理削除を考慮したユーザーバリデーションを実現する

Rails でユーザー登録、認証機能を実装する際、deviseを使用することはわりと一般的かと思います。

またユーザーの論理削除を実現するための gem としてはparanoiaが有名かと思います。(Rails3 時代によく使われていた acts_as_paranoid をよりシンプルにしたやつ)

どちらも素晴らしい gem なのですがこれらを同時に使用した際、以下のような問題が発生しました。

  1. devise のvalidatableを使用してUserモデルの validation を実装 (email, password 関連の validation が追加される)。
  2. paranoiaacts_as_paranoidメソッドを呼んで、ユーザーの論理削除をできるようにする。
  3. email: hoge@hoge.comのユーザー hogeemail: fuga@fuga.comのユーザー fuga を用意する。
  4. fuga.destroyで fuga を論理削除。
  5. hoge.update_attributes(email: 'fuga@fuga.com')hoge の email を更新しようとするとユニーク制約で validation エラー発生。

こんな感じで削除済のユーザーと同じ email を登録しようとするとユニーク制約にひっかかってしまって困りました。

今回はこれを通常のユーザーの email ユニーク制約は残しつつ、削除済みユーザーはその対象外となるよう修正していきたいと思います。

とりあえず validatable を外す

まずは deviseのvalidatableを外して、必要なコードを本家からコピーしてくる。

base.class_eval do
  validates_presence_of   :email, if: :email_required?
  #validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
  validates_format_of     :email, with: email_regexp, allow_blank: true, if: :email_changed?

  validates_presence_of     :password, if: :password_required?
  validates_confirmation_of :password, if: :password_required?
  validates_length_of       :password, within: password_length, allow_blank: true
end

必要そうなのはこの辺り。ただvalidates_uniqueness_ofはこのままだとダメなので一旦外す。

DB の index を修正する

devise 導入時の migration で

class DeviseCreateUsers < ActiveRecord::Migration
 中略
  add_index :users, :email, unique: true
end

のようにしていると思うので、その前提で。

これだとdeleted_atに関係なくユニーク制約がついてしまっているので、これを修正するための migration ファイルを作成する。

class UpdateIndexUsersOnEmail < ActiveRecord::Migration
  def up
    remove_index :users, :email
    execute 'CREATE UNIQUE INDEX unique_index_on_users_email ON users(email) WHERE deleted_at IS NULL'
  end

  def down
    execute 'DROP INDEX unique_index_on_users_email'
    add_index :users, :email, unique: true
  end
end

こんな感じでdeleted_atnullの場合のみユニークとする。これで DB 側の修正は OK のはず。

paranoia_uniqueness_validator を使う

次に Model 側の修正。 paranoia_uniqueness_validatorという gem を使用する。これはacts_as_paranoid

class User < ActiveRecord::Base
  validates_as_paranoid
  validates_uniqueness_of_without_deleted :email
end

のようにしていた機能がparanoiaではparanoia_uniqueness_validatorという gem に切り離されたので、これを別途追加する必要があるといった感じ。

こちらの記事とか参考にさせていただきました。

んで、paranoia_uniqueness_validatorでは以下のように validation を追加する。

class User < ActiveRecord::Base
  validates :email, uniqueness_without_deleted: true, allow_blank: true, if: :email_changed?
end

これで期待通りの挙動になるよう修正できた、、はず。

まとめ

gem は便利だけど、思い通りにいかない時は自力で頑張る必要があるということですね。。世知辛い。 もしなにかおかしな所がございましたら、コメント等頂けると嬉しいです!!

参考