fix the tests + finish the integration of Dragonfly + clean code

This commit is contained in:
did 2011-06-20 17:39:59 -07:00
parent 87e7ced3c1
commit f4d9acd282
20 changed files with 346 additions and 252 deletions

View File

@ -31,7 +31,7 @@ class Asset
end end
def to_liquid def to_liquid
Locomotive::Liquid::Drops::Asset.new(self) { :url => self.source.url }.merge(self.attributes).stringify_keys
end end
end end

View File

@ -92,7 +92,7 @@ class ThemeAsset
end end
def to_liquid def to_liquid
{ :url => self.source.url }.merge(self.attributes) { :url => self.source.url }.merge(self.attributes).stringify_keys
end end
def self.all_grouped_by_folder(site, include_all = true) def self.all_grouped_by_folder(site, include_all = true)

View File

@ -4,18 +4,22 @@ module Locomotive
def self.resize_url(source, resize_string) def self.resize_url(source, resize_string)
file = nil file = nil
if source.is_a?(String) # simple string if source.is_a?(String) || source.is_a?(Hash) # simple string or theme asset
source = source['url'] if source.is_a?(Hash)
if source =~ /^http/ if source =~ /^http/
file = self.app.fetch_url(source) file = self.app.fetch_url(source)
else else
file = self.app.fetch_file(File.join('public', source)) file = self.app.fetch_file(File.join('public', source))
end end
elsif source.respond_to?(:url) # carrierwave uploader elsif source.respond_to?(:url) # carrierwave uploader
if source.file.respond_to?(:url) if source.file.respond_to?(:url)
file = self.app.fetch_url(source.url) # amazon s3, cloud files, ...etc file = self.app.fetch_url(source.url) # amazon s3, cloud files, ...etc
else else
file = self.app.fetch_file(source.path) file = self.app.fetch_file(source.path)
end end
else else
Locomotive.logger.warning "Unable to resize on the fly: #{source.inspect}" Locomotive.logger.warning "Unable to resize on the fly: #{source.inspect}"
return return

View File

@ -66,9 +66,7 @@ module Locomotive
end end
def add_middlewares def add_middlewares
Rails.application.configure do |config| Rails.application.config.middleware.use '::Locomotive::Hosting::Bushido::Middleware'
config.middleware.use '::Locomotive::Hosting::Bushido::Middleware'
end
end end
# manage domains # manage domains

View File

@ -18,34 +18,30 @@ module Locomotive
end end
def call(env) def call(env)
if Locomotive.bushido? status, headers, response = @app.call(env)
status, headers, response = @app.call(env)
content = "" content = ""
response.each { |part| content += part } response.each { |part| content += part }
# "claiming" bar + stats ? # "claiming" bar + stats ?
content.gsub!(/<\/body>/i, <<-STR content.gsub!(/<\/body>/i, <<-STR
<script type="text/javascript"> <script type="text/javascript">
var _bushido_app = '#{@bushido_app_name}'; var _bushido_app = '#{@bushido_app_name}';
var _bushido_claimed = #{@bushido_claimed.to_s}; var _bushido_claimed = #{@bushido_claimed.to_s};
var _bushido_metrics_token = '#{@bushido_metrics_token}'; var _bushido_metrics_token = '#{@bushido_metrics_token}';
(function() { (function() {
var bushido = document.createElement('script'); bushido.type = 'text/javascript'; bushido.async = true; var bushido = document.createElement('script'); bushido.type = 'text/javascript'; bushido.async = true;
bushido.src = '#{BUSHIDO_JS_URL}?#{::Bushido::VERSION.gsub('.', '')}'; bushido.src = '#{BUSHIDO_JS_URL}?#{::Bushido::VERSION.gsub('.', '')}';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(bushido, s); var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(bushido, s);
})(); })();
</script> </script>
</body> </body>
STR STR
) )
headers['content-length'] = bytesize(content).to_s headers['content-length'] = bytesize(content).to_s
[status, headers, [content]] [status, headers, [content]]
else
@app.call(env)
end
end end
end end

View File

