んじゃあ、フルネームを作ろう。

いや、前の生地でやろうと思ったんだけど、testで詰まって、記事が長くなったんで、新たに立てた。

フルネーム = 姓 + ミドル・ネーム(存在するならば) + 名、な感じで。
で、ミドル・ネームは、「"」で括って、出力する方面で。

で、コード。
app/models/user.rb

+  #Generate Fullname == family_name + middle_name(unless empty) with double-quote + given_name
+  def full_name
+    name = family_name + " "
+    name += "\"#{middle_name}\" " unless middle_name.empty?
+    name += given_name
+    name
+  end

イヤ、つうか、ね?
絶対に、名前のフォーマットを決めるメソッドとか、どっかにあるよ?
i18n使ったようなの。

絶対あるね(キッパリ)。
無い筈ないね(シッカリ)。

んで、テスト書く。
test/unit/user_test.html

+  def test_should_create_users_fullname_with_middle_name
+    user = users(:quentin)
+    assert user.full_name == "Family \"Middle\" Given"
+  end
+
+  def test_should_create_users_fullname_without_middle_name
+    user = users(:aaron)
+    assert user.full_name == "Hogehoge Fugafuga"
+  end

んで、test。
バッチリ、だね。おk。

とりあえず、メールアドレスで認証するようにする。

今どき、ハンドル・ネームでのログインってのも、ねえ?
なんてことを、bloggerにも書いてたな...

まあ、ヨシとするwww

で、Userモデルから、loginとnameを消して、nicknameを追加することにする。
で、マイグレーションファイルの作成。
まず、loginとnameの削除。

$ script/generate migration RemoveColumnLoginAndNameFromUser login:string name:string

で、編集。
*数字*_remove_column_login_and_name_from_user.rb

class RemoveColumnLoginAndNameFromUser < ActiveRecord::Migration
  def self.up
    remove_column :users, :login
    remove_column :users, :name
  end

  def self.down
    add_column :users, :name, :string, :limit => 40
    add_column :users, :login, :string, :limit => 100, :default => '', :null => true
  end
end

んで、nicknameの追加。

$ script/generate migration AddColumnNicknameToUser nickname:string

で、編集。
*数字*_add_column_nickname_to_user.rb

class AddColumnNicknameToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :nickname, :string, :limit => 100, :default => '', :null => true
  end

  def self.down
    remove_column :users, :nickname
  end
end

はい。これで、DBのスキーマ変更はイイでしょう。

後は、

  • user.loginへの呼び出しを、user.emailへ変更する(一部user.nickname)
  • なぜか('/')なんて形でルートへパスを指定しているところがあるんで、(root_path)-に書き換える
  • ルートパスを:controller => "sessions", :action => "new"に設定する。
  • public/index.htmlをindex.html.bakへリネームする。

って云う感じですか。

まあ、面倒だから、diff。
app/helpers/users_helper.rb

-    options.reverse_merge! :content_method => :login, :title_method => :login, :class => :nickname
+    options.reverse_merge! :content_method => :email, :title_method => :email, :class => :nickname

app/models/user.rb

-  validates_presence_of     :login
-  validates_length_of       :login,    :within => 3..40
-  validates_uniqueness_of   :login
-  validates_format_of       :login,    :with => Authentication.login_regex, :message => Authentication.bad_login_message
+  validates_presence_of     :nickname
+  validates_format_of       :nickname,     :with => Authentication.name_regex,  :message => Authentication.bad_name_message, :allow_nil => true
+  validates_length_of       :nickname,     :maximum => 100
 
-  validates_format_of       :name,     :with => Authentication.name_regex,  :message => Authentication.bad_name_message, :allow_nil => true
-  validates_length_of       :name,     :maximum => 100
(略)
-  attr_accessible :login, :email, :name, :password, :password_confirmation
+  attr_accessible :email, :nickname, :password, :password_confirmation
(略)
   def self.authenticate(login, password)
     return nil if login.blank? || password.blank?
-    u = find_in_state :first, :active, :conditions => {:login => login} # need to get the salt
+    u = find_in_state :first, :active, :conditions => {:email => login} # need to get the salt
     u && u.authenticated?(password) ? u : nil
   end
(略)
-  def login=(value)
-    write_attribute :login, (value ? value.downcase : nil)
-  end
-  

app/controllers/users_controller.rb

     if success && @user.errors.empty?
-      redirect_back_or_default('/')
+      redirect_back_or_default(root_path)
       flash[:notice] = "Thanks for signing up!  We're sending you an email with your activation code."
