Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keeping model records and associated directories in sync (with tests)

In my app I create a directory when I create my theme record. This is to store file assets related to the theme. I've always struggled with how to keep the existence of the directory in sync with the lifecycle of the record. This is my current take:

after_create :create_theme_directory
after_rollback :destroy_theme_directory, :on => :create, :if => :id

def directory
    Rails.configuration.uploads_root.join('themes', id.to_s)
end

private

def create_theme_directory
    FileUtils.mkdir_p directory
end

def destroy_theme_directory
    FileUtils.remove_dir directory, :force => true
end

It works well except that Rspec doesn't seem to trigger the removal of the directory when it rolls back theme records after tests.

Is there a best practice for this kind of thing? The idea being that one should never be left with a stray directory without an associated record.

like image 554
Brendon Muir Avatar asked Nov 30 '13 09:11

Brendon Muir


2 Answers

The after_rollback callback you have defined will only get called if the creating, destroying or updating a record were done through ActiveRecord. When RSpec resets, it doesn't go through ActiveRecord so it doesn't trigger any of the transaction callbacks (after_rollback and after_commit).

You could add another callback that destroys the directory if it still exists:

after_commit :destroy_theme_directory, :on => :destroy

def destroy_theme_directory
  if File.directory?(directory)
    FileUtils.remove_dir directory, :force => true
  end
end

And then trigger both the create and destroy actions in your feature spec:

scenario 'create and destroy' do
  visit new_directory_path
  #fill_in fields
  click_button "Create"

  expect(page).to have_content "created"

  visit users_path
  click_link "Delete" #assuming only directory object exists and you have a delete link in your directory index page
end

In this way, you trigger both the create and destroy actions in your specs so you don't need to do any clean-up.

The other option is to manually remove the directory in the spec where you test its creation.

#assuming you have model spec for testing that directory is created
it 'creates corresponding directory'
  directory.create
  expect(File.directory?(directory)).to eq true

  # the line below is just for cleanup. No need to do it in an after_all block if it only needs to be done for a few specs
  FileUtils.remove_dir directory, :force => true 
end

Hope that helps.

like image 78
Gjaldon Avatar answered Oct 04 '22 15:10

Gjaldon


Really interesting question for which I have an interest of my own since I am currently developing an application where files are being uploaded and converted.

Not an expert on RSpec at all, but I suppose it is very much database orientated and leaves it up to the user to clean up after himself if it's not related to the database (like creation of folders/updating images/initiating an external interface).

Found this example on how to use after(:all) for the cleanup with RSepc and carrierwave testing - similar to my initial though.

Another alternative is more pragmatic and probably more limited when it comes to testing. Have a backup file for your target structure available and restore it after tests have been completed.

I am indeed interested if anyone has a better method.

Hope this helps! Eugen

like image 31
smile2day Avatar answered Oct 04 '22 16:10

smile2day