他人のブログを読んだり、勉強会等のLTを聴いて感じること

こんにちは。

他人のブログを読んでいたり、勉強会や某ABCに参加してLTとか聴いていて感じたことを書きます。

1. お前のブログはオリジナリティがあるのか?
既知の内容なのに、そういった内容をブログに書いている人をよく見かけます。参考サイトのリンクを張っていればまだ許せますが、そういったものがないブログもあります。また、本に載っていることを書いているブログも見かけます。例えば、最近見かけたのでは、iOSでのUITableViewの使い方とか。これはひどいと思いました。お前が考案したAPIなのか?w

2. お前のブログは、どこかのサイトのページの翻訳ではないか?
Androidに関して、キーワードで日本語サイトをググると、Googleのドキュメントリファレンスのページをほぼ翻訳したページを見かけることがあります。ブログを書いている本人は、日本人のために翻訳してあげているんだとか言いそうですが、ブログ中に出てくるサンプルコードも、元のドキュメントリファレンスのページと同じことが多々あります。真っ先に日本語に訳して、日本で最先端を行っているとでも思っているのでしょうか。技術的にとんがりたいなら、自分で作ったサンプルアプリのコードでも載せて欲しいですね。また、最近見かけたブログでは、英語圏のブログのほぼ翻訳で、コード断片もコピペで、元のコードの、作者独自定義の定数を、定義もせずに(定義を引用もせずに)使用している例がありました。元の英語圏のブログでは、githubにサンプルコードがあったので、定数の定義を見ることができましたが、翻訳サイトでは、githubへのリンクは張られていませんでした。

3. 勉強会や某ABCのLTで笑いを取ることしかしない
勉強会や某ABCに参加してLTを聞いていると、次のようなことがあります。

a. アニメの画像をスライドに入れる。
面白くないですし、あなたは、そのアニメの作者に、画像の使用許諾を取ったのですか?

b. 奇声を上げて笑いをとろうとする
勉強会では見かけないですが、某ABCのLTではよく見かけます。ちっとも面白くないです。ビジネス、技術アピールで注目を浴びるようにして下さい。

c. その他
某ABCのLTで、当時流行っていた某ゲームアプリを、某テストツールで動かすことをやったLTがありました。はっきり言って、技術的に面白くないです。自分のプロダクトをテストツールで動かすのならいいですが、そうでないなら、単に笑いを取るだけの内容ですね。

RailsのオリジナルRakeタスクのテスト rspec 3.0.x編

モリジュン(@zyunnosuke)氏が、RailsでオリジナルRakeタスク作成からRSpecテストまでの記事において、Rakeタスクの作り方、RSpecによるテストの仕方を解説してます。その記事では、RSpecは2.x系で、テストコードで、shouldを使っています。今回、RSpec 3.0.xで、expectを使いたいので、挑戦してみました。

1. プロジェクトの作成

$ rails new rake-test-sample
$ cd rake-test-sample
$ bundle update
$ bundle install

2. モデルの作成

rakeのタスクの内容をクラスに抽出したものとして、モデル ReportGeneratorを作成する。

$ rails generate model report_generator
$ bundle exec db:migrate

モデルクラス ReportGeneratorには、次の内容を記述。

class ReportGenerator < ActiveRecord::Base
  def self.generate
    puts "ReportGenerator.generate called."
  end
end
&#91;/ruby&#93;

クラスメソッドで、ダミーな処理を記述している。

3. Rakeタスクファイルの作成

&#91;bash&#93;
$ rails generate task reports
&#91;/bash&#93;

生成されたlib/tasks/repots.rakeに次の内容を記述。元記事のまんまです。

&#91;ruby&#93;
namespace :reports do
  # descの記述は必須
  desc "Generate report"

  # :environmentはモデルにアクセスするのに必須
  task :generate => :environment do
    # 処理を記述
    ReportGenerator.generate
  end
end

タスクの確認は

$ rake -vT |grep reports
rake reports:generate                   # Generate report

タスクの実行は

$ rake reports:generate

4. Gemfileにrspecとrspec-its の追加

元記事では、specファイルで、itsを使っていますが、itsはrspec 3.0.xではサポートされなくなりました。RSpec の入門とその一歩先へ、第3イテレーション ~RSpec 3バージョン~によると、gem rspec-itsを導入すると、itsが使えるとのことです。そこで、元記事に書かれていたrake_shared_context のgemと共に、Gemfileに以下を追加。