(略)
-      redirect_to '/login'
+      redirect_to(login_path)
(略)
-      redirect_back_or_default('/')
+      redirect_back_or_default(root_path)
(略)
-      redirect_back_or_default('/')
+      redirect_back_or_default(root_path)

app/controllers/sessions_controller.rb

-    user = User.authenticate(params[:login], params[:password])
+    user = User.authenticate(params[:email], params[:password])
(略)
-      redirect_back_or_default('/')
+      redirect_back_or_default(root_path)
(略)
-      @login       = params[:login]
+      @login       = params[:email]
(略)
-    redirect_back_or_default('/')
+    redirect_back_or_default(root_path)

app/views/users/new.html.erb

-<p><%= label_tag 'login' %><br/>
-<%= f.text_field :login %></p>
+<p><%= label_tag 'nickname' %><br/>
+<%= f.text_field :nickname %></p>

app/views/users/_user_bar.html.erb

-  <div id="user-bar-greeting">Logged in as <%= link_to_current_user :content_method => :login %></div>
+  <div id="user-bar-greeting">Logged in as <%= link_to_current_user :content_method => :nickname %></div>

app/views/sessions/new.html.erb

-<p><%= label_tag 'login' %><br />
-<%= text_field_tag 'login', @login %></p>
+<p><%= label_tag 'email' %><br />
+<%= text_field_tag 'email', @login %></p>

app/views/user_mailer/signup_notification.erb

-  Username: <%=h @user.login %>
+  Email: <%=h @user.email %>

app/views/user_mailer/activation.erb

-<%=h @user.login %>, your account has been activated.  Welcome aboard!
+<%=h @user.nickname %>, your account has been activated.  Welcome aboard!

config/routes.rb

+  map.root :controller => 'sessions', :action => 'new'

んで、public/index.htmlのリネーム

$ svn move ./public/index.html ./public/index.html.bak

で、マイグレートして動作確認。

$ rake db:migrate

おk。

スバラシイ:-)

コミットする前にテストでもしましょうか。

で、

$ rake

してみたら、アンタ!

=> Errors running test:units and test:functionals!

!
!?
で、ターミナルをスクロール・バックしてみると...

=> /Users/hogehoge/Rails/climb-on/config/initializers/mail.rb:4: You have a nil object when you didn't expect it! (NoMethodError)

ん?
あれ?
んんんんんん...じゃあ、とりあえず、config.yml読み込むところコメントアウトして、これまでmail.rbに書いてたのを全部コメントアウトして、以下の内容をベタ書き追記。

ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
  :address => 'your.smtp.domain',
  :port => 587,
  :domain => 'localdomain',
  :authentication => :login,
  :user_name => 'username',
  :password => 'password'
}

んで、もう一度、

$ rake
=> 19 tests, 0 assertions, 0 failures, 19 errors

ぅわおっ!!
全滅じゃんwww
まあ、あれだ、loginカラムとnameカラム削ってるから、その辺でテストが引っかかってるんだろうな、と。
んじゃあ、その辺なんとかしましょうか。

とりあえず、fixture変えないと、ね。
test/fixtures/users.yml

quentin:
  id:                        1
  nickname:                  quentin
  email:                     quentin@example.com
  salt:                      ee4305f8dfb7d8a338f88fa8953e1eae5b97647a # SHA1('0')
  crypted_password:          d3cb87455619be9535daab550a3f9ab5a98b2d93 # 'monkey'
  created_at:                <%= 5.days.ago.to_s :db  %>
  remember_token_expires_at: <%= 1.days.from_now.to_s %>
  remember_token:            77de68daecd823babbb58edb1c8e14d7106e83bb
  activation_code:           
  activated_at:              <%= 5.days.ago.to_s :db %>
  state:                     active
      
aaron:
  id:                        2
  nickname:                  aaron
  email:                     aaron@example.com
  salt:                      4f0811fbeea7c6eb2b9b3608e63a74cf2050edd8 # SHA1('1')
  crypted_password:          023b11faf750a945ab19acca61fbee391692586d # 'monkey'
  created_at:                <%= 1.days.ago.to_s :db %>
  remember_token_expires_at: 
  remember_token:            
  activation_code:           1b6453892473a467d07372d45eb05abc2031647a
  activated_at:              

  state:                     pending


old_password_holder:
  id:                        3
  nickname:                  old_password_holder
  email:                     salty_dog@example.com
  salt:                      7e3041ebc2fc05a40c60028e2c4901a81035d3cd
  crypted_password:          00742970dc9e6319f8019fd54864d3ea740f04b1 # test
  created_at:                <%= 1.days.ago.to_s :db %>

  activation_code:           
  activated_at:              <%= 5.days.ago.to_s :db %>


  state:                     active