@ -42,8 +42,6 @@ module Locomotive
end end
def add_other_assets def add_other_assets
collection = AssetCollection.find_or_create_internal(site)
Dir[File.join(theme_path, 'public', 'samples', '*')].each do |asset_path| Dir[File.join(theme_path, 'public', 'samples', '*')].each do |asset_path|
next if File.directory?(asset_path) next if File.directory?(asset_path)
@ -52,7 +50,7 @@ module Locomotive
name = File.basename(asset_path, File.extname(asset_path)).parameterize('_') name = File.basename(asset_path, File.extname(asset_path)).parameterize('_')
collection.assets.create! :name => name, :source => File.open(asset_path) self.site.assets.create! :name => name, :source => File.open(asset_path)
end end
end end

View File

@ -43,7 +43,6 @@ module Locomotive
# invalidate the cache of the dynamic classes (custom fields) # invalidate the cache of the dynamic classes (custom fields)
site.content_types.all.collect { |c| c.invalidate_content_klass; c.fetch_content_klass } site.content_types.all.collect { |c| c.invalidate_content_klass; c.fetch_content_klass }
site.asset_collections.all.collect { |c| c.invalidate_content_klass; c.fetch_content_klass }
end end
protected protected

View File

@ -162,9 +162,9 @@ module Locomotive
def reset! def reset!
@site.pages.destroy_all @site.pages.destroy_all
@site.assets.destroy_all
@site.theme_assets.destroy_all @site.theme_assets.destroy_all
@site.content_types.destroy_all @site.content_types.destroy_all
@site.asset_collections.destroy_all
end end
def get_uploader(site) def get_uploader(site)

View File

@ -8,18 +8,18 @@ module Locomotive
class_inheritable_reader :liquid_attributes class_inheritable_reader :liquid_attributes
write_inheritable_attribute :liquid_attributes, [] write_inheritable_attribute :liquid_attributes, []
attr_reader :source attr_reader :_source
delegate :hash, :to => :source delegate :hash, :to => :_source
def initialize(source) def initialize(source)
unless source.nil? unless source.nil?
@source = source @_source = source
@liquid = liquid_attributes.flatten.inject({}) { |h, k| h.update k.to_s => @source.send(k) } @liquid = liquid_attributes.flatten.inject({}) { |h, k| h.update k.to_s => @_source.send(k) }
end end
end end
def id def id
(@source.respond_to?(:id) ? @source.id : nil) || 'new' (@_source.respond_to?(:id) ? @_source.id : nil) || 'new'
end end
def before_method(method) def before_method(method)

View File

@ -2,22 +2,22 @@ module Locomotive
module Liquid module Liquid
module Drops module Drops
class Content < Base class Content < Base
delegate :meta_keywords, :meta_description, :to => '@source' delegate :meta_keywords, :meta_description, :to => '_source'
def _id def _id
@source._id.to_s self._source._id.to_s
end end
def before_method(meth) def before_method(meth)
return '' if @source.nil? return '' if self._source.nil?
if not @@forbidden_attributes.include?(meth.to_s) if not @@forbidden_attributes.include?(meth.to_s)
value = @source.send(meth) value = self._source.send(meth)
end end
end end
def highlighted_field_value def highlighted_field_value
@source.highlighted_field_value self._source.highlighted_field_value
end end
end end

View File

@ -2,26 +2,26 @@ module Locomotive
module Liquid module Liquid
module Drops module Drops
class Page < Base class Page < Base
delegate :meta_keywords, :meta_description, :to => "@source" delegate :meta_keywords, :meta_description, :to => "_source"
def title def title
@source.templatized? ? @context['content_instance'].highlighted_field_value : @source.title self._source.templatized? ? @context['content_instance'].highlighted_field_value : self._source.title
end end
def slug def slug
@source.templatized? ? @source.content_type.slug.singularize : @source.slug self._source.templatized? ? self._source.content_type.slug.singularize : self._source.slug
end end
def children def children
@children ||= liquify(*@source.children) @children ||= liquify(*self._source.children)
end end
def fullpath def fullpath
@fullpath ||= @source.fullpath @fullpath ||= self._source.fullpath
end end
def depth def depth
@source.depth self._source.depth
end end
end end

View File

@ -6,11 +6,11 @@ module Locomotive
liquid_attributes << :name << :meta_keywords << :meta_description liquid_attributes << :name << :meta_keywords << :meta_description
def index def index
@index ||= @source.pages.root.first @index ||= self._source.pages.root.first
end end
def pages def pages
@pages ||= @source.pages.to_a.collect(&:to_liquid) @pages ||= self._source.pages.to_a.collect(&:to_liquid)
end end
end end

