Ruby on Rails Tutorial (2)
Chapter 6 から Chapter 10 でログインおよび認証な実装を、とのこと。ちなみに git なログによれば Chapter 6 までは一応なぞっている模様。
とりあえず controller を作成して
$ rails generate controller Users new
メソドは new のみ。model も作成しています。
$ rails generate model User name:string email:string
なんとなくな記憶なんですが、属性は作られんかったような。って確認してみたら違いました。app/models/user.rb は記述ナシですが db/migrate 配下なソレは属性な記述がありますね。
とりあえず何はなくとも db:migrate しておく模様。
$ bundle exec rake db:migrate
annotate という gem がある模様ですが、スルー。属性に外からアクセスしたい場合には以下な記述になるのか。
class User < ActiveRecord::Base attr_accessible :name, :email end
成程。ってことは対応するテーブルのフィールドがどうなってるか、ってのは db/migrate の対応するテーブルな記述を見ていくしかないのか。
次の項で
$ rails console --sandbox
でもごもごしてます。find(id) で検索したりな例が出てますが、User.find_by_email("mhartl@example.com") などというメソドを発見して、あーこーゆーのあったなとか思いだしたりとか。
User validations
テスツな環境は違うんで、ということで以下。
$ bundle exec rake db:test:prepare
で、Validating the presence of a name attribute. な実装が以下。
class User < ActiveRecord::Base attr_accessible :name, :email validates :name, :presence => true end
sandbox な REPL で確認してます。
$ rails console --sandbox Loading development environment in sandbox (Rails 3.0.9) Any modifications you make will be rolled back on exit ruby-1.9.2-p180 :001 > user = User.new() => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, encrypted_password: nil> ruby-1.9.2-p180 :002 > user.valid? => false ruby-1.9.2-p180 :003 >
以下な spec が云々、とありました。
describe User do before(:each) do @attr = { :name => "Example User", :email => "user@example.com" } end it "should require a name" do no_name_user = User.new(@attr.merge(:name => "")) no_name_user.should_not be_valid end end
@attr.merge とか面白いですね。あと should_not というメソドに be_valid を渡して云々ってのも ruby ぽくて面白い。
あるいは name 属性の文字列長なシバリだったり
validates :name, :presence => true, :length => { :maximum => 50 }
それに関する spec だったり
it "should reject names that are too long" do long_name = "a" * 51 long_name_user = User.new(@attr.merge(:name => long_name)) long_name_user.should_not be_valid end
email 属性のフォーマットに関する記述、の前に以下な spec 追加して確認した後に
it "should accept valid email addresses" do addresses = %w[user@foo.com THE_USER@foo.bar.org first.last@foo.jp] addresses.each do |address| valid_email_user = User.new(@attr.merge(:email => address)) valid_email_user.should be_valid end end it "should reject invalid email addresses" do addresses = %w[user@foo,com user_at_foo.org example.user@foo.] addresses.each do |address| invalid_email_user = User.new(@attr.merge(:email => address)) invalid_email_user.should_not be_valid end end
って結局 validation なナニなので
- should be_valid
- should_not be_valid
のどちらかになるのか。ちなみに model の記述は以下になる模様。
validates :email, :presence => true, :format => { :with => email_regex }
email_regex は以下だそうな。
email_regex = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
正規表現orz
一応テキストには上記正規表現についての簡単な解説もあります。次に出てくるのが email 属性は重複しない、というナニ。spec は以下。
it "should reject duplicate email addresses" do # Put a user with given email address into the database. User.create!(@attr) user_with_duplicate_email = User.new(@attr) user_with_duplicate_email.should_not be_valid end
model な記述は以下になる模様。
validates :email, :presence => true, :format => { :with => email_regex }, :uniqueness => true
あと、email なナニは大文字小文字を別なモノとして認識しないので、ということで以下な spec が追加されてます。
it "should reject email addresses identical up to case" do upcased_email = @attr[:email].upcase User.create!(@attr.merge(:email => upcased_email)) user_with_duplicate_email = User.new(@attr) user_with_duplicate_email.should_not be_valid end
これ向けな validates の引数も用意されている模様。
validates :email, :presence => true, :format => { :with => email_regex }, :uniqueness => { :case_sensitive => false }
成程、としか言いようが無いなぁ。
で、uniqueness を model な定義だけでは担保できないらしく、email なフィールドは index に、とのこと。
$ rails generate migration add_email_uniqueness_index
して add_email_uniqueness_index なソレを以下にする模様。
class AddEmailUniquenessIndex < ActiveRecord::Migration def self.up add_index :users, :email, :unique => true end def self.down remove_index :users, :email end end
ちなみに現状の実装はこの次の Chapter 7 に届いているのですが、試験はパスしてるみたいです。引き続き確認します。
Chapter 7
ええと spec に追加されているのは以下。
- it should require a password
- it should require a matching password confirmation
- it should reject short passwords
- it should reject long passwords
password_confirmation ってのはもっかい入れてね的ナニなのかな。
てか、この時点で password は model な属性でない云々な記述があって ? って思ったのですが、rails では暗号化されないと駄目、って理解で良いのかどうか。あと、password_confirmation なるソレも同様に virtual な属性らしい。
一応使ってる端末なプロジェクトも以下なナニは作成済み。
class AddPasswordToUsers < ActiveRecord::Migration def self.up add_column :users, :encrypted_password, :string end def self.down remove_column :users, :encrypted_password end end
おそらく以下も実行済み?
$ bundle exec rake db:migrate $ bundle exec rake db:test:prepare
以下な試験もパスしてますので大丈夫なのかな。
- it should have an encrypted password attribute
- it should set the encrypted password
これがテーブルへのカラムの追加の方法なのか。なんとなく model に対して云々って思ってたのですが、そうではないのね。
で、次の 7.2 あたりからが実装未着手な部分らしい。未だ Chapter 7 の半分もイケてない模様。とりあえず has_password? という boolean を戻す不思議なメソドが出てきます。
spec な記述が以下らしい。
describe "has_password? method" do it "should be true if the passwords match" do @user.has_password?(@attr[:password]).should be_true end it "should be false if the passwords don't match" do @user.has_password?("invalid").should be_false end end
- it should be true if the passwords match
- it should be false if the passwords don't match
成程。で、Implementing has_password? ということで以下。
def has_password?(submitted_password) encrypted_password == encrypt(submitted_password) end
で、migrate をナニ。
$ rails generate migration add_salt_to_users salt:string
string な salt を追加するのか。
class AddSaltToUsers < ActiveRecord::Migration def self.up add_column :users, :salt, :string end def self.down remove_column :users, :salt end end
migration をナニしたら以下も忘れずに。
$ bundle exec rake db:migrate $ bundle exec rake db:test:prepare
あと user.rb の private なあたりを以下にする模様。
private def encrypt_password self.salt = make_salt unless has_password?(password) self.encrypted_password = encrypt(password) end def encrypt(string) secure_hash("#{salt}--#{string}") end def make_salt secure_hash("#{Time.now.utc}--#{password}") end def secure_hash(string) Digest::SHA2.hexdigest(string) end
ちょっと実装して試験してみます。以下。
$ bundle exec rspec spec/models/user_spec.rb \ > -e "should be true if the passwords match" . 1 example, 0 failures $ bundle exec rspec spec/models/user_spec.rb \ > -e "should be false if the passwords don't match" . 1 example, 0 failures $ bundle exec rspec spec/models/user_spec.rb -e "has_password? method" Run filtered using {:full_description=>/(?-mix:has_password\? method)/} .. 2 examples, 0 failures $
で、一つ目で has_password? なメソドが無い、と叱られます。rails console なナニではメソドがある、とのことなのですが。
って、以下を入れるの忘れてましたorz
before(:each) do @user = User.create!(@attr) end
上記引用の通り試験パスしてます。なんつーか馬鹿だなぁ。"has_password? method" で OK でしたね。
次は authenticate なメソドらしい。password encryption なブロックに追加。
- it should return nil on email/password mismatch
- it should return nil for an email address with no user
- it should return the user on email/password match
で、ついに find_by_email が出てくるのだな。
def has_password?(submitted_password) encrypted_password == encrypt(submitted_password) end def self.authenticate(email, submitted_password) user = find_by_email(email) return nil if user.nil? return user if user.has_password?(submitted_password) end
で、7.3 節に入った瞬間、話の流れが急になった気がするのでとりあえず実装とか記録は止めて読むだけにしときます。ので、とりあえずエントリ投入。