admin:
  id: 4
  nickname: admin
  email: admin@example.com
  salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
  crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
  # activation_code: adminscode # only if you're activating new signups
  created_at: <%= 1.days.ago.to_s :db %>

...やっぱやめたwww
なんか、テストの為のテストみたいな感じになってきたwww

ちょっと先に進みたい。
んで、強行進行!
出たとこ勝負っ!

とも思ったけど、これからは、新しいメソッド作るのがメインになってくるんで、とりあえず、現存のテストをコメント・アウトして、今後のメソッドは、テストすることにしよう。
おう、しよう。

コードをキレイにしましょうか。

で、これからは、自分のコードを足すのがメインになってくる筈なんで、コードのスタイルを統一していきましょうかね。

で、基本的に、Rubyコーディング規約に準ずることにしたよ。

他人が敷いたレールに則るのって、ラクチンだよねw

Railsの基本だね;-p

だがしかし、外すところでは、外すよ。
例えば、ファイル名は、「-」じゃなくって、「_」繋ぎにするとか。
まあ、これも、railsがそうなってるから、なんだけどね;-p

メソッドの引数に関しても、宣言くさいもの、
include、require
belongs_to、has_many、has_one、has_and_belongs_to_many
validate_hogehoge
attr_hogehoge
なんかは、敢えて()を付けないことにした。
特に、validate_hogehogeは、付いてない方が、可読性が良い気がする。
良い気がすれば、おk。wwww

っていうか、この一節、bloggerからの丸コピーwww

名を名乗れ。

とりあえず、ユーザmodelの中に、本名の項目を作ろう。
family_nameとmiddle_nameとgiven_nameね。
外人も使うかも知れないからねwww

$ script/generate migration AddColumnFamilyNameAndMiddleNameAndGivenNameToUser family_name:string middle_name:string given_name:string

