solidus_tax_cloud 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +35 -0
- data/.gem_release.yml +5 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rubocop.yml +7 -0
- data/.rubocop_todo.yml +132 -0
- data/CHANGELOG.md +52 -0
- data/Gemfile +33 -0
- data/LICENSE +26 -0
- data/README.md +130 -0
- data/Rakefile +6 -0
- data/TaxCloudImplementationVerificationGuide.pdf +0 -0
- data/app/assets/javascripts/spree/backend/solidus_tax_cloud.js +2 -0
- data/app/assets/javascripts/spree/frontend/solidus_tax_cloud.js +2 -0
- data/app/assets/stylesheets/spree/backend/solidus_tax_cloud.css +4 -0
- data/app/assets/stylesheets/spree/backend/solidus_tax_cloud.scss +0 -0
- data/app/assets/stylesheets/spree/frontend/solidus_tax_cloud.css +4 -0
- data/app/decorators/models/solidus_tax_cloud/spree/app_configuration_decorator.rb +18 -0
- data/app/decorators/models/solidus_tax_cloud/spree/line_item_decorator.rb +47 -0
- data/app/decorators/models/solidus_tax_cloud/spree/order_decorator.rb +42 -0
- data/app/decorators/models/solidus_tax_cloud/spree/product_decorator.rb +25 -0
- data/app/decorators/models/solidus_tax_cloud/spree/shipment_decorator.rb +27 -0
- data/app/models/spree/calculator/tax_cloud_calculator.rb +111 -0
- data/app/models/spree/tax_cloud.rb +66 -0
- data/app/overrides/spree/admin/products/_form.rb +9 -0
- data/app/overrides/spree/admin/shared/_configuration_menu.rb +9 -0
- data/bin/console +17 -0
- data/bin/r +15 -0
- data/bin/rails +15 -0
- data/bin/rake +7 -0
- data/bin/sandbox +84 -0
- data/bin/sandbox_rails +18 -0
- data/bin/setup +8 -0
- data/config/initializers/tax_cloud_usps_username.rb +5 -0
- data/config/locales/en.yml +13 -0
- data/config/routes.rb +7 -0
- data/db/migrate/20121220192438_create_spree_tax_cloud_transactions.rb +13 -0
- data/db/migrate/20121220193944_create_spree_tax_cloud_cart_items.rb +22 -0
- data/db/migrate/20130829215819_fix_scale_of_cart_item_prices.rb +15 -0
- data/db/migrate/20140623225628_add_tic_to_products.rb +11 -0
- data/lib/assets/javascripts/spree/frontend/solidus_tax_cloud.js.erb +1 -0
- data/lib/assets/stylesheets/spree/frontend/solidus_tax_cloud.css.erb +5 -0
- data/lib/controllers/backend/spree/admin/tax_cloud_settings_controller.rb +29 -0
- data/lib/decorators/backend/controllers/solidus_tax_cloud/spree/admin/orders_controller_decorator.rb +21 -0
- data/lib/decorators/frontend/controllers/solidus_tax_cloud/spree/checkout_controller_decorator.rb +24 -0
- data/lib/generators/solidus_tax_cloud/install/install_generator.rb +29 -0
- data/lib/generators/solidus_tax_cloud/templates/ca-bundle.crt +3895 -0
- data/lib/solidus_tax_cloud.rb +10 -0
- data/lib/solidus_tax_cloud/engine.rb +23 -0
- data/lib/solidus_tax_cloud/error.rb +6 -0
- data/lib/solidus_tax_cloud/factories.rb +4 -0
- data/lib/solidus_tax_cloud/version.rb +5 -0
- data/lib/tasks/.gitkeep +0 -0
- data/lib/tasks/tax_cloud.rake +74 -0
- data/lib/views/backend/spree/admin/products/_edit_tax_cloud_tic.html.erb +6 -0
- data/lib/views/backend/spree/admin/tax_cloud_settings/edit.html.erb +32 -0
- data/solidus_tax_cloud.gemspec +39 -0
- data/spec/features/checkout_spec.rb +511 -0
- data/spec/models/tax_cloud_api_spec.rb +192 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/capybara.rb +20 -0
- data/spec/support/tax_cloud.rb +20 -0
- data/spec/support/transactions.rb +5 -0
- metadata +204 -0
data/Rakefile
ADDED
Binary file
|
File without changes
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusTaxCloud
|
4
|
+
module Spree
|
5
|
+
module AppConfigurationDecorator
|
6
|
+
def self.prepended(base)
|
7
|
+
base.class_eval do
|
8
|
+
preference :taxcloud_default_product_tic, :string, default: '00000'
|
9
|
+
preference :taxcloud_shipping_tic, :string, default: '11010'
|
10
|
+
end
|
11
|
+
|
12
|
+
Rails.application.config.spree.calculators.tax_rates << ::Spree::Calculator::TaxCloudCalculator
|
13
|
+
end
|
14
|
+
|
15
|
+
::Spree::AppConfiguration.prepend self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusTaxCloud
|
4
|
+
module Spree
|
5
|
+
module LineItemDecorator
|
6
|
+
def tax_cloud_cache_key
|
7
|
+
if ActiveRecord::Base.try(:cache_versioning)
|
8
|
+
cache_key
|
9
|
+
else
|
10
|
+
key = "Spree::LineItem #{id}: #{quantity}x<#{variant.cache_key}>@#{total_excluding_vat}#{currency}"
|
11
|
+
|
12
|
+
if order.ship_address
|
13
|
+
key += "shipped_to<#{order.ship_address.try(:cache_key)}>"
|
14
|
+
elsif order.billing_address
|
15
|
+
key += "billed_to<#{order.bill_address.try(:cache_key)}>"
|
16
|
+
end
|
17
|
+
|
18
|
+
key
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def tax_cloud_cache_version
|
23
|
+
if ActiveRecord::Base.try(:cache_versioning)
|
24
|
+
key = "Spree::LineItem #{id}: #{quantity}x<#{variant.cache_version}>@#{total_excluding_vat}#{currency}"
|
25
|
+
|
26
|
+
if order.ship_address
|
27
|
+
key += "shipped_to<#{order.ship_address.try(:cache_version)}>"
|
28
|
+
elsif order.billing_address
|
29
|
+
key += "billed_to<#{order.bill_address.try(:cache_version)}>"
|
30
|
+
end
|
31
|
+
|
32
|
+
key
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def price_with_discounts
|
37
|
+
round_to_two_places(total_excluding_vat / quantity)
|
38
|
+
end
|
39
|
+
|
40
|
+
def round_to_two_places(amount)
|
41
|
+
BigDecimal(amount.to_s).round(2, BigDecimal::ROUND_HALF_UP)
|
42
|
+
end
|
43
|
+
|
44
|
+
::Spree::LineItem.prepend self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusTaxCloud
|
4
|
+
module Spree
|
5
|
+
module OrderDecorator
|
6
|
+
def self.prepended(base)
|
7
|
+
base.class_eval do
|
8
|
+
state_machine.after_transition to: :complete, do: :capture_tax_cloud
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def capture_tax_cloud
|
13
|
+
return unless is_taxed_using_tax_cloud?
|
14
|
+
|
15
|
+
response = ::Spree::TaxCloud.transaction_from_order(self).authorized_with_capture
|
16
|
+
if response != 'OK'
|
17
|
+
Rails.logger.error "ERROR: TaxCloud returned an order capture response of #{response}."
|
18
|
+
end
|
19
|
+
log_tax_cloud(response)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Order.tax_zone.tax_rates is used here to check if the order is taxable by Tax Cloud.
|
23
|
+
# It's not possible check against the order's tax adjustments because
|
24
|
+
# an adjustment is not created for 0% rates. However, US orders must be
|
25
|
+
# submitted to Tax Cloud even when the rate is 0%.
|
26
|
+
# Note that we explicitly use ship_address instead of tax_address,
|
27
|
+
# as per compliance with Tax Cloud instructions.
|
28
|
+
def is_taxed_using_tax_cloud?
|
29
|
+
::Spree::TaxRate.for_address(ship_address).any? { |rate| rate.calculator_type == 'Spree::Calculator::TaxCloudCalculator' }
|
30
|
+
end
|
31
|
+
|
32
|
+
def log_tax_cloud(response)
|
33
|
+
# Implement into your own application.
|
34
|
+
# You could create your own Log::TaxCloud model then use either HStore or
|
35
|
+
# JSONB to store the response.
|
36
|
+
# The response argument carries the response from an order transaction.
|
37
|
+
end
|
38
|
+
|
39
|
+
::Spree::Order.prepend self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusTaxCloud
|
4
|
+
module Spree
|
5
|
+
module ProductDecorator
|
6
|
+
def self.prepended(base)
|
7
|
+
base.class_eval do
|
8
|
+
validates_format_of :tax_cloud_tic, with: /\A\d{5}\z/, message: I18n.t('spree.standard_taxcloud_tic')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Use the store-default TaxCloud product TIC if none is defined for this product
|
13
|
+
def tax_cloud_tic
|
14
|
+
read_attribute(:tax_cloud_tic) || ::Spree::Config.taxcloud_default_product_tic
|
15
|
+
end
|
16
|
+
|
17
|
+
# Empty strings are written as nil (which avoids the format validation)
|
18
|
+
def tax_cloud_tic=(tic)
|
19
|
+
write_attribute(:tax_cloud_tic, tic.presence)
|
20
|
+
end
|
21
|
+
|
22
|
+
::Spree::Product.prepend self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolidusTaxCloud
|
4
|
+
module Spree
|
5
|
+
module ShipmentDecorator
|
6
|
+
def tax_cloud_cache_key
|
7
|
+
if ActiveRecord::Base.try(:cache_versioning)
|
8
|
+
cache_key
|
9
|
+
else
|
10
|
+
"#{cache_key}--from:#{stock_location.cache_key}--to:#{order.shipping_address.cache_key}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def tax_cloud_cache_version
|
15
|
+
if ActiveRecord::Base.try(:cache_versioning)
|
16
|
+
"#{cache_version}--from:#{stock_location.cache_version}--to:#{order.shipping_address.cache_version}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def price_with_discounts
|
21
|
+
total_excluding_vat
|
22
|
+
end
|
23
|
+
|
24
|
+
::Spree::Shipment.prepend self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class Calculator::TaxCloudCalculator < Calculator::DefaultTax
|
5
|
+
def self.description
|
6
|
+
I18n.t('spree.tax_cloud')
|
7
|
+
end
|
8
|
+
|
9
|
+
# Default tax calculator still needs to support orders for legacy reasons
|
10
|
+
# Orders created before Spree 2.1 had tax adjustments applied to the order, as a whole.
|
11
|
+
# Orders created with Spree 2.2 and after, have them applied to the line items individually.
|
12
|
+
def compute_order(_order)
|
13
|
+
raise 'Spree::TaxCloud is designed to calculate taxes at the shipment and line-item levels.'
|
14
|
+
end
|
15
|
+
|
16
|
+
# When it comes to computing shipments or line items: same same.
|
17
|
+
def compute_shipment_or_line_item(item)
|
18
|
+
if rate.included_in_price
|
19
|
+
raise 'TaxCloud cannot calculate inclusive sales taxes.'
|
20
|
+
else
|
21
|
+
round_to_two_places(tax_for_item(item))
|
22
|
+
# TODO: take discounted_amount into account. This is a problem because TaxCloud API does not take discounts nor does it return percentage rates.
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
alias compute_shipment compute_shipment_or_line_item
|
27
|
+
alias compute_line_item compute_shipment_or_line_item
|
28
|
+
|
29
|
+
def compute_shipping_rate(_shipping_rate)
|
30
|
+
if rate.included_in_price
|
31
|
+
raise 'TaxCloud cannot calculate inclusive sales taxes.'
|
32
|
+
else
|
33
|
+
# Sales tax will be applied to the Shipment itself, rather than to the Shipping Rates.
|
34
|
+
# Note that this method is called from ShippingRate.display_price, so if we returned
|
35
|
+
# the shipping sales tax here, it would display as part of the display_price of the
|
36
|
+
# ShippingRate, which is not consistent with how US sales tax typically works -- i.e.,
|
37
|
+
# it is an additional amount applied to a sale at the end, rather than being part of
|
38
|
+
# the displayed cost of a good or service.
|
39
|
+
0
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def tax_for_item(item)
|
46
|
+
order = item.order
|
47
|
+
item_address = order.ship_address || order.billing_address
|
48
|
+
|
49
|
+
# Only calculate tax when we have an address and it's in our jurisdiction
|
50
|
+
return 0 unless item_address.present? && calculable.zone.include?(item_address)
|
51
|
+
|
52
|
+
# Cache will expire if the order, any of its line items, or any of its shipments change.
|
53
|
+
# When the cache expires, we will need to make another API call to TaxCloud.
|
54
|
+
Rails.cache.fetch(
|
55
|
+
['TaxCloudRatesForItem', item.tax_cloud_cache_key],
|
56
|
+
version: item.tax_cloud_cache_version,
|
57
|
+
time_to_idle: 30.minutes,
|
58
|
+
) do
|
59
|
+
# In the case of a cache miss, we recompute the amounts for _all_ the LineItems and Shipments for this Order.
|
60
|
+
# TODO An ideal implementation will break the order down by Shipments / Packages
|
61
|
+
# and use the actual StockLocation address for each separately, and create Adjustments
|
62
|
+
# for the Shipments to reflect tax on shipping.
|
63
|
+
transaction = Spree::TaxCloud.transaction_from_order(order)
|
64
|
+
lookup_cart_items = transaction.lookup.cart_items
|
65
|
+
|
66
|
+
# Now we will loop back through the items and assign them amounts from the lookup.
|
67
|
+
# This inefficient method is due to the fact that item_id isn't preserved in the lookup.
|
68
|
+
# TODO There may be a way to refactor this,
|
69
|
+
# possibly by overriding the TaxCloud::Responses::Lookup model
|
70
|
+
# or the CartItems model.
|
71
|
+
index = -1 # array is zero-indexed
|
72
|
+
|
73
|
+
item_tax_amount = nil
|
74
|
+
|
75
|
+
# Retrieve line_items from lookup
|
76
|
+
order.line_items.each do |line_item|
|
77
|
+
tax_amount = lookup_cart_items[index += 1].tax_amount
|
78
|
+
|
79
|
+
if line_item == item
|
80
|
+
item_tax_amount = tax_amount
|
81
|
+
else
|
82
|
+
Rails.cache.write(
|
83
|
+
['TaxCloudRatesForItem', line_item.tax_cloud_cache_key],
|
84
|
+
tax_amount,
|
85
|
+
version: line_item.tax_cloud_cache_version,
|
86
|
+
time_to_idle: 30.minutes,
|
87
|
+
)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
order.shipments.each do |shipment|
|
92
|
+
tax_amount = lookup_cart_items[index += 1].tax_amount
|
93
|
+
|
94
|
+
if shipment == item
|
95
|
+
item_tax_amount = tax_amount
|
96
|
+
else
|
97
|
+
Rails.cache.write(
|
98
|
+
['TaxCloudRatesForItem', shipment.tax_cloud_cache_key],
|
99
|
+
tax_amount,
|
100
|
+
version: shipment.tax_cloud_cache_version,
|
101
|
+
time_to_idle: 30.minutes,
|
102
|
+
)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Lastly, return the particular rate that we were initially looking for
|
107
|
+
item_tax_amount
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class TaxCloud
|
5
|
+
def self.transaction_from_order(order)
|
6
|
+
stock_location = order.shipments.first.try(:stock_location) || Spree::StockLocation.active.where('city IS NOT NULL and state_id IS NOT NULL').first
|
7
|
+
raise I18n.t('spree.ensure_one_valid_stock_location') unless stock_location
|
8
|
+
|
9
|
+
destination = address_from_spree_address(order.ship_address || order.billing_address)
|
10
|
+
begin
|
11
|
+
destination = destination.verify # Address validation may fail, and that is okay.
|
12
|
+
rescue ::TaxCloud::Errors::ApiError
|
13
|
+
end
|
14
|
+
|
15
|
+
transaction = ::TaxCloud::Transaction.new(
|
16
|
+
customer_id: order.user_id || order.email,
|
17
|
+
order_id: order.number,
|
18
|
+
cart_id: order.number,
|
19
|
+
origin: address_from_spree_address(stock_location),
|
20
|
+
destination: destination
|
21
|
+
)
|
22
|
+
|
23
|
+
index = -1 # array is zero-indexed
|
24
|
+
# Prepare line_items for lookup
|
25
|
+
order.line_items.each { |line_item| transaction.cart_items << cart_item_from_item(line_item, index += 1) }
|
26
|
+
# Prepare shipments for lookup
|
27
|
+
order.shipments.each { |shipment| transaction.cart_items << cart_item_from_item(shipment, index += 1) }
|
28
|
+
transaction
|
29
|
+
end
|
30
|
+
|
31
|
+
# Note that this method can take either a Spree::StockLocation (which has address
|
32
|
+
# attributes directly on it) or a Spree::Address object
|
33
|
+
def self.address_from_spree_address(address)
|
34
|
+
::TaxCloud::Address.new(
|
35
|
+
address1: address.address1,
|
36
|
+
address2: address.address2,
|
37
|
+
city: address.city,
|
38
|
+
state: address.try(:state).try(:abbr),
|
39
|
+
zip5: address.zipcode.try(:[], 0...5)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.cart_item_from_item(item, index)
|
44
|
+
case item
|
45
|
+
when Spree::LineItem
|
46
|
+
::TaxCloud::CartItem.new(
|
47
|
+
index: index,
|
48
|
+
item_id: item.try(:variant).try(:sku).presence || "LineItem #{item.id}",
|
49
|
+
tic: (item.product.tax_cloud_tic || Spree::Config.taxcloud_default_product_tic),
|
50
|
+
price: item.price_with_discounts,
|
51
|
+
quantity: item.quantity
|
52
|
+
)
|
53
|
+
when Spree::Shipment
|
54
|
+
::TaxCloud::CartItem.new(
|
55
|
+
index: index,
|
56
|
+
item_id: "Shipment #{item.number}",
|
57
|
+
tic: Spree::Config.taxcloud_shipping_tic,
|
58
|
+
price: item.price_with_discounts,
|
59
|
+
quantity: 1
|
60
|
+
)
|
61
|
+
else
|
62
|
+
raise I18n.t('spree.cart_item_cannot_be_made')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Deface::Override.new(
|
4
|
+
virtual_path: 'spree/admin/products/_form',
|
5
|
+
name: 'add_tic_to_admin_product_edit',
|
6
|
+
insert_after: "[data-hook='admin_product_form_tax_category']",
|
7
|
+
partial: 'spree/admin/products/edit_tax_cloud_tic',
|
8
|
+
original: 'a6d7d1941bde020c34025a78466febd8453cf71a'
|
9
|
+
)
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Deface::Override.new(
|
4
|
+
virtual_path: 'spree/admin/shared/_configuration_menu',
|
5
|
+
name: 'add_tax_cloud_admin_menu_link',
|
6
|
+
insert_bottom: "[data-hook='admin_configurations_sidebar_menu']",
|
7
|
+
text: "<%= configurations_sidebar_menu_item 'TaxCloud Settings', edit_admin_tax_cloud_settings_path %>",
|
8
|
+
original: 'dcaffe234d5b43d5b7673bc13f227500957c9e73'
|
9
|
+
)
|
data/bin/console
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require "bundler/setup"
|
6
|
+
require "solidus_tax_cloud"
|
7
|
+
|
8
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
9
|
+
# with your gem easier. You can also use a different console, if you like.
|
10
|
+
$LOAD_PATH.unshift(*Dir["#{__dir__}/../app/*"])
|
11
|
+
|
12
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
13
|
+
# require "pry"
|
14
|
+
# Pry.start
|
15
|
+
|
16
|
+
require "irb"
|
17
|
+
IRB.start(__FILE__)
|
data/bin/r
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
5
|
+
# installed from the root of your application.
|
6
|
+
|
7
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
8
|
+
ENGINE_PATH = File.expand_path('../lib/solidus_tax_cloud/engine', __dir__)
|
9
|
+
|
10
|
+
# Set up gems listed in the Gemfile.
|
11
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
12
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
13
|
+
|
14
|
+
require 'rails/all'
|
15
|
+
require 'rails/engine/commands'
|
data/bin/rails
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
app_root = 'spec/dummy'
|
6
|
+
|
7
|
+
unless File.exist? "#{app_root}/bin/rails"
|
8
|
+
system "bin/rake", app_root or begin # rubocop:disable Style/AndOr
|
9
|
+
warn "Automatic creation of the dummy app failed"
|
10
|
+
exit 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir.chdir app_root
|
15
|
+
exec 'bin/rails', *ARGV
|