同じにやっても動かない

書いてある通りやっているのに動かないのです

RspecのControllerテストでDragonflyのモデルに新規レコードを登録する

RspecのControllerテストでの添付ファイルの作り方がよくわからなかったのですが、こんな感じでできましたのでメモ。


Dragonflyのモデル定義のphoto.rbはこんな感じ。

class Photo < ActiveRecord::Base
  attr_accessible :photo_image
end


photo_controller_spec.rbはこんな感じです。

describe PhotosController do
  filepath = "/OK.jpg"
  post :create, photo: { photo_image: fixture_file_upload(filepath, 'image/jpg') }

  it do
    response.response_code.should == 200
  end
end


fixture_file_uploadにアップロードしたいファイルのパスを渡して、content-typeを指定するだけで、temp fileとしてアップロードしてくれます。

filepathは、"$APP_ROOT\spec\fixtures" 以下のパスを指定します。

ではでは。

devise + Rspec でログイン状態のテストをしようとしたら動かなかった

今回も書いてあるとおりにやっていたつもりなのですが、動きませんでした。
そして、よく見たら書いてある通りにやっていなかったというお話です。

やり方はここに書いてある通りです。


書いてある通りに書いた(つもり)specがこれ。

describe "ログインユーザの POST" do
  before do 
    login_user # ログイン
    @model = build(:model)
    post :create, model: @model.attributes
  end
  
  it { response.should be_success }
end

そんでもってコンソールに出たエラーがこれ。