んで、編集。
db/migrate/*数字*_add_column_family_name_and_middle_name_and_given_name_to_user.rb

class AddColumnFamilyNameAndMiddleNameAndGivenNameToUser < ActiveRecord::Migration
  def self.up
    add_column :users, :family_name, :string,   :limit => 100, :default => ''
    add_column :users, :middle_name, :string,   :limit => 100, :default => '', :null => true
    add_column :users, :given_name,   :string,  :limit => 100, :default => ''
  end

  def self.down
    remove_column :users, :given_name
    remove_column :users, :middle_name
    remove_column :users, :family_name
  end
end

middle_nameは、無い場合があるけど、まあ、普通の文化圏の方なら、名字と名前は無い事は無いでしょう。

ホントに?www

んで、validationを書こう。
app/models/user.rb

   validates_format_of       :email,    :with => Authentication.email_regex, :message => Authentication.bad_email_message
 
+  validates_presence_of     :family_name
+  validates_format_of       :family_name,     :with => Authentication.name_regex,  :message => Authentication.bad_name_message
+  validates_length_of       :family_name,     :maximum => 100
+
+  validates_format_of       :middle_name,     :with => Authentication.name_regex,  :message => Authentication.bad_name_message, :allow_nil => true
+  validates_length_of       :middle_name,     :maximum => 100
+
+  validates_presence_of     :given_name
+  validates_format_of       :given_name,     :with => Authentication.name_regex,  :message => Authentication.bad_name_message
+  validates_length_of       :given_name,     :maximum => 100
+
   # HACK HACK HACK -- how to do attr_accessible from here?


んじゃあ、ちょっとfixture書き換える。
test/fixtures/users.yml

   nickname:                  quentin
+  family_name:               Family
+  middle_name:               Middle
+  given_name:                Given
   email:                     quentin@example.com

   nickname:                  aaron
+  family_name:               Hogehoge
+  middle_name:               
+  given_name:                Fugafuga
   email:                     aaron@example.com

   nickname:                  old_password_holder
+  family_name:               名字
+  middle_name:               ミドル・ネーム
+  given_name:                名前
   email:                     salty_dog@example.com
 
  nickname: admin
+  family_name:               アドミン
+  middle_name:               ザ
+  given_name:                管理者
   email: admin@example.com

んで、テストコードを足す。
test/unit/user_test.rb

   fixtures :users
 
+  def test_invalid_with_empty_attributes
+    user = User.create
+    assert !user.valid?
+    assert user.errors.invalid?(:nickname)
+    assert user.errors.invalid?(:email)
+    assert user.errors.invalid?(:family_name)
+    assert user.errors.invalid?(:given_name)
+  end

んで、テスト。

$ ruby test/unit/user_test.rb
=> Loaded suite test/unit/user_test
=> Started
=> .
=> Finished in 0.108762 seconds.
=> 
=> 1 tests, 5 assertions, 0 failures, 0 errors

ん、スバラシイぃ:-)
まあ、何がわかったって、新規User作成時に、nicknameとemailとfamily_nameとgiven_nameのいずれかで、validation_hogehogeがエラーを返してないかどうかを見ているだけなんだけどwww
まあ、でも、今やったのって、validation書いただけだもんねぇ...

んじゃあ、もう少し、コードを足すか。
test/unit/user_test.rb

+  def test_invalid_with_empty_attributes
+    user = User.create
+    assert !user.valid?
+    assert user.errors.invalid?(:nickname)
+    assert user.errors.invalid?(:email)
+    assert user.errors.invalid?(:family_name)
+    assert user.errors.invalid?(:given_name)
+  end
+
+  def test_should_create_user
+    assert_difference 'User.count' do
+      user = create_user
+      assert !user.new_record?, "#{user.errors.full_messages.to_sentence}"
+    end
+  end
+
+  protected
+    def create_user(options = {})
+      record = User.new({
+        :nickname => "Nick",
+        :email => 'quire@example.com',
+        :password => 'quire69',
+        :password_confirmation => 'quire69',
+        :family_name => "Family",
+        :middle => "Middle",
+        :given_name => "Given"
+      }.merge(options))
+      record.register! if record.valid?
+      record
+    end
+

んで、テスト。

$ ruby ./test/unit/user_test.rb
Loaded suite test/unit/user_test
Started
.F
Finished in 0.081857 seconds.

  1) Failure:
=> test_should_create_user(UserTest)
=>     [test/unit/user_test.rb:21:in `test_should_create_user'
=>      /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/core_ext/test/unit/assertions.rb:46:in `assert_difference'
=>      test/unit/user_test.rb:19:in `test_should_create_user'
=>      /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `__send__'
=>      /opt/local/lib/ruby/gems/1.8/gems/activesupport-2.2.2/lib/active_support/testing/setup_and_teardown.rb:60:in `run']:
=> Nickname can't be blank and Family name can't be blank and Given name can't be blank.
=> <false> is not true.
=> 
=> 2 tests, 6 assertions, 1 failures, 0 errors

ん?
んん?
ん〜...
で、はまったはまったwww

結局、どうも、Userモデルで、attr_accessibleにfamily_nameとmiddle_nameとGiven_nameを指定していないから、ということに到達しましたwww
いやあ、これが、結局ここで何時間も止まることになる訳ですがwww

んで、修正。
app/models/user.rb

-  attr_accessible :login, :email, :name, :password, :password_confirmation
+  attr_accessible :email, :password, :password_confirmation, :nickname, :family_name, :middle_name, :given_name

で、これでどうよ?

$ ruby ./test/unit/user_test.rb
=> Loaded suite test/unit/user_test
=> Started
=> .E
=> Finished in 0.142293 seconds.
=> 
=>   1) Error:
=> test_should_create_user(UserTest):
=> SocketError: getaddrinfo: nodename nor servname provided, or not known
(略)
=> 2 tests, 5 assertions, 0 failures, 1 errors

はいいいいっ!?

で、はまるはまるwwww
結局、どうも、config/initializers/mail.rbで、設定を端折っていたからだと思われ。
で、ちゃんと設定して、

$ ruby test/unit/user_test.rb
Loaded suite test/unit/user_test
Started
.E
=>Finished in 2.002131 seconds.
=>
=>  1) Error:
=>test_should_create_user(UserTest):
=>Net::SMTPFatalError: 550 5.1.2 Bad destination system: quire@example.com
(略)
=> 2 tests, 5 assertions, 0 failures, 1 errors

はいいいっ!?
じゃあ、test/unit/user_test.rbのcreate_userメソッドの中の:emailを、まともなメールアドレスに書き換える。

=> Loaded suite test/unit/user_test
=> Started
=> .E
=> Finished in 3.069374 seconds.
=> 
=>   1) Error:
=> test_should_create_user(UserTest):
=> Net::SMTPFatalError: 550 5.7.0 From address is not one of your addresses.
(略)
=> 2 tests, 5 assertions, 0 failures, 1 errors

もう、なんか、ねwww
どうでもイイ気がしてきたwww