Тестирование Rails 4 с RSpec и FactoryGirl, вложенные атрибуты не сохраняются

Я заставлял себя изучать Rails4 с BDD, и до сих пор это получалось довольно хорошо. Тем не менее, я уже несколько часов занимаюсь этой проблемой и не могу найти причину, по которой она не работает. В настоящее время я работаю над тестированием контроллеров, со встроенными только контроллерами и моделями, без форм или чего-либо еще. Он работает через консоль, поэтому я просто использую что-то не так.

Вот мой код для справки:

/admin/pages_controller.rb

class Admin::PagesController < ApplicationController

  def update
    @page = Page.find_by_id(params[:id])

    @page.update_attributes!(message_params)

    redirect_to edit_admin_page_path(@page)
  end

  private
   def message_params
      params.require(:page).permit(
        :url, 
        :position, 
        :name, 
        :tags,
        images_attributes:
            [:image_file_name, :image_file_size, :image_content_type, :name, :caption, :tags, :owner_id, :owner_type],
        block_attributes:
            [:id, :body, :owner_id, :owner_type]
      )
   end

end

модели/page.rb

class Page < ActiveRecord::Base
    validates :name, presence: true
    before_save :validate_url

    has_many :images, :as => :owner 
    has_one :block, :as => :owner

    accepts_nested_attributes_for :images, :block

    def validate_url
        if url.blank?
          self.url = self.name.strip.downcase.gsub(" ","-").gsub(%r([^0-9a-z-]), '').gsub("--","-")
        end
    end
end

pages_controller_spec.rb

  describe "POST #update" do
    before :each do
        @page = FactoryGirl.create(:page)
    end

    it "makes sure user can upload an image" do
        image = FactoryGirl.build(:image, owner_id: @page.id, owner_type: "Page") 
        post :update, id: @page, page: { :images => [ image ]}
        @page.reload
        expect(@page.images.first).to eq(image)
    end

    it "updates values of the attributes accordingly" do
        post :update, id: @page, page: { :name => 'foo', :url => 'bar' }
        @page.reload
        expect(@page.name).to eq('foo')
    end

    it "updates the values of the block" do
        @page = FactoryGirl.create(:page_with_block)
        post :update, id: @page, page: { block: { :body => 'foobar' } }
        @page.reload
        expect(@page.block.body).to eq('foobar')        
    end

  end

спец/фабрики/blocks.rb

FactoryGirl.define do
  factory :block do
    body "<html><body><h1>Hello world!</h1><section>This is the content section</section></body></html>"
  end  
end

спец/фабрики/images.rb

include ActionDispatch::TestProcess

FactoryGirl.define do
  factory :image do
    image { fixture_file_upload Rails.root.to_s + '/spec/images/1.jpg', 'image/jpg'}
    name { Faker::App.name }
    caption { Faker::Lorem.sentence }
    tags { Faker::Lorem.words }
  end

end

Результаты RSpec:

  1) Admin::PagesController POST #update makes sure user can upload an image
     Failure/Error: expect(@page.images.first).to eq(image)

       expected: #<Image id: nil, image_file_name: "1.jpg", image_file_size: 15078, image_content_type: "image/jpg", name: "Alpha", caption: "Assumenda et exercitationem quo.", tags: ["doloribus", "maiores", "dicta"], owner_id: 37, owner_type: "Page", created_at: nil, updated_at: nil>
            got: nil

       (compared using ==)
     # ./spec/controllers/admin/pages_controller_spec.rb:111:in `block (3 levels) in <top (required)>'

  2) Admin::PagesController POST #update updates the values of the block
     Failure/Error: expect(@page.block.body).to eq('foobar')

       expected: "foobar"
            got: "<html><body><h1>Hello world!</h1><section>This is the content section</section></body></html>"

       (compared using ==)
     # ./spec/controllers/admin/pages_controller_spec.rb:124:in `block (3 levels) in <top (required)>'

Finished in 3.95 seconds (files took 3.71 seconds to load)
19 examples, 2 failures

модели/image.rb

class Image < ActiveRecord::Base
    validates_presence_of :image_file_name, :image_file_size, :image_content_type
    before_save :clean_up

    belongs_to :page, foreign_key: "owner_id"

    has_attached_file :image
    validates_attachment_content_type(:image, :content_type => /^image\/(jpg|jpeg|pjpeg|png|x-png|gif)$/)
    validates :image, :attachment_presence => true


    def clean_up
        if name.blank?
          self.name = self.image_file_name.strip.downcase.gsub(" ","-").gsub(%r([^0-9a-z-]), '').gsub("--","-")
        end
    end
end

Любая помощь будет оценена по этому вопросу, так как большинство поисковых запросов, которые я нашел по этому вопросу, касаются форм или params.require. Я могу обновить атрибуты самой страницы с помощью второй спецификации, но ничего из того, что я делаю, не попадает во вложенные ресурсы. Что мне здесь не хватает? Заранее спасибо!

Изменить: обновлено, чтобы включить модель изображения


person Drew Kario    schedule 23.04.2015    source источник
comment
Вы забыли опубликовать Image класс. Опубликуйте это, и я дам вам знать, что вы должны делать.   -  person ole    schedule 24.04.2015
comment
@ole Опубликовал это. Извините, я забыл добавить это.   -  person Drew Kario    schedule 24.04.2015


Ответы (2)