View File

@ -77,16 +77,15 @@ Factory.define :snippet do |s|
end end
## Theme assets ## ## Assets ##
Factory.define :theme_asset do |a| Factory.define :asset do |a|
a.site { Site.where(:subdomain => "acme").first || Factory(:site) } a.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end
## Asset collections ## ## Theme assets ##
Factory.define :asset_collection do |s| Factory.define :theme_asset do |a|
s.name 'Trip to Chicago' a.site { Site.where(:subdomain => "acme").first || Factory(:site) }
s.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end end

View File

@ -35,7 +35,7 @@ describe 'Heroku support' do
end end
it 'has a nil connection' do it 'has a nil connection' do
Locomotive.heroku_connection.should be_nil Locomotive.respond_to?(:heroku_connection).should be_false
end end
it 'tells heroku is disabled' do it 'tells heroku is disabled' do

View File

@ -0,0 +1,64 @@
require 'spec_helper'
describe Locomotive::Liquid::Filters::Resize do
before :each do
@site = Factory.create(:site)
@theme_asset = Factory.create(:theme_asset, :source => FixturedAsset.open('5k.png'), :site => @site)
@theme_asset_path = "/sites/#{@theme_asset.site_id}/theme/images/5k.png"
@asset = Factory.create(:asset, :source => FixturedAsset.open('5k.png'), :site => @site)
@asset_url = @asset.source.url
@asset_path = "/sites/#{@asset.site_id}/assets/#{@asset.id}/5k.png"
@context = Liquid::Context.new( { }, { 'asset_url' => @asset_url, 'theme_asset' => @theme_asset.to_liquid }, { :site => @site })
@app = Locomotive::Dragonfly.app
end
describe '#resize' do
context 'when an asset url string is given' do
before :each do
@template = Liquid::Template.parse('{{ asset_url | resize: "40x30" }}')
end
it 'should return the location of the resized image' do
@template.render(@context).should =~ /media\/.*\/5k.png/
end
it 'should use the path in the public folder to generate a location' do
@template.render(@context).should == @app.fetch_file("public#{@asset_path}").thumb('40x30').url
end
end
context 'when a theme asset is given' do
before :each do
@template = Liquid::Template.parse("{{ theme_asset | resize: '300x400' }}")
end
it 'should return the location of the resized image' do
@template.render(@context).should =~ /media\/.*\/5k.png/
end
it 'should use the path of the theme asset to generate a location' do
@template.render(@context).should == @app.fetch_file("public#{@theme_asset_path}").thumb('300x400').url
end
end
context 'when no resize string is given' do
before :each do
@template = Liquid::Template.parse('{{ asset | resize: }}')
end
it 'should return a liquid error' do
@template.render(@context).should include 'Liquid error: wrong number of arguments'
end
end
end
end

View File

@ -51,7 +51,8 @@ describe 'Locomotive rendering system' do
end end
it 'sets the status to 404 not found when no page is found' do it 'sets the status to 404 not found when no page is found' do
@controller.expects(:not_found_page).returns(@page) # @controller.expects(:not_found_page).returns(@page)
@page.stubs(:not_found?).returns(true)
@controller.send(:prepare_and_set_response, 'Hello world !') @controller.send(:prepare_and_set_response, 'Hello world !')
@controller.status.should == :not_found @controller.status.should == :not_found
end end

View File