group :development, :test do
  gem 'rake_shared_context'
  gem 'rspec-rails', '3.0.0'
  gem 'rspec-its', '1.0.1'
end
$ bundle update
$ bundle install

を実行。

5. RakeタスクのRSpecの作成

rspecによるテストが出来るように次のコマンドを実行します。

$ bundle exec rails generate rspec:install

元記事では、spec/lib/tasks/reports_rake_spec.rbファイルは、次の内容でした。

require 'spec_helper'

describe 'reports:generate' do
  include_context 'rake'

  its(:prerequisites) { should include('environment') }

  it 'generates the report' do
    ReportGenerator.should_receive(:generate)
    subject.invoke
  end
end

これは、rspec 2.x向けの記述なので、次のように書き換えます。

require 'spec_helper'

describe 'reports:generate' do
  include_context 'rake'

  its(:prerequisites) { is_expected.to include('environment') }

  it 'generate the report' do
    expect(ReportGenerator).to receive(:generate)
    subject.invoke
  end
end

記述の仕方は、RSpec の入門とその一歩先へ、第3イテレーション ~RSpec 3バージョン~ や、RSpec 3の重要な変更を参考にしました。

6. Rakeテストの実行

$ bundle exec rspec spec/lib/tasks/reports_rake_spec.rb

warningが多いので、プロジェクトディレクトリの .rspecファイルで、–warningsの行を削除しておきます。