Прежде всего, избегайте использования загрузки файла с фикстурой через factory_girl http://pivotallabs.com/avoid-using-fixture-file-upload-with-factorygirl-and-paperclip/

Вы можете просто использовать его следующим образом:

FactoryGirl.define do
  factory :image do
    image { File.new(Rails.root.join('spec/images/1.jpg') }
    name { Faker::App.name }
    caption { Faker::Lorem.sentence }
    tags { Faker::Lorem.words }
  end
end

Это не четкий ответ, это просто путь в правильном направлении:

describe "POST #update" do
  let(:img) { Rack::Test::UploadedFile.new("spec/factories/files/file.csv") } 
  let!(:page) { FactoryGirl.create(:page) }
  let(:image_attrs) do 
    FactoryGirl.attributes_for(:image, owner: page)
  end
  let(:image) { page.images.first }

  it "makes sure user can upload an image" do
    expect do
      post :update, id: page, page: { :images_attributes => { "0" => image_params } }
    end.to change { page.reload.images.count }.by(1)

    # Now compare the attributes
    expect(image.content_type).to eq(image_attrs[:some_type])
    # ...
  end

  it "updates values of the attributes accordingly" do
    expect do
      post :update, id: page, page: { name: 'foo', url: 'bar' }
    end.to change { page.reload.name }.to("foo")
  end

  context "updates the values of the block" do
    let(:page) { FactoryGirl.create(:page_with_block) }

    it do
      expect do
        post :update, id: page, page: { block: { :body => 'foobar' } }
      end.to change { page.reload.block.body }.to("foobar")
    end
  end
end
person ole    schedule 24.04.2015
comment
Я использовал загрузку фикстур, потому что каждый раз, когда я запускал спецификацию в папке system/images, она создавала десятки изображений, но теперь я добавил метод очистки, так что это не имеет значения. Я попробовал ваш код, и он ничего не изменил, кроме того, что заставил меня понять, насколько глупо было указывать изображения с внешним ключом вместо полиморфного. Остальное я реализовал как вы выложили, но вложенные атрибуты оба не сохраняют до сих пор. - person Drew Kario; 24.04.2015

Изменил спецификацию на это из-за комментария:

describe "POST #update" do
let(:img) { Rack::Test::UploadedFile.new('spec/images/1.jpg') }
let(:image_attrs) do 
    FactoryGirl.attributes_for(:image, owner: page, image: img)
end
let!(:page) { FactoryGirl.create(:page_with_block) }


it "makes sure user can upload an image" do
    expect do
      post :update, id: page, page: { :images => { "0" => image_attrs } }
    end.to change { page.reload.images.count }.by(1)
end

it "updates values of the attributes accordingly" do
    post :update, id: page, page: { :name => 'foo', :url => 'bar' }
    page.reload
    expect(page.name).to eq('foo')
end

it "updates the values of the block" do
    expect do
        post :update, id: page, page: { block: { :body => 'foobar' } }
    end.to change { page.reload.block.body }.to("foobar")   
end

конец

Проблема все еще возникает:

................F.F

Failures:

  1) Admin::PagesController POST #update makes sure user can upload an image
     Failure/Error: expect do
       expected result to have changed by 1, but was changed by 0
     # ./spec/controllers/admin/pages_controller_spec.rb:111:in `block (3 levels) in <top (required)>'

  2) Admin::PagesController POST #update updates the values of the block
     Failure/Error: expect do
       expected result to have changed to "foobar", but did not change
     # ./spec/controllers/admin/pages_controller_spec.rb:123:in `block (3 levels) in <top (required)>'

Finished in 1.32 seconds (files took 1.34 seconds to load)
19 examples, 2 failures

Failed examples:

rspec ./spec/controllers/admin/pages_controller_spec.rb:110 # Admin::PagesController POST #update makes sure user can upload an image
rspec ./spec/controllers/admin/pages_controller_spec.rb:122 # Admin::PagesController POST #update updates the values of the block

РЕДАКТИРОВАТЬ: оказалось, что я неправильно разрешил image_attributes хорошо работать со скрепкой, и в основном многие мелкие вещи были неправильными. Я больше привык к настройке Rails 3 и неверно истолковал, как работают гемы Rails 4.

Обновленная спецификация:

  describe "POST #update" do
    let(:img) { Rack::Test::UploadedFile.new(Rails.root.join('spec/images/1.jpg'), 'image/jpg') }
    let(:image_attrs) do 
        FactoryGirl.attributes_for(:image, owner: page, image: img)
    end
    let(:block_attrs) do
        FactoryGirl.attributes_for(:block, owner: page, :body => 'foobar')
    end
    let!(:page) { FactoryGirl.create(:page_with_block) }


    it "makes sure user can upload an image" do
        expect do
          post :update, id: page, page: { :images_attributes => { "0" => image_attrs } }
        end.to change { page.reload.images.count }.by(1)
    end

    it "updates values of the attributes accordingly" do
        post :update, id: page, page: { :name => 'foo', :url => 'bar' }
        page.reload
        expect(page.name).to eq('foo')
    end

    it "updates the values of the block" do
        expect do
            post :update, id: page, page: { :block_attributes => block_attrs }
        end.to change { page.reload.block.body }.to("foobar")   
    end
  end

Спасибо @ole за то, что указал мне правильное направление для тестирования кода!

person Drew Kario    schedule 24.04.2015