@ -1,186 +0,0 @@
require 'spec_helper'
describe AssetCollection do
it 'should have a valid factory' do
Factory.build(:asset_collection).should be_valid
end
describe 'custom fields (beta)' do
before(:each) do
Site.any_instance.stubs(:create_default_pages!).returns(true)
site = Factory.build(:site)
Site.stubs(:find).returns(site)
@collection = Factory.build(:asset_collection, :site => site)
@collection.asset_custom_fields.build :label => 'My Description', :_alias => 'description', :kind => 'Text'
@collection.asset_custom_fields.build :label => 'Active', :kind => 'Boolean'
# AssetCollection.logger = Logger.new($stdout)
# AssetCollection.db.connection.instance_variable_set(:@logger, Logger.new($stdout))
end
context 'unit' do
before(:each) do
@field = CustomFields::Field.new(:kind => 'String')
end
it 'should tell if it is a String' do
@field.string?.should be_true
end
it 'should tell if it is a Text' do
@field.kind = 'Text'
@field.text?.should be_true
end
end
context 'validation' do
%w{label kind}.each do |key|
it "should validate presence of #{key}" do
field = @collection.asset_custom_fields.build({ :label => 'Shortcut', :kind => 'String' }.merge(key.to_sym => nil))
field.should_not be_valid
field.errors[key.to_sym].should == ["can't be blank"]
end
end
it 'should not have unique label' do
field = @collection.asset_custom_fields.build :label => 'Active', :kind => 'Boolean'
field.should_not be_valid
field.errors[:label].should == ["is already taken"]
end
it 'should invalidate parent if custom field is not valid' do
field = @collection.asset_custom_fields.build
@collection.should_not be_valid
@collection.asset_custom_fields.last.errors[:label].should == ["can't be blank"]
end
end
context 'define core attributes' do
it 'should have an unique name' do
@collection.asset_custom_fields.first._name.should == "custom_field_1"
@collection.asset_custom_fields.last._name.should == "custom_field_2"
end
it 'should have an unique alias' do
@collection.asset_custom_fields.first.safe_alias.should == "description"
@collection.asset_custom_fields.last.safe_alias.should == "active"
end
end
context 'build and save' do
it 'should build asset' do
asset = @collection.assets.build
lambda {
asset.description
asset.active
asset.custom_fields.size.should == 2
}.should_not raise_error
end
it 'should assign values to newly built asset' do
asset = build_asset(@collection)
asset.description.should == 'Lorem ipsum'
asset.active.should == true
end
it 'should save asset' do
asset = build_asset(@collection)
asset.save and @collection.reload
asset = @collection.assets.first
asset.description.should == 'Lorem ipsum'
asset.active.should == true
end
it 'should not modify assets from another collection' do
asset = build_asset(@collection)
asset.save and @collection.reload
new_collection = AssetCollection.new
lambda { new_collection.assets.build.description }.should raise_error
end
end
context 'modifying fields' do
before(:each) do
@asset = build_asset(@collection).save
end
it 'should add new field' do
@collection.asset_custom_fields.build :label => 'Active at', :name => 'active_at', :kind => 'Date'
@collection.upsert(:validate => false)
@collection.invalidate_asset_klass
@collection.reload
asset = @collection.assets.first
lambda { asset.active_at }.should_not raise_error
end
it 'should remove field' do
@collection.asset_custom_fields.clear
@collection.upsert(:validate => false)
@collection.invalidate_asset_klass
@collection.reload
asset = @collection.assets.first
lambda { asset.active_at }.should raise_error
end
it 'should rename field label' do
@collection.asset_custom_fields.first.label = 'Simple description'
@collection.asset_custom_fields.first._alias = nil
@collection.upsert(:validate => false)
@collection.invalidate_asset_klass
@collection.reload
asset = @collection.assets.first
asset.simple_description.should == 'Lorem ipsum'
end
end
context 'managing from hash' do
it 'adds new field' do
@collection.asset_custom_fields.clear
field = @collection.asset_custom_fields.build :label => 'Title'
@collection.asset_custom_fields_attributes = { 0 => { :id => field.id.to_s, 'label' => 'A title', 'kind' => 'String' }, 1 => { 'label' => 'Tagline', 'kind' => 'String' } }
@collection.asset_custom_fields.size.should == 2
@collection.asset_custom_fields.first.label.should == 'A title'
@collection.asset_custom_fields.last.label.should == 'Tagline'
end
it 'updates/removes fields' do
field = @collection.asset_custom_fields.build :label => 'Title', :kind => 'String'
@collection.save; @collection = AssetCollection.find(@collection.id)
@collection.update_attributes(:asset_custom_fields_attributes => {
'0' => { 'id' => lookup_field_id(0), 'label' => 'My Description', 'kind' => 'Text', '_destroy' => '1' },
'1' => { 'id' => lookup_field_id(1), 'label' => 'Active', 'kind' => 'Boolean', '_destroy' => '1' },
'2' => { 'id' => lookup_field_id(2), 'label' => 'My Title !', 'kind' => 'String' },
'new_record' => { 'label' => 'Published at', 'kind' => 'String' }
})
@collection = AssetCollection.find(@collection.id)
@collection.asset_custom_fields.size.should == 2
@collection.asset_custom_fields.first.label.should == 'My Title !'
end
end
end
def build_asset(collection)
collection.assets.build(:name => 'Asset on steroids', :description => 'Lorem ipsum', :active => true)
end
def lookup_field_id(index)
@collection.asset_custom_fields.all[index].id.to_s
end
end