`find_and_eval_shared’: Could not find shared context “rake” (ArgumentError)

というエラーが出ますが、spec/spec_helper.rbの冒頭に次の記述をします。

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'

この記述は、パーフェクトRuby on Railsを参考にしました。

再度、テストを実行。

$ bundle exec rspec spec/lib/tasks/reports_rake_spec.rb
...
Finished in 0.01324 seconds (files took 1.22 seconds to load)
2 examples, 0 failures

テストが通りました。

7. ソースの公開場所

andropenguin/rake-test-sample

8. Special Thanks

Rails3レシピブック”モックやスタブを使う”のコードを書き換えてみた

Rails3レシピブックの”モックやスタブを使う”のテストコードは、rspec-mocksのバージョンが古く、バージョン 2.14.6では書き換える必要があります。Rubyにおけるモック、スタブについては自分は初心者なので、試行錯誤して、書き換えてみました。最後のテストの部分は、本によると、失敗するはずなのですが、なぜか通ってしまいます。この点は理解不足なので、保留にします。コードは、GitHubにおいてあります。

https://github.com/andropenguin/samplecode-stub-mock

Rails 4.0.0、RSpec、Capybara使用プロジェクト向けGemfile

Ruby on Rails チュートリアルの、Ruby on Rails 4向けの記事を読んでいます。記事に書いてあるGemfileだと、依存関係がおかしく、buundle installでエラーになります。うまくいくGemfileを示します。

source ‘https://rubygems.org’
ruby ‘2.0.0’

gem ‘rails’, ‘4.0.0’
gem ‘bootstrap-sass’, ‘2.3.2.0’
gem ‘bcrypt-ruby’, ‘3.0.1’
gem ‘faker’, ‘1.1.2’
gem ‘will_paginate’, ‘3.0.4’
gem ‘bootstrap-will_paginate’, ‘0.0.9’

group :development, :test do
gem ‘sqlite3’, ‘1.3.7’
gem ‘rspec-rails’, ‘2.13.1’
# The following optional lines are part of the advanced setup.
# gem ‘guard-rspec’, ‘2.5.0’
# gem ‘spork-rails’, github: ‘sporkrb/spork-rails’
# gem ‘guard-spork’, ‘1.5.0’
# gem ‘childprocess’, ‘0.3.6’
end

group :test do
gem ‘selenium-webdriver’, ‘2.0.0’
gem ‘capybara’, ‘2.1.0’
gem ‘factory_girl_rails’, ‘4.2.0’
gem ‘cucumber-rails’, ‘1.3.0’, :require => false
gem ‘database_cleaner’, github: ‘bmabey/database_cleaner’

# Uncomment this line on OS X.
# gem ‘growl’, ‘1.0.3’

# Uncomment these lines on Linux.
# gem ‘libnotify’, ‘0.8.0’

# Uncomment these lines on Windows.
# gem ‘rb-notifu’, ‘0.0.4’
# gem ‘win32console’, ‘1.3.2’
end

#gem ‘sass-rails’, ‘4.0.2’
gem ‘sass-rails’, ‘4.0.0’
gem ‘railties’, ‘4.0.0’
gem ‘uglifier’, ‘2.1.1’
gem ‘coffee-rails’, ‘4.0.0’
gem ‘jquery-rails’, ‘2.2.1’
gem ‘turbolinks’, ‘1.1.1’
gem ‘jbuilder’, ‘1.0.2’

group :doc do
gem ‘sdoc’, ‘0.3.20’, require: false
end

group :production do
gem ‘pg’, ‘0.15.1’
gem ‘rails_12factor’, ‘0.0.2’
end

gem ‘protected_attributes’

gem ‘execjs’
gem ‘therubyracer’

基礎Ruby on Rails改訂新版のサンプルアプリでFakerを使ってみた

最近、Everyday Rails – RSpecによるRailsテスト入門という電子書籍を読んで、RSpecでテストコードを書くことに取り組んでいます。練習として、基礎Ruby on Rails改訂新板のサンプルアプリにRSpecでテストコードを書きました。Memberクラスは、次のようなバリデーションがあります。

class Member < ActiveRecord::Base
  include EmailAddressChecker

  attr_accessible :number, :name, :full_name, :email, :birthday, :gender, :administrator
  validates :number, presence: true,
            numericality:  { only_integer: true,
            greater_than: 0, less_than: 100, allow_blank: true },
            uniqueness: true
  validates :name, presence: true,
            format: { with: /\A&#91;A-Za-z&#93;\w*\z/, allow_blank: true,
                      message: :invalid_member_name },
            length:  { minimum:  2, maximum:  20, allow_blank: true },
            uniqueness: { case_sensitive: false }
  validates :full_name, length: { maximum:  20 }
  validate :check_email
  validates :gender, inclusion: { in: 0..1 }

  private
    def check_email
      if email.present?
        errors.add(:email, :invalid) unless well_formed_as_email_address(email)
      end
    end

    class << self
      def search(query)
        rel = order("number")
        if query.present?
          rel = rel.where("name LIKE ? OR full_name LIKE ?",
                        "%#{query}%", "%#{query}%")
        end
        rel
      end
    end
end
&#91;/ruby&#93;

FactoryGirl gemとFaker gemを使って、テストデータを生成しました。

&#91;ruby&#93;
require 'faker'

FactoryGirl.define do
  factory :member do
    sequence(:number) { |n| n unless n > 99 }
    name {
      name = Faker::Name.first_name.slice!(0, 19).capitalize
      name if name.length >= 2
    }
    full_name { Faker::Name.name.slice!(0, 19) }
    email {Faker::Internet.email }
    birthday "1981-12-01"
    gender { Faker::Number.number(1).to_i % 2 }
    administrator false

    factory :invalid_member do
      name nil
    end
  end
end

この場合、テストコードを書くと、英語環境ではテストは失敗しません。ところが、i18nで国際化して、日本語環境でテストすると、fakerは国際化に対応しているようで、nameが日本語になったり、emailが不正な表現になって失敗するようになります。バリデーションでは、nameは英字で始まり、英数字からならないとダメだからです。しかし、full_nameについては、日本語のデータが生成されるようにしたいです。fakerのサイトページを見ると、Faker::Config.locale でロケールをスイッチできるようです。そこで、次のファイルを使ってテストデータを生成するようにしました。

require 'faker'

FactoryGirl.define do
  factory :member do
    sequence(:number) { |n| n unless n > 99 }
    name {
      Faker::Config.locale = 'en-us'
      name = Faker::Name.first_name.slice!(0, 19).capitalize
      name if name.length >= 2
    }
    full_name {
      Faker::Config.locale = 'ja'
      Faker::Name.name.slice!(0, 19)
    }
    email {
      Faker::Config.locale = 'en-us'
      Faker::Internet.email
    }
    birthday "1981-12-01"
    gender { Faker::Number.number(1).to_i % 2 }
    administrator false

    factory :invalid_member do
      name nil
    end
  end
end

Faker::Config.locale でロケールをスイッチすると、その後は設定したロケールのままになると期待したのですが、そのようにはならないようで、各フィールドごとにロケールをスイッチしないとダメでした。このテストデータを使用すると、テストが通るようになりました。

OmniAuthを使ってtwitterで認証をする – part2

以前、「OminiAuthを使ってtwitterで認証をする」の記事を書いたのですが、理解が足りないようで、最近、twitterでのOmniAuthを使った実装をやっているんですが、うまくいっていません。そこで、外国のサイトで有名なrailscastsにあるドキュメント #241 Simple OmniAuth にあたってみました。

このドキュメントでは、はじめにブログシステムが与えれているものと想定して、omniauthを使って認証の実装をやっています。ソースコードをダウンロードできるので、ダウンロードして、ソースコードを見ながら、omniauthを使って認証の実装をする前のブログシステムを再現しました。そして、ドキュメントに従って、認証を実装しました。

いくつか、サンプルで変更した点があるので列挙します。

  • Userクラスでcreate_with_omniauthメソッドを実装するとき、request.env[oamniauth.auth]が、user_infoではなくinfo属性を持っていることを考慮する必要がある。
  • ドキュメントでは、ヘルパーメソッド current_userをapp/hcontrollers/application_conroller.rbで定義しているが、app/helpers/sessions_helper.rbで定義して、それを前者のファイルでincludeした。
  • ブログシステムに記事がない状態で、twitterでアプリ認証後遷移する画面でtrueの単語が表示されてしまうが、原因を探っていない。:-)

ソースコードを

revised-241-simple-omniauth

に公開します。

#250 Authentication from Scratchの改良

Ruby on Railsで、パスワードを非可逆暗号化する場合、has_secure_passwordを使うと便利です。しかし、私は、twilogサービスのように、メールアドレスを入力しない場合、暗号化したバスワードをシステムに保存しないようなRailsアプリを作りたく思っています。has_secure_passwordは、Rails 4では、validationsというオプションを持っていて、値falseを渡すと、has_secure_passwordがスキップされます。しかし、Rails 3.2では、そのようなオプションがなく、スキップが可能ではありません。一つの回避策として、stackoverflowに、How to skip has_secure_password validationsというスレッドがあり、それを試してみたのですが、コードが足りないようで、動作しませんでした。さらに、ググった所、RailsCastに、#250 Authentication from Scratchで、スクラッチから認証を実装しているサンプルを見つけました。

#250 Authentication from Scratchのサンプルを、Rails 3.2で動かそうとすると、問題にぶつかりました。サンプルでは、passwordにattr_accessorを使っていますが、これは、Rails 3.2では使用を推奨されていません。かわりに、attr_accessible を使用すべきですが、それが定義された属性は、データベーステーブルのカラムにならなければいけません(後記、ここの記述は間違いかもしれません。ググっていて、そういう情報に触れたのですが、そのurlを失念しました。また、カラムにならなくても、問題無いようです)。平文のpasswordやpassword_confirmationは、データベースに保存すべきではないので、attr_accessibleを使用できないということになります。そこで、Usersクラスでは、emailのみ、attr_accessibleを使用し、passwordとpassword_confirmationに対しては、アクセサをあらわに書きました。


  def password
    @password
  end

  def password=(password_str)
    @password = password_str
  end

  def password_confirmation
    @password_confirmation
  end

  def password_confirmation=(password_confirmation_str)
    @password_confirmation = password_confirmation_str
  end

そして、メールアドレス、パスワード入力フォームから呼ばれるcontroller、UsersControllerクラスで、


  def create
    email = params[:user][:email]
    password = params[:user][:password]
    password_confrimation = params[:user][:password_confirmation]
    @user = User.new(:email => email)
    @user.password = password
    @user.password_confirmation = password_confrimation
    if @user.save
      redirect_to root_url, notice: "signed up!"
    else
      render "new"
    end
  end

のように書き、Userクラスを生成するとき、emailの値のみを渡すようにしました。このようにすることで、パスワードが平文でデータベースに保存されることはなくなりました。

改良したサンプルを、GitHubに置きました。 andropenguin/auth_from_scratch

あとは、メールアドレス、パスワードの入力がない場合、パスワードの保存を行わず、入力がある場合、暗号化したパスワードをシステムに保存するようにしたいですが、それは多分、そんなに難しくはないでしょう。

OmniAuthを使ってtwitterで認証する

OmniAuthを使ってtwitterで認証する

認証、ツイート機能実装

ログイン、ログアウトはわりと、すぐにできたのですが、ツイートできるようになるのに時間がかかりました。Ruby on Railsの認証 – OmniAuthでtwitter認証 + 簡単なRSpec にあるGitHub omniauthTwitterSample を参考にしました。
ソースコードを

omniauthTwitterSample2

に公開します。

参考
1. Ruby on Railsの認証 – OmniAuthでtwitter認証 + 簡単なRSpec

2. (メモ) Rails+OmniAuthによるTwitterログイン

3. omniauth-twitter

備考
2の記事で、ツイートするupdateメソッドのレシーバーを、Twitter::Client.newで作っているが、それだと、updateがundefinedメソッドになってしまって、ツイートできない。The Twitter Ruby Gem によると、レシーバーを

client = Twitter::REST::Client.new do |config|
config.consumer_key = “YOUR_CONSUMER_KEY”
config.consumer_secret = “YOUR_CONSUMER_SECRET”
config.access_token = “YOUR_ACCESS_TOKEN”
config.access_token_secret = “YOUR_ACCESS_SECRET”
end

としている。このclientを使うと、client.updateでツイートできる。

マーチン・ファウラー著リファクタリングの第1章のテストコードを作った

マーチン・ファウラー著のリファクタリングを読んでいるんだけど、第1章のサンプルコードに対するテストコードを作成したので公開する。
この本では、リファクタリングの前に、リファクタリング対象コードに対するテストコードを書けと書いてあるのに、ユニットテストの紹介は第4章で手短にしか説明していない。また、第1章のビデオレンタルシステムのサンプルコードのリファクタリングでは、テストコードが掲載されていなくて、テストコードなしでリファクタリングするのは怖いし、できない。そこで、第1章のサンプルコードに対するテストファイルを作成した。テスト項目をグループ分けしていなかったり、同じような記述をメソッド抽出していないけど、テストコードだからいいかと思う。テストコードはGitHubに上げたので、使いたい人がいたら、どうぞ。ただし、なるべくテスト対象コードを本の記述の通りに入力したつもりだが、ascii、html両フォーマットでのフッター行の期待する文字列が正しくなく、テストコードが動作しない場合があるかもしれない。その場合は、修正するので、Pull Requestを送って欲しい。なお、JUnitは4を使用し、Eclipseでmavenプロジェクトを作成し、サンプルコードを写経、テストコードを作成、テスト、リファクタリングを行った。pom.xmlファイルも同梱した。

https://github.com/andropenguin/testfiles_refactoring_chap01.git

リファクタリングRubyエディションの第1章のサンプルに対するテストコードは、以前、リファクタリングRubyエディションの第1章のテストコードを作ったで記事を書いた。

リファクタリングRubyエディション 6.17のテストコード

今、リファクタリングRubyエディションを読んでいます。
テストコードが載っていないし、コードを補わないと動かなかったり、擬似コードで動かないコードが載っていたりして、ストレスマッハです。
6.17 動的メソッド定義(Dynamic Method Definition)のテストコードが書けたので、記事にします。

リファクタリング対象コードは、本では

	def failure
		self.state = :failure
	end

	def error
		self.state = :error		
	end

	def success
		self.state = :success
	end

となっていますが、stateはクラス変数で、アクセサを定義する必要があります。Ruby クラス変数のゲッタによると、クラス変数に対しては、attrなんとかでアクセサを定義することはできないそうなので、コードを書いて定義しました。リファクタリング対象コードは、次のようになります。

ファイル methodsample.rb

class MethodSample
	def state
		@@state
	end

	def state=(val)
		@@state = val
	end

	def failure
		self.state = :failure
	end

	def error
		self.state = :error		
	end

	def success
		self.state = :success
	end
end

これに対するテストコードは次のようになります。

ファイル test_methodsample.rb

require ‘test/unit’
require_relative ‘methodsample’

class TestMethodSample < Test::Unit::TestCase def setup @methodsample = MethodSample.new end def test_failure @methodsample.failure result = @methodsample.state expected_result = :failure assert_equal expected_result, result end def test_error @methodsample.error result = @methodsample.state expected_result = :error assert_equal expected_result, result end def test_success @methodsample.success result = @methodsample.state expected_result = :success assert_equal expected_result, result end end [/ruby] あと、この節のサンプル:def_eachを使って類似メソッドを定義するで、def_eachメソッドを定義するClassクラスは記述されているんですが、プロダクションコードでどう使うのか書かれていないので、補います。 ファイル methodsample.rb [ruby] require_relative 'class' class MethodSample def state @@state end def state=(val) @@state = val end def_each :failure, :error, :success do |method_name| self.state = method_name end end [/ruby] これは、Module#define_methodを抽象化する話を参考にさせていただきました。