undefined local variable or method `login_user' for #<RSpec::Core::ExampleGroup::Nested_1::Nested_12:0x96e6730>


何がわるかったかというと、login_userをコールする位置でした。
理由まで調べていませんが、beforeやit method の中でコールするとこのエラーが出ます。


参考URLに書いてある通り、describeのすぐ後でコールしたらうまくいきました。
こんな感じ。

describe "ログインユーザの POST" do
  # ここでコール!
  login_user

  before do 
    @model = build(:model)
    post :create, model: @model.attributes
  end
  
  it { response.should be_success }
end


書いてある通りにやっていないので動かないの巻。

model.create / model.save は成功するのに model.all.count = 0になる on Rspec + FactoryGirl

SQLもWebアプリも難しいですね。
しょうもない事でハマってしまいました。


model単体のテストをRspec + FactoryGirlで書いていたのですが、model.create / model.save は成功する(trueが返ってきてレコードがちゃんとできる)のに、model.all.countはいつも0で、DBには保存されていないように見えるという不思議な現象に悩んでおりました。


で、原因はmodelの定義で設定していたdefault_scopeでした。

関連している他のモデルの属性を使ってソートするdefault_scopeを設定してたのですが、今回のテストには関連先まで定義していなかったため、どんなにレコードを登録しても検索条件には引っかからずmodel.allは常に0という事が起こっていたのです orz。


で、モデル単体のテストときは全てのスコープを無効にするようにしたら、ちゃんと登録済レコードが見えるようになりました。

こんな感じ。

it {
  Model.unscopded {
    テストコード
  }
}


でも、これだとまた忘れた頃に同じ事ではまりそうなので、結局default_scopeで設定するのをやめて、名前付きスコープで定義するようにしましたです。


ではでは。

Deviseでconfirmableを設定しているときのFixtureの書き方

Deviseでconfirmableを設定していると、普通にemailとpasswordだけでFixtureを用意してもうまく行きませんでした。
user.createの時にconfirmation mailを送るため、それでこけているようです。

createの前にskip_confirmation!を設定しても良いのですが、下記方法でconfirmedなUserと認識してくれたので、これで行こうと思います。

[FactoryGirlの例]

FactoryGirl.define do
  factory :user do
    email "test@test.org"
    password "testtest"
    confirmed_at Time.now  # これでConfirmedユーザー
  end
end

ではでは。

ポリモーフィック関連のfactoryの書き方 on FactoryGirl

こんな感じで、polymorphic関連を設定しているとします。

# Photo model
class Photo < ActiveRecord::Base
  belongs_to :category, :polymorphic => true
end

# Mountain model
class Mountain < ActiveRecord::Base
  has_one :photo, :as => :category
end

# River model
class River < ActiveRecord::Base
  has_one :photo, :as => :category
end


そんでもって、こんなfactoryを書きます。

FactoryGirl.define do
  factory :mountain_photo, class: "Photo" do
    association :category, factory: :mountain
  end

  factory :river_photo, class: "Photo" do
    association :category, factory: :river
  end

  factory :moutain do
    name "Mt.hoge"
 end

  factory :river do
    name "Riv.fuga"
  end
end


これで "FactoryGirl.create(:mountain_photo)" とやると、:categoryに:mountainが入ったレコードが、"FactoryGirl.create(:river_photo)" とやれば、:categoryに:riverが入ったレコードが生成されるようになります。

ではでは。

Dragonflyの画像ファイルを持つphotoモデルをRspec + FactoryGirl で testする

Dragonflyで画像ファイルを管理しているのですが、test方法のメモです。

テスト環境は Rspec + FactoryGirl ですが、Unit testとFixtureでも同じだと思います。


Dragonflyはtest時以下ディレクトリからファイルを探します

public\system\dragonfly\test\YYYY\MM


登録しようとする画像ファイル名が"IMG_0001.jpg"だとすると、Dragonflyの生成するuidはYYYY/MM/DD_HH_MM_SS_MLS_IMG_0001.jpgになるので、上記ディレクトリに "DD_HH_MM_SS_MLS_IMG_0001.jpg"という名前で画像ファイルを置けばよいです。


photo_spec.rbはこんな感じです。

# -*- coding: utf-8 -*-
require 'spec_helper'

describe Photo do

  describe "画像ファイル" do
    before do
      @jpg = create(:photo, photo_image_name: "OK.jpg", photo_image_uid: "2013/08/01_00_00_00_001_OK.jpg")
    end
    
    it { @jpg.should_not  be_new_record }
  end
end

上記specだと "public\system\dragonfly\test\2013\08\01_00_00_00_001_OK.jpg" というファイルを準備しておけば良いです。

参考までにphotoモデルはこんな感じ。

class Photo < ActiveRecord::Base
  attr_accessible :photo_image
  
  # For dragonfly
  image_accessor :photo_image
end

ではでは。

limitの値によってクエリの結果が異なる

先日、"関連付けたモデルの属性で、関連元モデルをソートする"というエントリを書きましたが、これ、一見うまくいっているように見えるだけで、所望の結果が得られない場合があることがわかりました。

Model.allで全レコードを取得したときにしか正しい結果が得られません。
paginationやlimitを使って取得レコード数を制限している場合は、レコードの内容によっては所望の結果が得られない場合があります。


結論から先に言うと、:include ではなく :joinsを使用するというのが正解のようです。
こんな感じ。

default_scope :joins => :report, :group=> "spots.id", :order => "reports.created_at DESC"

SQLを直接発行してうまくいくクエリをActivRecordに書きなおしただけですが、これでうまくいきました。


さて、うまくいかない状況をもう少し詳しく説明します。
先日のエントリで紹介した設定は下記の通り。

class Spot < ActiveRecord::Base
  has_many :report
  default_scope :include => :report, :order => "reports.created_at DESC"
end

class Report < ActiveRecord::Base
  belongs_to :spot
end

これで、spot.all すると、新しいReportを持っている順にspotがsortされて取得できます。
結果も正しい。


でも spot.limit(8)とかやると、spot.allのときと順番が違ったり、取得できるレコードまで違ったりするのです。

生成されたクエリを直接SQLで発行すると、limitが付いている場合でも正しい結果が取得できたりと、わけがわかりません。


色々と実験したのですが、:includeの場合は、limitで指定したレコード数分のspotレコードに紐付いているreportだけをロードしてきて、その中でソートをかけるという動作をしているように見えます。
なので、allじゃないと正しい結果にならない場合があるようです。


SQLちゃんと勉強していればわかる内容なのでしょうが、なんでこんなことになるのかよくわかっておりません。。。