46
spec/models/asset_spec.rb Normal file
View File

@ -0,0 +1,46 @@
# coding: utf-8
require 'spec_helper'
describe Asset do
describe 'attaching a file' do
before(:each) do
Asset.any_instance.stubs(:site_id).returns('test')
@asset = Factory.build(:asset)
end
it 'should process picture' do
@asset.source = FixturedAsset.open('5k.png')
@asset.source.file.content_type.should_not be_nil
@asset.image?.should be_true
end
it 'should get width and height from the image' do
@asset.source = FixturedAsset.open('5k.png')
@asset.width.should == 32
@asset.height.should == 32
end
end
describe 'vignette' do
before(:each) do
@asset = Factory.build(:asset, :source => FixturedAsset.open('5k.png'))
end
it 'does not resize image smaller than 50x50' do
@asset.vignette_url.should =~ /^\/spec\/.*\/5k.png/
end
it 'has any possible resized versions' do
@asset.stubs(:with).returns(81)
@asset.stubs(:height).returns(81)
@asset.vignette_url.should =~ /^\/media\/.*\/5k.png/
end
end
end

View File

@ -43,16 +43,16 @@ describe ContentType do
content_type.should_not be_valid content_type.should_not be_valid
content_type.errors[:content_custom_fields].should == ["is too small (minimum element number is 1)"] content_type.errors[:content_custom_fields].should == ["is too small (minimum element number is 1)"]
end end
%w(created_at updated_at).each do |_alias| %w(created_at updated_at).each do |_alias|
it "does not allow #{_alias} as alias" do it "does not allow #{_alias} as alias" do
content_type = Factory.build(:content_type) content_type = Factory.build(:content_type)
field = content_type.content_custom_fields.build :label => 'anything', :kind => 'String', :_alias => _alias field = content_type.content_custom_fields.build :label => 'anything', :kind => 'String', :_alias => _alias
field.valid?.should be_false field.valid?.should be_false
field.errors[:_alias].should == ['is reserved'] field.errors[:_alias].should == ['is reserved']
end end
end end
end end
context '#ordered_contents' do context '#ordered_contents' do
@ -92,4 +92,180 @@ describe ContentType do
end end
describe 'custom fields' do
before(:each) do
site = Factory.build(:site)
Site.stubs(:find).returns(site)
@content_type = Factory.build(:content_type, :site => site, :highlighted_field_name => 'custom_field_1')
@content_type.content_custom_fields.build :label => 'My Description', :_alias => 'description', :kind => 'text'
@content_type.content_custom_fields.build :label => 'Active', :kind => 'boolean'
# ContentType.logger = Logger.new($stdout)
# ContentType.db.connection.instance_variable_set(:@logger, Logger.new($stdout))
end
context 'unit' do
before(:each) do
@field = CustomFields::Field.new(:kind => 'String')
end
it 'should tell if it is a String' do
@field.string?.should be_true
end
it 'should tell if it is a Text' do
@field.kind = 'Text'
@field.text?.should be_true
end
end
context 'validation' do
%w{label kind}.each do |key|
it "should validate presence of #{key}" do
field = @content_type.content_custom_fields.build({ :label => 'Shortcut', :kind => 'String' }.merge(key.to_sym => nil))
field.should_not be_valid
field.errors[key.to_sym].should == ["can't be blank"]
end
end
it 'should not have unique label' do
field = @content_type.content_custom_fields.build :label => 'Active', :kind => 'Boolean'
field.should_not be_valid
field.errors[:label].should == ["is already taken"]
end
it 'should invalidate parent if custom field is not valid' do
field = @content_type.content_custom_fields.build
@content_type.should_not be_valid
@content_type.content_custom_fields.last.errors[:label].should == ["can't be blank"]
end
end
context 'define core attributes' do
it 'should have an unique name' do
@content_type.content_custom_fields.first._name.should == "custom_field_1"
@content_type.content_custom_fields.last._name.should == "custom_field_2"
end
it 'should have an unique alias' do
@content_type.content_custom_fields.first.safe_alias.should == "description"
@content_type.content_custom_fields.last.safe_alias.should == "active"
end
end
context 'build and save' do
it 'should build asset' do
asset = @content_type.contents.build
lambda {
asset.description
asset.active
asset.custom_fields.size.should == 2
}.should_not raise_error
end
it 'should assign values to newly built asset' do
asset = build_content(@content_type)
asset.description.should == 'Lorem ipsum'
asset.active.should == true
end
it 'should save asset' do
asset = build_content(@content_type)
asset.save and @content_type.reload
asset = @content_type.contents.first
asset.description.should == 'Lorem ipsum'
asset.active.should == true
end
it 'should not modify contents from another collection' do
asset = build_content(@content_type)
asset.save and @content_type.reload
new_collection = ContentType.new
lambda { new_collection.contents.build.description }.should raise_error
end
end
context 'modifying fields' do
before(:each) do
@asset = build_content(@content_type).save
end
it 'should add new field' do
@content_type.content_custom_fields.build :label => 'Active at', :name => 'active_at', :kind => 'Date'
@content_type.upsert(:validate => false)
@content_type.invalidate_content_klass
@content_type.reload
asset = @content_type.contents.first
lambda { asset.active_at }.should_not raise_error
end
it 'should remove field' do
@content_type.content_custom_fields.clear
@content_type.upsert(:validate => false)
@content_type.invalidate_content_klass
@content_type.reload
asset = @content_type.contents.first
lambda { asset.active_at }.should raise_error
end
it 'should rename field label' do
@content_type.content_custom_fields.first.label = 'Simple description'
@content_type.content_custom_fields.first._alias = nil
@content_type.upsert(:validate => false)
@content_type.invalidate_content_klass
@content_type.reload
asset = @content_type.contents.first
asset.simple_description.should == 'Lorem ipsum'
end
end
context 'managing from hash' do
it 'adds new field' do
@content_type.content_custom_fields.clear
field = @content_type.content_custom_fields.build :label => 'Title'
@content_type.content_custom_fields_attributes = { 0 => { :id => field.id.to_s, 'label' => 'A title', 'kind' => 'String' }, 1 => { 'label' => 'Tagline', 'kind' => 'String' } }
@content_type.content_custom_fields.size.should == 2
@content_type.content_custom_fields.first.label.should == 'A title'
@content_type.content_custom_fields.last.label.should == 'Tagline'
end
it 'updates/removes fields' do
field = @content_type.content_custom_fields.build :label => 'Title', :kind => 'String'
@content_type.save; @content_type = ContentType.find(@content_type.id)
@content_type.update_attributes(:content_custom_fields_attributes => {
'0' => { 'id' => lookup_field_id(0), 'label' => 'My Description', 'kind' => 'Text', '_destroy' => '1' },
'1' => { 'id' => lookup_field_id(1), 'label' => 'Active', 'kind' => 'Boolean', '_destroy' => '1' },
'2' => { 'id' => lookup_field_id(2), 'label' => 'My Title !', 'kind' => 'String' },
'new_record' => { 'label' => 'Published at', 'kind' => 'String' }
})
@content_type = ContentType.find(@content_type.id)
@content_type.content_custom_fields.size.should == 2
@content_type.content_custom_fields.first.label.should == 'My Title !'
end
end
end
def build_content(content_type)
content_type.contents.build(:name => 'Asset on steroids', :description => 'Lorem ipsum', :active => true)
end
def lookup_field_id(index)
@content_type.content_custom_fields.all[index].id.to_s
end
end end

View File

@ -2,7 +2,6 @@ require 'carrierwave/test/matchers'
CarrierWave.configure do |config| CarrierWave.configure do |config|
config.storage = :file config.storage = :file
# config.store_dir = "spec/tmp/uploads"
config.cache_dir = "spec/tmp/cache" config.cache_dir = "spec/tmp/cache"
config.root = File.join(Rails.root, 'spec', 'tmp') config.root = File.join(Rails.root, 'spec', 'tmp')
end end