diff --git a/.travis.yml b/.travis.yml
index b9b187ea6a8..241ead6f1c8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,11 +21,5 @@ notifications:
skip_join: true
channels:
- "irc.freenode.org#spree"
-branches:
- only:
- - 1-0-stable
- - 1-1-stable
- - master
rvm:
- - 1.8.7
- - 1.9.3
+ - 1.9.3
\ No newline at end of file
diff --git a/README.md b/README.md
index 2851fd10356..df308004827 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,3 @@
-**THIS README IS FOR THE MASTER BRANCH OF SPREE AND REFLECTS THE WORK CURRENTLY EXISTING ON THE MASTER BRANCH. IF YOU ARE WISHING TO USE A NON-MASTER BRANCH OF
-SPREE, PLEASE CONSULT THAT BRANCH'S README AND NOT THIS ONE.**
-
SUMMARY
-------
@@ -32,9 +29,9 @@ Installation
The fastest way to get started is by using the spree command line tool
available in the spree gem which will add Spree to an existing Rails application.
- $ gem install rails -v 3.2.7
+ $ gem install rails -v 3.2.9
$ gem install spree
- $ rails _3.2.7_ new my_store
+ $ rails _3.2.9_ new my_store
$ spree install my_store
This will add the Spree gem to your Gemfile, create initializers, copy migrations and
@@ -100,21 +97,21 @@ The source code is essentially a collection of gems. Spree is meant to be run w
1. Clone the Git repo
- git clone git://github.com/spree/spree.git
- cd spree
+ $ git clone git://github.com/spree/spree.git
+ $ cd spree
2. Install the gem dependencies
- bundle install
+ $ bundle install
3. Create a sandbox Rails application for testing purposes (and automatically perform all necessary database setup)
- bundle exec rake sandbox
+ $ bundle exec rake sandbox
4. Start the server
- cd sandbox
- rails server
+ $ cd sandbox
+ $ rails server
Performance
-----------
@@ -134,7 +131,7 @@ Use Dedicated Spree Devise Authentication
Add the following to your Gemfile
$ gem 'spree_auth_devise', :git => 'git://github.com/spree/spree_auth_devise'
-
+
Then run `bundle install`. Authentication will then work exactly as it did in previous versions of Spree.
If you're installing this in a new Spree 1.2+ application, you'll need to install and run the migrations with
@@ -142,6 +139,14 @@ If you're installing this in a new Spree 1.2+ application, you'll need to instal
$ bundle exec rake spree_auth:install:migrations
$ bundle exec rake db:migrate
+change the following line in `config/initializers/spree.rb`
+```ruby
+Spree.user_class = "Spree::LegacyUser"
+```
+to
+```ruby
+Spree.user_class = "Spree::User"
+```
and then run `bundle exec rake spree_auth:admin:create` in order to set up the admin user for the application.
Running Tests
diff --git a/Rakefile b/Rakefile
index 5e1df6ac5a7..8ca3d1053f5 100644
--- a/Rakefile
+++ b/Rakefile
@@ -87,5 +87,7 @@ end
desc "Creates a sandbox application for simulating the Spree code in a deployed Rails app"
task :sandbox do
- exec("lib/sandbox.sh")
+ Bundler.with_clean_env do
+ exec("lib/sandbox.sh")
+ end
end
diff --git a/SPREE_VERSION b/SPREE_VERSION
index a5b53b3152e..23aa8390630 100644
--- a/SPREE_VERSION
+++ b/SPREE_VERSION
@@ -1 +1 @@
-1.2.0.rc1
+1.2.2
diff --git a/api/app/controllers/spree/api/v1/addresses_controller.rb b/api/app/controllers/spree/api/v1/addresses_controller.rb
index c22596b2652..aa708d0ffb9 100644
--- a/api/app/controllers/spree/api/v1/addresses_controller.rb
+++ b/api/app/controllers/spree/api/v1/addresses_controller.rb
@@ -4,10 +4,12 @@ module V1
class AddressesController < Spree::Api::V1::BaseController
def show
@address = Address.find(params[:id])
+ authorize! :read, @address
end
def update
@address = Address.find(params[:id])
+ authorize! :read, @address
@address.update_attributes(params[:address])
render :show, :status => 200
end
diff --git a/api/app/controllers/spree/api/v1/base_controller.rb b/api/app/controllers/spree/api/v1/base_controller.rb
index 4881d401cbb..6b44d12806a 100644
--- a/api/app/controllers/spree/api/v1/base_controller.rb
+++ b/api/app/controllers/spree/api/v1/base_controller.rb
@@ -6,7 +6,8 @@ class BaseController < ActionController::Metal
attr_accessor :current_api_user
- before_filter :check_for_api_key
+ before_filter :set_content_type
+ before_filter :check_for_api_key, :if => :requires_authentication?
before_filter :authenticate_user
rescue_from CanCan::AccessDenied, :with => :unauthorized
@@ -25,13 +26,28 @@ def map_nested_attributes_keys(klass, attributes)
private
+ def set_content_type
+ content_type = case params[:format]
+ when "json"
+ "application/json"
+ when "xml"
+ "text/xml"
+ end
+ headers["Content-Type"] = content_type
+ end
+
def check_for_api_key
render "spree/api/v1/errors/must_specify_api_key", :status => 401 and return if api_key.blank?
end
def authenticate_user
- unless @current_api_user = Spree.user_class.find_by_spree_api_key(api_key)
- render "spree/api/v1/errors/invalid_api_key", :status => 401 and return
+ if requires_authentication? || api_key.present?
+ unless @current_api_user = Spree.user_class.find_by_spree_api_key(api_key)
+ render "spree/api/v1/errors/invalid_api_key", :status => 401 and return
+ end
+ else
+ # Effectively, an anonymous user
+ @current_api_user = Spree.user_class.new
end
end
@@ -39,6 +55,10 @@ def unauthorized
render "spree/api/v1/errors/unauthorized", :status => 401 and return
end
+ def requires_authentication?
+ Spree::Api::Config[:requires_authentication]
+ end
+
def not_found
render "spree/api/v1/errors/not_found", :status => 404 and return
end
diff --git a/api/app/controllers/spree/api/v1/countries_controller.rb b/api/app/controllers/spree/api/v1/countries_controller.rb
index 8435596d5a1..55f72e9cd25 100644
--- a/api/app/controllers/spree/api/v1/countries_controller.rb
+++ b/api/app/controllers/spree/api/v1/countries_controller.rb
@@ -3,7 +3,10 @@ module Api
module V1
class CountriesController < Spree::Api::V1::BaseController
def index
- @countries = Country.includes(:states).order('name ASC')
+ @countries = Country.
+ ransack(params[:q]).result.
+ includes(:states).order('name ASC').
+ page(params[:page]).per(params[:per_page])
end
def show
diff --git a/api/app/controllers/spree/api/v1/images_controller.rb b/api/app/controllers/spree/api/v1/images_controller.rb
index b8df78a4d79..12d0fc94f70 100644
--- a/api/app/controllers/spree/api/v1/images_controller.rb
+++ b/api/app/controllers/spree/api/v1/images_controller.rb
@@ -7,20 +7,23 @@ def show
end
def create
+ authorize! :create, Image
@image = Image.create(params[:image])
render :show, :status => 201
end
def update
+ authorize! :update, Image
@image = Image.find(params[:id])
@image.update_attributes(params[:image])
render :show, :status => 200
end
def destroy
+ authorize! :delete, Image
@image = Image.find(params[:id])
@image.destroy
- render :text => nil
+ render :text => nil, :status => 204
end
end
diff --git a/api/app/controllers/spree/api/v1/line_items_controller.rb b/api/app/controllers/spree/api/v1/line_items_controller.rb
index 28dfd2b866d..90c2c9a5fad 100644
--- a/api/app/controllers/spree/api/v1/line_items_controller.rb
+++ b/api/app/controllers/spree/api/v1/line_items_controller.rb
@@ -26,7 +26,7 @@ def destroy
authorize! :read, order
@line_item = order.line_items.find(params[:id])
@line_item.destroy
- render :text => nil, :status => 200
+ render :text => nil, :status => 204
end
private
diff --git a/api/app/controllers/spree/api/v1/orders_controller.rb b/api/app/controllers/spree/api/v1/orders_controller.rb
index dddac3aabba..55ea57d2f95 100644
--- a/api/app/controllers/spree/api/v1/orders_controller.rb
+++ b/api/app/controllers/spree/api/v1/orders_controller.rb
@@ -2,31 +2,25 @@ module Spree
module Api
module V1
class OrdersController < Spree::Api::V1::BaseController
- before_filter :map_nested_attributes, :only => [:create, :update]
before_filter :authorize_read!, :except => [:index, :search, :create]
def index
# should probably look at turning this into a CanCan step
raise CanCan::AccessDenied unless current_api_user.has_spree_role?("admin")
- @orders = Order.page(params[:page]).per(params[:per_page])
+ @orders = Order.ransack(params[:q]).result.page(params[:page]).per(params[:per_page])
end
def show
end
- def search
- @orders = Order.ransack(params[:q]).result.page(params[:page])
- render :index
- end
-
def create
- @order = Order.build_from_api(current_api_user, @nested_params)
- next!
+ @order = Order.build_from_api(current_api_user, nested_params)
+ next!(:status => 201)
end
def update
authorize! :update, Order
- if order.update_attributes(@nested_params)
+ if order.update_attributes(nested_params)
order.update!
render :show
else
@@ -35,8 +29,8 @@ def update
end
def address
- order.build_ship_address(params[:shipping_address])
- order.build_bill_address(params[:billing_address])
+ order.build_ship_address(params[:shipping_address]) if params[:shipping_address]
+ order.build_bill_address(params[:billing_address]) if params[:billing_address]
next!
end
@@ -64,17 +58,17 @@ def empty
private
- def map_nested_attributes
- @nested_params = map_nested_attributes_keys Order, params[:order]
+ def nested_params
+ map_nested_attributes_keys Order, params[:order] || {}
end
def order
@order ||= Order.find_by_number!(params[:id])
end
- def next!
+ def next!(options={})
if @order.valid? && @order.next
- render :show, :status => 200
+ render :show, :status => options[:status] || 200
else
render :could_not_transition, :status => 422
end
diff --git a/api/app/controllers/spree/api/v1/payments_controller.rb b/api/app/controllers/spree/api/v1/payments_controller.rb
index e33386c5010..8b14446088e 100644
--- a/api/app/controllers/spree/api/v1/payments_controller.rb
+++ b/api/app/controllers/spree/api/v1/payments_controller.rb
@@ -6,7 +6,7 @@ class PaymentsController < Spree::Api::V1::BaseController
before_filter :find_payment, :only => [:show, :authorize, :purchase, :capture, :void, :credit]
def index
- @payments = @order.payments
+ @payments = @order.payments.ransack(params[:q]).result.page(params[:page]).per(params[:per_page])
end
def new
@@ -29,6 +29,10 @@ def authorize
perform_payment_action(:authorize)
end
+ def capture
+ perform_payment_action(:capture)
+ end
+
def purchase
perform_payment_action(:purchase)
end
diff --git a/api/app/controllers/spree/api/v1/product_properties_controller.rb b/api/app/controllers/spree/api/v1/product_properties_controller.rb
new file mode 100644
index 00000000000..b2b7508251a
--- /dev/null
+++ b/api/app/controllers/spree/api/v1/product_properties_controller.rb
@@ -0,0 +1,64 @@
+module Spree
+ module Api
+ module V1
+ class ProductPropertiesController < Spree::Api::V1::BaseController
+ before_filter :find_product
+ before_filter :product_property, :only => [:show, :update, :destroy]
+
+ def index
+ @product_properties = @product.product_properties.
+ ransack(params[:q]).result
+ .page(params[:page]).per(params[:per_page])
+ end
+
+ def show
+ end
+
+ def new
+ end
+
+ def create
+ authorize! :create, ProductProperty
+ @product_property = @product.product_properties.new(params[:product_property])
+ if @product_property.save
+ render :show, :status => 201
+ else
+ invalid_resource!(@product_property)
+ end
+ end
+
+ def update
+ authorize! :update, ProductProperty
+ if @product_property && @product_property.update_attributes(params[:product_property])
+ render :show, :status => 200
+ else
+ invalid_resource!(@product_property)
+ end
+ end
+
+ def destroy
+ authorize! :delete, ProductProperty
+ if(@product_property)
+ @product_property.destroy
+ render :text => nil, :status => 204
+ else
+ invalid_resource!(@product_property)
+ end
+
+ end
+
+ private
+ def find_product
+ @product = super(params[:product_id])
+ end
+
+ def product_property
+ if @product
+ @product_property ||= @product.product_properties.find_by_id(params[:id])
+ @product_property ||= @product.product_properties.joins(:property).where('spree_properties.name' => params[:id]).readonly(false).first
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/api/app/controllers/spree/api/v1/products_controller.rb b/api/app/controllers/spree/api/v1/products_controller.rb
index 9967742309c..99722e91715 100644
--- a/api/app/controllers/spree/api/v1/products_controller.rb
+++ b/api/app/controllers/spree/api/v1/products_controller.rb
@@ -3,12 +3,7 @@ module Api
module V1
class ProductsController < Spree::Api::V1::BaseController
def index
- @products = product_scope.page(params[:page])
- end
-
- def search
- @products = product_scope.ransack(params[:q]).result.page(params[:page])
- render :index
+ @products = product_scope.ransack(params[:q]).result.page(params[:page]).per(params[:per_page])
end
def show
@@ -44,7 +39,7 @@ def destroy
@product = find_product(params[:id])
@product.update_attribute(:deleted_at, Time.now)
@product.variants_including_master.update_all(:deleted_at => Time.now)
- render :text => nil, :status => 200
+ render :text => nil, :status => 204
end
end
end
diff --git a/api/app/controllers/spree/api/v1/return_authorizations_controller.rb b/api/app/controllers/spree/api/v1/return_authorizations_controller.rb
new file mode 100644
index 00000000000..b8accbb3346
--- /dev/null
+++ b/api/app/controllers/spree/api/v1/return_authorizations_controller.rb
@@ -0,0 +1,53 @@
+module Spree
+ module Api
+ module V1
+ class ReturnAuthorizationsController < Spree::Api::V1::BaseController
+ before_filter :authorize_admin!
+
+ def index
+ @return_authorizations = order.return_authorizations.
+ ransack(params[:q]).result.
+ page(params[:page]).per(params[:per_page])
+ end
+
+ def show
+ @return_authorization = order.return_authorizations.find(params[:id])
+ end
+
+ def create
+ @return_authorization = order.return_authorizations.build(params[:return_authorization], :as => :api)
+ if @return_authorization.save
+ render :show, :status => 201
+ else
+ invalid_resource!(@return_authorization)
+ end
+ end
+
+ def update
+ @return_authorization = order.return_authorizations.find(params[:id])
+ if @return_authorization.update_attributes(params[:return_authorization])
+ render :show
+ else
+ invalid_resource!(@return_authorization)
+ end
+ end
+
+ def destroy
+ @return_authorization = order.return_authorizations.find(params[:id])
+ @return_authorization.destroy
+ render :text => nil, :status => 204
+ end
+
+ private
+
+ def order
+ @order ||= Order.find_by_number!(params[:order_id])
+ end
+
+ def authorize_admin!
+ authorize! :manage, Spree::ReturnAuthorization
+ end
+ end
+ end
+ end
+end
diff --git a/api/app/controllers/spree/api/v1/shipments_controller.rb b/api/app/controllers/spree/api/v1/shipments_controller.rb
index 2f44e970754..ecd7f3629d5 100644
--- a/api/app/controllers/spree/api/v1/shipments_controller.rb
+++ b/api/app/controllers/spree/api/v1/shipments_controller.rb
@@ -6,6 +6,7 @@ class ShipmentsController < BaseController
before_filter :find_and_update_shipment, :only => [:ship, :ready]
def ready
+ authorize! :read, Shipment
unless @shipment.ready?
@shipment.ready!
end
@@ -13,6 +14,7 @@ def ready
end
def ship
+ authorize! :read, Shipment
unless @shipment.shipped?
@shipment.ship!
end
@@ -23,6 +25,7 @@ def ship
def find_order
@order = Spree::Order.find_by_number!(params[:order_id])
+ authorize! :read, @order
end
def find_and_update_shipment
diff --git a/api/app/controllers/spree/api/v1/taxonomies_controller.rb b/api/app/controllers/spree/api/v1/taxonomies_controller.rb
index e3180018056..d1d802ecfc5 100644
--- a/api/app/controllers/spree/api/v1/taxonomies_controller.rb
+++ b/api/app/controllers/spree/api/v1/taxonomies_controller.rb
@@ -3,7 +3,10 @@ module Api
module V1
class TaxonomiesController < Spree::Api::V1::BaseController
def index
- @taxonomies = Taxonomy.order('name').includes(:root => :children)
+ @taxonomies = Taxonomy.
+ order('name').includes(:root => :children).
+ ransack(params[:q]).result.
+ page(params[:page]).per(params[:per_page])
end
def show
@@ -32,7 +35,7 @@ def update
def destroy
authorize! :delete, Taxonomy
taxonomy.destroy
- render :text => nil, :status => 200
+ render :text => nil, :status => 204
end
private
diff --git a/api/app/controllers/spree/api/v1/taxons_controller.rb b/api/app/controllers/spree/api/v1/taxons_controller.rb
index bed8b1118ca..96f3235e6bc 100644
--- a/api/app/controllers/spree/api/v1/taxons_controller.rb
+++ b/api/app/controllers/spree/api/v1/taxons_controller.rb
@@ -32,7 +32,7 @@ def update
def destroy
authorize! :delete, Taxon
taxon.destroy
- render :text => nil, :status => 200
+ render :text => nil, :status => 204
end
private
diff --git a/api/app/controllers/spree/api/v1/variants_controller.rb b/api/app/controllers/spree/api/v1/variants_controller.rb
index 655c986fe38..694e7adbca4 100644
--- a/api/app/controllers/spree/api/v1/variants_controller.rb
+++ b/api/app/controllers/spree/api/v1/variants_controller.rb
@@ -5,7 +5,9 @@ class VariantsController < Spree::Api::V1::BaseController
before_filter :product
def index
- @variants = scope.includes(:option_values).scoped
+ @variants = scope.
+ includes(:option_values).ransack(params[:q]).result.
+ page(params[:page]).per(params[:per_page])
end
def show
@@ -39,7 +41,7 @@ def destroy
authorize! :delete, Variant
@variant = scope.find(params[:id])
@variant.destroy
- render :text => nil, :status => 200
+ render :text => nil, :status => 204
end
private
@@ -48,7 +50,23 @@ def product
end
def scope
- @product ? @product.variants_including_master : Variant
+ if @product
+ unless current_api_user.has_spree_role?("admin") || params[:show_deleted]
+ variants = @product.variants_including_master
+ else
+ variants = @product.variants_including_master_and_deleted
+ end
+ else
+ variants = Variant.scoped
+ if current_api_user.has_spree_role?("admin")
+ unless params[:show_deleted]
+ variants = Variant.active
+ end
+ else
+ variants = variants.active
+ end
+ end
+ variants
end
end
end
diff --git a/api/app/controllers/spree/api/v1/zones_controller.rb b/api/app/controllers/spree/api/v1/zones_controller.rb
index 2b5def5df18..ec33f4f65db 100644
--- a/api/app/controllers/spree/api/v1/zones_controller.rb
+++ b/api/app/controllers/spree/api/v1/zones_controller.rb
@@ -3,7 +3,7 @@ module Api
module V1
class ZonesController < Spree::Api::V1::BaseController
def index
- @zones = Zone.order('name ASC')
+ @zones = Zone.order('name ASC').ransack(params[:q]).result.page(params[:page]).per(params[:per_page])
end
def show
@@ -32,7 +32,7 @@ def update
def destroy
authorize! :delete, Zone
zone.destroy
- render :text => nil, :status => 200
+ render :text => nil, :status => 204
end
private
diff --git a/api/app/helpers/spree/api/api_helpers.rb b/api/app/helpers/spree/api/api_helpers.rb
index 51d97b8a1fa..8cbf5b5784d 100644
--- a/api/app/helpers/spree/api/api_helpers.rb
+++ b/api/app/helpers/spree/api/api_helpers.rb
@@ -63,6 +63,14 @@ def taxonomy_attributes
def taxon_attributes
[:id, :name, :permalink, :position, :parent_id, :taxonomy_id]
end
+
+ def return_authorization_attributes
+ [:id, :number, :state, :amount, :order_id, :reason, :created_at, :updated_at]
+ end
+
+ def country_attributes
+ [:id, :iso_name, :iso, :iso3, :name, :numcode]
+ end
end
end
end
diff --git a/api/app/models/spree/api_configuration.rb b/api/app/models/spree/api_configuration.rb
new file mode 100644
index 00000000000..a9d5d733ef4
--- /dev/null
+++ b/api/app/models/spree/api_configuration.rb
@@ -0,0 +1,5 @@
+module Spree
+ class ApiConfiguration < Preferences::Configuration
+ preference :requires_authentication, :boolean, :default => true
+ end
+end
diff --git a/api/app/models/spree/order_decorator.rb b/api/app/models/spree/order_decorator.rb
index c52cdb3b8cc..34e8a2599d1 100644
--- a/api/app/models/spree/order_decorator.rb
+++ b/api/app/models/spree/order_decorator.rb
@@ -1,6 +1,7 @@
Spree::Order.class_eval do
def self.build_from_api(user, params)
order = create
+ params[:line_items_attributes] ||= []
params[:line_items_attributes].each do |line_item|
order.add_variant(Spree::Variant.find(line_item[:variant_id]), line_item[:quantity])
end
diff --git a/api/app/views/spree/api/v1/countries/index.rabl b/api/app/views/spree/api/v1/countries/index.rabl
index cfb3010e608..028c4ad0f3f 100644
--- a/api/app/views/spree/api/v1/countries/index.rabl
+++ b/api/app/views/spree/api/v1/countries/index.rabl
@@ -1,2 +1,7 @@
-collection @countries
-extends "spree/api/v1/countries/show"
\ No newline at end of file
+object false
+child(@countries => :countries) do
+ attributes *country_attributes
+end
+node(:count) { @countries.count }
+node(:current_page) { params[:page] || 1 }
+node(:pages) { @countries.num_pages }
diff --git a/api/app/views/spree/api/v1/countries/show.rabl b/api/app/views/spree/api/v1/countries/show.rabl
index 4e3bf830420..19d4322a9c3 100644
--- a/api/app/views/spree/api/v1/countries/show.rabl
+++ b/api/app/views/spree/api/v1/countries/show.rabl
@@ -1,5 +1,5 @@
object @country
-attributes :id, :iso_name, :iso, :iso3, :name, :numcode
+attributes *country_attributes
child :states => :states do
attributes :id, :name, :abbr, :country_id
-end
\ No newline at end of file
+end
diff --git a/api/app/views/spree/api/v1/orders/index.rabl b/api/app/views/spree/api/v1/orders/index.rabl
index 033dead6a1f..c937d2bffcf 100644
--- a/api/app/views/spree/api/v1/orders/index.rabl
+++ b/api/app/views/spree/api/v1/orders/index.rabl
@@ -1,5 +1,5 @@
object false
-child(@orders) do
+child(@orders => :orders) do
attributes *order_attributes
end
node(:count) { @orders.count }
diff --git a/api/app/views/spree/api/v1/orders/show.rabl b/api/app/views/spree/api/v1/orders/show.rabl
index b9b335cc48c..8920e75ad8b 100644
--- a/api/app/views/spree/api/v1/orders/show.rabl
+++ b/api/app/views/spree/api/v1/orders/show.rabl
@@ -1,6 +1,9 @@
object @order
attributes *order_attributes
-extends "spree/api/v1/orders/#{@order.state}"
+
+if lookup_context.find_all("spree/api/v1/orders/#{@order.state}").present?
+ extends "spree/api/v1/orders/#{@order.state}"
+end
child :billing_address => :bill_address do
extends "spree/api/v1/addresses/show"
diff --git a/api/app/views/spree/api/v1/payments/index.rabl b/api/app/views/spree/api/v1/payments/index.rabl
index 3d9b2a44f85..f72651a0d53 100644
--- a/api/app/views/spree/api/v1/payments/index.rabl
+++ b/api/app/views/spree/api/v1/payments/index.rabl
@@ -1,2 +1,7 @@
-collection @payments
-attributes *payment_attributes
+object false
+child(@payments => :payments) do
+ attributes *payment_attributes
+end
+node(:count) { @payments.count }
+node(:current_page) { params[:page] || 1 }
+node(:pages) { @payments.num_pages }
diff --git a/api/app/views/spree/api/v1/product_properties/index.rabl b/api/app/views/spree/api/v1/product_properties/index.rabl
new file mode 100644
index 00000000000..3f81a99d7b6
--- /dev/null
+++ b/api/app/views/spree/api/v1/product_properties/index.rabl
@@ -0,0 +1,7 @@
+object false
+child(@product_properties => :product_properties) do
+ attributes *product_property_attributes
+end
+node(:count) { @product_properties.count }
+node(:current_page) { params[:page] || 1 }
+node(:pages) { @product_properties.num_pages }
diff --git a/api/app/views/spree/api/v1/product_properties/new.rabl b/api/app/views/spree/api/v1/product_properties/new.rabl
new file mode 100644
index 00000000000..9556ea41320
--- /dev/null
+++ b/api/app/views/spree/api/v1/product_properties/new.rabl
@@ -0,0 +1,2 @@
+node(:attributes) { [*product_property_attributes] }
+node(:required_attributes) { [] }
\ No newline at end of file
diff --git a/api/app/views/spree/api/v1/product_properties/show.rabl b/api/app/views/spree/api/v1/product_properties/show.rabl
new file mode 100644
index 00000000000..12e24137b53
--- /dev/null
+++ b/api/app/views/spree/api/v1/product_properties/show.rabl
@@ -0,0 +1,2 @@
+object @product_property
+attributes *product_property_attributes
diff --git a/api/app/views/spree/api/v1/products/index.rabl b/api/app/views/spree/api/v1/products/index.rabl
index 8797e5e4f54..54107ca7530 100644
--- a/api/app/views/spree/api/v1/products/index.rabl
+++ b/api/app/views/spree/api/v1/products/index.rabl
@@ -1,5 +1,6 @@
object false
-node(:count) { @products.total_count }
+node(:count) { @products.count }
+node(:total_count) { @products.total_count }
node(:current_page) { params[:page] ? params[:page].to_i : 1 }
node(:pages) { @products.num_pages }
child(@products) do
diff --git a/api/app/views/spree/api/v1/return_authorizations/index.rabl b/api/app/views/spree/api/v1/return_authorizations/index.rabl
new file mode 100644
index 00000000000..6574912a6d0
--- /dev/null
+++ b/api/app/views/spree/api/v1/return_authorizations/index.rabl
@@ -0,0 +1,7 @@
+object false
+child(@return_authorizations => :return_authorizations) do
+ attributes *return_authorization_attributes
+end
+node(:count) { @return_authorizations.count }
+node(:current_page) { params[:page] || 1 }
+node(:pages) { @return_authorizations.num_pages }
diff --git a/api/app/views/spree/api/v1/return_authorizations/new.rabl b/api/app/views/spree/api/v1/return_authorizations/new.rabl
new file mode 100644
index 00000000000..362fa8ac9de
--- /dev/null
+++ b/api/app/views/spree/api/v1/return_authorizations/new.rabl
@@ -0,0 +1,3 @@
+object false
+node(:attributes) { [*return_authorization_attributes] }
+node(:required_attributes) { required_fields_for(Spree::ReturnAuthorization) }
diff --git a/api/app/views/spree/api/v1/return_authorizations/show.rabl b/api/app/views/spree/api/v1/return_authorizations/show.rabl
new file mode 100644
index 00000000000..00b07a3b1c2
--- /dev/null
+++ b/api/app/views/spree/api/v1/return_authorizations/show.rabl
@@ -0,0 +1,2 @@
+object @return_authorization
+attributes *return_authorization_attributes
\ No newline at end of file
diff --git a/api/app/views/spree/api/v1/taxonomies/index.rabl b/api/app/views/spree/api/v1/taxonomies/index.rabl
index f12f6acd9c3..0a279e048d7 100644
--- a/api/app/views/spree/api/v1/taxonomies/index.rabl
+++ b/api/app/views/spree/api/v1/taxonomies/index.rabl
@@ -1,2 +1,7 @@
-collection @taxonomies
-extends "spree/api/v1/taxonomies/show"
+object false
+child(@taxonomies => :taxonomies) do
+ extends "spree/api/v1/taxonomies/show"
+end
+node(:count) { @taxonomies.count }
+node(:current_page) { params[:page] || 1 }
+node(:pages) { @taxonomies.num_pages }
diff --git a/api/app/views/spree/api/v1/variants/index.rabl b/api/app/views/spree/api/v1/variants/index.rabl
index 7790118a0e4..d37c886a2a0 100644
--- a/api/app/views/spree/api/v1/variants/index.rabl
+++ b/api/app/views/spree/api/v1/variants/index.rabl
@@ -1,3 +1,10 @@
-collection @variants
-attributes *variant_attributes
-child(:option_values => :option_values) { attributes *option_value_attributes }
+object false
+node(:count) { @variants.count }
+node(:total_count) { @variants.total_count }
+node(:current_page) { params[:page] ? params[:page].to_i : 1 }
+node(:pages) { @variants.num_pages }
+
+child(@variants => :variants) do
+ attributes *variant_attributes
+ child(:option_values => :option_values) { attributes *option_value_attributes }
+end
diff --git a/api/app/views/spree/api/v1/zones/index.rabl b/api/app/views/spree/api/v1/zones/index.rabl
index b16039b448b..232ed4e2395 100644
--- a/api/app/views/spree/api/v1/zones/index.rabl
+++ b/api/app/views/spree/api/v1/zones/index.rabl
@@ -1,2 +1,7 @@
-collection @zones
-extends 'spree/api/v1/zones/show'
+object false
+child(@zones => :zones) do
+ extends 'spree/api/v1/zones/show'
+end
+node(:count) { @zones.count }
+node(:current_page) { params[:page] || 1 }
+node(:pages) { @zones.num_pages }
diff --git a/api/config/routes.rb b/api/config/routes.rb
index ea2cfa6348c..4e201f28011 100644
--- a/api/config/routes.rb
+++ b/api/config/routes.rb
@@ -11,22 +11,16 @@
namespace :api do
scope :module => :v1 do
resources :products do
- collection do
- get :search
- end
-
resources :variants
+ resources :product_properties
end
resources :images
-
resources :variants, :only => [:index] do
end
resources :orders do
- collection do
- get :search
- end
+ resources :return_authorizations
member do
put :address
put :delivery
@@ -38,6 +32,7 @@
resources :payments do
member do
put :authorize
+ put :capture
put :purchase
put :void
put :credit
diff --git a/api/lib/spree/api/engine.rb b/api/lib/spree/api/engine.rb
index 8b7ee9e2212..502da59daaa 100644
--- a/api/lib/spree/api/engine.rb
+++ b/api/lib/spree/api/engine.rb
@@ -6,6 +6,10 @@ class Engine < Rails::Engine
isolate_namespace Spree
engine_name 'spree_api'
+ initializer "spree.api.environment", :before => :load_config_initializers do |app|
+ Spree::Api::Config = Spree::ApiConfiguration.new
+ end
+
def self.activate
Dir.glob(File.join(File.dirname(__FILE__), "../../../app/**/*_decorator*.rb")) do |c|
Rails.configuration.cache_classes ? require(c) : load(c)
diff --git a/api/spec/controllers/spree/api/v1/addresses_controller_spec.rb b/api/spec/controllers/spree/api/v1/addresses_controller_spec.rb
index 293fee15d50..1ff3175556a 100644
--- a/api/spec/controllers/spree/api/v1/addresses_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/addresses_controller_spec.rb
@@ -9,15 +9,37 @@ module Spree
@address = create(:address)
end
- it "gets an address" do
- api_get :show, :id => @address.id
- json_response['address']['address1'].should eq @address.address1
+ context "with their own address" do
+ before do
+ Address.any_instance.stub :user => current_api_user
+ end
+
+ it "gets an address" do
+ api_get :show, :id => @address.id
+ json_response['address']['address1'].should eq @address.address1
+ end
+
+ it "updates an address" do
+ api_put :update, :id => @address.id,
+ :address => { :address1 => "123 Test Lane" }
+ json_response['address']['address1'].should eq '123 Test Lane'
+ end
end
- it "updates an address" do
- api_put :update, :id => @address.id,
- :address => { :address1 => "123 Test Lane" }
- json_response['address']['address1'].should eq '123 Test Lane'
+ context "on somebody else's address" do
+ before do
+ Address.any_instance.stub :user => stub_model(Spree::LegacyUser)
+ end
+
+ it "cannot retreive address information" do
+ api_get :show, :id => @address.id
+ assert_unauthorized!
+ end
+
+ it "cannot update address information" do
+ api_get :update, :id => @address.id
+ assert_unauthorized!
+ end
end
end
end
diff --git a/api/spec/controllers/spree/api/v1/base_controller_spec.rb b/api/spec/controllers/spree/api/v1/base_controller_spec.rb
index a604465cbe9..9723bddd7bb 100644
--- a/api/spec/controllers/spree/api/v1/base_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/base_controller_spec.rb
@@ -1,6 +1,7 @@
require 'spec_helper'
describe Spree::Api::V1::BaseController do
+ render_views
controller(Spree::Api::V1::BaseController) do
def index
render :json => { "products" => [] }
diff --git a/api/spec/controllers/spree/api/v1/countries_controller_spec.rb b/api/spec/controllers/spree/api/v1/countries_controller_spec.rb
index fb198f20393..3493d7e5c1b 100644
--- a/api/spec/controllers/spree/api/v1/countries_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/countries_controller_spec.rb
@@ -12,7 +12,31 @@ module Spree
it "gets all countries" do
api_get :index
- json_response.first['country']['iso3'].should eq @country.iso3
+ json_response["countries"].first['country']['iso3'].should eq @country.iso3
+ end
+
+ context "with two countries" do
+ before { @zambia = create(:country, :name => "Zambia") }
+
+ it "can view all countries" do
+ api_get :index
+ json_response['count'].should == 2
+ json_response['current_page'].should == 1
+ json_response['pages'].should == 1
+ end
+
+ it 'can query the results through a paramter' do
+ api_get :index, :q => { :name_cont => 'zam' }
+ json_response['count'].should == 1
+ json_response['countries'].first['country']['name'].should eq @zambia.name
+ end
+
+ it 'can control the page size through a parameter' do
+ api_get :index, :per_page => 1
+ json_response['count'].should == 1
+ json_response['current_page'].should == 1
+ json_response['pages'].should == 2
+ end
end
it "includes states" do
diff --git a/api/spec/controllers/spree/api/v1/images_controller_spec.rb b/api/spec/controllers/spree/api/v1/images_controller_spec.rb
index fc5da37b2ce..876fa79c4d7 100644
--- a/api/spec/controllers/spree/api/v1/images_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/images_controller_spec.rb
@@ -13,43 +13,53 @@ module Spree
stub_authentication!
end
- it "can upload a new image for a product" do
- lambda do
- api_post :create,
- :image => { :attachment => upload_image("thinking-cat.jpg"),
- :viewable_type => 'Spree::Product',
- :viewable_id => product.id }
- response.status.should == 201
- json_response.should have_attributes(attributes)
- end.should change(Image, :count).by(1)
- end
+ context "as an admin" do
+ sign_in_as_admin!
+
+ it "can upload a new image for a variant" do
+ lambda do
+ api_post :create,
+ :image => { :attachment => upload_image('thinking-cat.jpg'),
+ :viewable_type => 'Spree::Variant',
+ :viewable_id => product.master.to_param }
+ response.status.should == 201
+ json_response.should have_attributes(attributes)
+ end.should change(Image, :count).by(1)
+ end
+
+ context "working with an existing image" do
+ let!(:product_image) { product.master.images.create!(:attachment => image('thinking-cat.jpg')) }
- it "can upload a new image for a variant" do
- lambda do
- api_post :create,
- :image => { :attachment => upload_image("thinking-cat.jpg"),
- :viewable_type => 'Spree::Variant',
- :viewable_id => product.master.to_param }
- response.status.should == 201
- json_response.should have_attributes(attributes)
- end.should change(Image, :count).by(1)
+ it "can update image data" do
+ product_image.position.should == 1
+ api_post :update, :image => { :position => 2 }, :id => product_image.id
+ response.status.should == 200
+ json_response.should have_attributes(attributes)
+ product_image.reload.position.should == 2
+ end
+
+ it "can delete an image" do
+ api_delete :destroy, :id => product_image.id
+ response.status.should == 204
+ lambda { product_image.reload }.should raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
end
- context "working with an existing image" do
- let!(:product_image) { product.master.images.create!(:attachment => image("thinking-cat.jpg")) }
+ context "as a non-admin" do
+ it "cannot create an image" do
+ api_post :create
+ assert_unauthorized!
+ end
- it "can update image data" do
- product_image.position.should == 1
- api_post :update, :image => { :position => 2 }, :id => product_image.id
- response.status.should == 200
- json_response.should have_attributes(attributes)
- product_image.reload.position.should == 2
+ it "cannot update an image" do
+ api_put :update, :id => 1
+ assert_unauthorized!
end
- it "can delete an image" do
- api_delete :destroy, :id => product_image.id
- response.status.should == 200
- lambda { product_image.reload }.should raise_error(ActiveRecord::RecordNotFound)
+ it "cannot delete an image" do
+ api_delete :destroy, :id => 1
+ assert_unauthorized!
end
end
end
diff --git a/api/spec/controllers/spree/api/v1/line_items_controller_spec.rb b/api/spec/controllers/spree/api/v1/line_items_controller_spec.rb
index b1dd46e5075..d6da43d21ff 100644
--- a/api/spec/controllers/spree/api/v1/line_items_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/line_items_controller_spec.rb
@@ -47,7 +47,7 @@ module Spree
it "can delete a line item on the order" do
line_item = order.line_items.first
api_delete :destroy, :id => line_item.id
- response.status.should == 200
+ response.status.should == 204
lambda { line_item.reload }.should raise_error(ActiveRecord::RecordNotFound)
end
end
diff --git a/api/spec/controllers/spree/api/v1/orders_controller_spec.rb b/api/spec/controllers/spree/api/v1/orders_controller_spec.rb
index 9631c3c98f6..8ae65bf2c54 100644
--- a/api/spec/controllers/spree/api/v1/orders_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/orders_controller_spec.rb
@@ -28,6 +28,13 @@ module Spree
json_response.should have_attributes(attributes)
end
+ # Regression test for #1992
+ it "can view an order not in a standard state" do
+ Order.any_instance.stub :user => current_api_user
+ order.update_column(:state, 'shipped')
+ api_get :show, :id => order.to_param
+ end
+
it "can not view someone else's order" do
Order.any_instance.stub :user => stub_model(Spree::LegacyUser)
api_get :show, :id => order.to_param
@@ -54,7 +61,7 @@ module Spree
it "can create an order" do
variant = create(:variant)
api_post :create, :order => { :line_items => [{ :variant_id => variant.to_param, :quantity => 5 }] }
- response.status.should == 200
+ response.status.should == 201
order = Order.last
order.line_items.count.should == 1
order.line_items.first.variant.should == variant
@@ -62,6 +69,13 @@ module Spree
json_response["order"]["state"].should == "address"
end
+ it "can create an order without any parameters" do
+ lambda { api_post :create }.should_not raise_error(NoMethodError)
+ response.status.should == 201
+ order = Order.last
+ json_response["order"]["state"].should == "address"
+ end
+
context "working with an order" do
before do
Order.any_instance.stub :user => current_api_user
@@ -97,6 +111,15 @@ def clean_address(address)
json_response["order"]["shipping_methods"].should_not be_empty
end
+ it "can add just shipping address information to an order" do
+ api_put :address, :id => order.to_param, :shipping_address => shipping_address
+ response.status.should == 200
+ order.reload
+ order.shipping_address.reload
+ order.shipping_address.firstname.should == shipping_address[:firstname]
+ order.bill_address.should be_nil
+ end
+
it "cannot use an address that has no valid shipping methods" do
shipping_method.destroy
api_put :address, :id => order.to_param, :shipping_address => shipping_address, :billing_address => billing_address
@@ -165,6 +188,14 @@ def clean_address(address)
context "as an admin" do
sign_in_as_admin!
+ context "with no orders" do
+ before { Spree::Order.delete_all }
+ it "still returns a root :orders key" do
+ api_get :index
+ json_response["orders"].should == []
+ end
+ end
+
context "with two orders" do
before { create(:order) }
@@ -187,6 +218,25 @@ def clean_address(address)
end
end
+ context "search" do
+ before do
+ create(:order)
+ Spree::Order.last.update_attribute(:email, 'spree@spreecommerce.com')
+ end
+
+ let(:expected_result) { Spree::Order.last }
+
+ it "can query the results through a parameter" do
+ api_get :index, :q => { :email_cont => 'spree' }
+ json_response["orders"].count.should == 1
+ json_response["orders"].first.should have_attributes(attributes)
+ json_response["orders"].first["order"]["email"].should == expected_result.email
+ json_response["count"].should == 1
+ json_response["current_page"].should == 1
+ json_response["pages"].should == 1
+ end
+ end
+
context "can cancel an order" do
before do
order.completed_at = Time.now
diff --git a/api/spec/controllers/spree/api/v1/payments_controller_spec.rb b/api/spec/controllers/spree/api/v1/payments_controller_spec.rb
index dea1ff11daa..e2ef5589f13 100644
--- a/api/spec/controllers/spree/api/v1/payments_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/payments_controller_spec.rb
@@ -2,10 +2,11 @@
module Spree
describe Spree::Api::V1::PaymentsController do
+ render_views
let!(:order) { create(:order) }
let!(:payment) { create(:payment, :order => order) }
let!(:attributes) { [:id, :source_type, :source_id, :amount,
- :payment_method_id, :response_code, :state, :avs_response,
+ :payment_method_id, :response_code, :state, :avs_response,
:created_at, :updated_at] }
let(:resource_scoping) { { :order_id => order.to_param } }
@@ -21,7 +22,7 @@ module Spree
it "can view the payments for their order" do
api_get :index
- json_response.first.should have_attributes(attributes)
+ json_response["payments"].first.should have_attributes(attributes)
end
it "can learn how to create a new payment" do
@@ -66,7 +67,29 @@ module Spree
it "can view the payments on any order" do
api_get :index
response.status.should == 200
- json_response.first.should have_attributes(attributes)
+ json_response["payments"].first.should have_attributes(attributes)
+ end
+
+ context "multiple payments" do
+ before { @payment = create(:payment, :order => order, :response_code => '99999') }
+
+ it "can view all payments on an order" do
+ api_get :index
+ json_response["count"].should == 2
+ end
+
+ it 'can control the page size through a parameter' do
+ api_get :index, :per_page => 1
+ json_response['count'].should == 1
+ json_response['current_page'].should == 1
+ json_response['pages'].should == 2
+ end
+
+ it 'can query the results through a paramter' do
+ api_get :index, :q => { :response_code_cont => '999' }
+ json_response['count'].should == 1
+ json_response['payments'].first['payment']['response_code'].should eq @payment.response_code
+ end
end
context "for a given payment" do
@@ -88,6 +111,24 @@ module Spree
payment.state.should == "failed"
end
+ it "can capture" do
+ api_put :capture, :id => payment.to_param
+ response.status.should == 200
+ payment.reload
+ payment.state.should == "completed"
+ end
+
+ it "returns a 422 status when purchasing fails" do
+ fake_response = stub(:success? => false, :to_s => "Insufficient funds")
+ Spree::Gateway::Bogus.any_instance.should_receive(:capture).and_return(fake_response)
+ api_put :capture, :id => payment.to_param
+ response.status.should == 422
+ json_response["error"].should == "There was a problem with the payment gateway: Insufficient funds"
+
+ payment.reload
+ payment.state.should == "pending"
+ end
+
it "can purchase" do
api_put :purchase, :id => payment.to_param
response.status.should == 200
diff --git a/api/spec/controllers/spree/api/v1/product_properties_controller_spec.rb b/api/spec/controllers/spree/api/v1/product_properties_controller_spec.rb
new file mode 100644
index 00000000000..4da171ce53e
--- /dev/null
+++ b/api/spec/controllers/spree/api/v1/product_properties_controller_spec.rb
@@ -0,0 +1,117 @@
+require 'spec_helper'
+require 'shared_examples/protect_product_actions'
+
+module Spree
+ describe Spree::Api::V1::ProductPropertiesController do
+ render_views
+
+ let!(:product) { create(:product) }
+ let!(:property_1) {product.product_properties.create(:property_name => "My Property 1", :value => "my value 1")}
+ let!(:property_2) {product.product_properties.create(:property_name => "My Property 2", :value => "my value 2")}
+
+ let(:attributes) { [:id, :product_id, :property_id, :value, :property_name] }
+ let(:resource_scoping) { { :product_id => product.to_param } }
+
+ before do
+ stub_authentication!
+ end
+
+ context "if product is deleted" do
+ before do
+ product.update_column(:deleted_at, Time.now)
+ end
+
+ it "can not see a list of product properties" do
+ api_get :index
+ response.status.should == 404
+ end
+ end
+
+ it "can see a list of all product properties" do
+ api_get :index
+ json_response["product_properties"].count.should eq 2
+ json_response["product_properties"].first.should have_attributes(attributes)
+ end
+
+ it "can control the page size through a parameter" do
+ api_get :index, :per_page => 1
+ json_response['product_properties'].count.should == 1
+ json_response['current_page'].should == 1
+ json_response['pages'].should == 2
+ end
+
+ it 'can query the results through a paramter' do
+ Spree::ProductProperty.last.update_attribute(:value, 'loose')
+ property = Spree::ProductProperty.last
+ api_get :index, :q => { :value_cont => 'loose' }
+ json_response['count'].should == 1
+ json_response['product_properties'].first['product_property']['value'].should eq property.value
+ end
+
+ it "can see a single product_property" do
+ api_get :show, :id => property_1.property_name
+ json_response.should have_attributes(attributes)
+ end
+
+ it "can learn how to create a new product property" do
+ api_get :new
+ json_response["attributes"].should == attributes.map(&:to_s)
+ json_response["required_attributes"].should be_empty
+ end
+
+ it "cannot create a new product property if not an admin" do
+ api_post :create, :product_property => { :property_name => "My Property 3" }
+ assert_unauthorized!
+ end
+
+ it "cannot update a product property" do
+ api_put :update, :id => property_1.property_name, :product_property => { :value => "my value 456" }
+ assert_unauthorized!
+ end
+
+ it "cannot delete a product property" do
+ api_delete :destroy, :property_name => property_1.property_name
+ assert_unauthorized!
+ lambda { property_1.reload }.should_not raise_error
+ end
+
+ context "as an admin" do
+ sign_in_as_admin!
+
+ it "can create a new product property" do
+ expect do
+ api_post :create, :product_property => { :property_name => "My Property 3", :value => "my value 3" }
+ end.to change(product.product_properties, :count).by(1)
+ json_response.should have_attributes(attributes)
+ response.status.should == 201
+ end
+
+ it "can update a product property" do
+ api_put :update, :id => property_1.property_name, :product_property => { :value => "my value 456" }
+ response.status.should == 200
+ end
+
+ it "can delete a variant" do
+ api_delete :destroy, :id => property_1.property_name
+ response.status.should == 204
+ lambda { property_1.reload }.should raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ context "with product identified by id" do
+ let(:resource_scoping) { { :product_id => product.id } }
+ it "can see a list of all product properties" do
+ api_get :index
+ json_response["product_properties"].count.should eq 2
+ json_response["product_properties"].first.should have_attributes(attributes)
+ end
+
+ it "can see a single product_property by id" do
+ api_get :show, :id => property_1.id
+ json_response.count.should eq 1
+ json_response.should have_attributes(attributes)
+ end
+ end
+
+ end
+end
diff --git a/api/spec/controllers/spree/api/v1/products_controller_spec.rb b/api/spec/controllers/spree/api/v1/products_controller_spec.rb
index 59c0c9904c0..5a2d4a735c6 100644
--- a/api/spec/controllers/spree/api/v1/products_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/products_controller_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
+require 'shared_examples/protect_product_actions'
module Spree
describe Spree::Api::V1::ProductsController do
@@ -33,15 +34,24 @@ module Spree
second_product = create(:product)
api_get :index, :page => 2
json_response["products"].first.should have_attributes(attributes)
- json_response["count"].should == 2
+ json_response["total_count"].should == 2
json_response["current_page"].should == 2
json_response["pages"].should == 2
end
+
+ it 'can control the page size through a parameter' do
+ create(:product)
+ api_get :index, :per_page => 1
+ json_response['count'].should == 1
+ json_response['total_count'].should == 2
+ json_response['current_page'].should == 1
+ json_response['pages'].should == 2
+ end
end
it "can search for products" do
create(:product, :name => "The best product in the world")
- api_get :search, :q => { :name_cont => "best" }
+ api_get :index, :q => { :name_cont => "best" }
json_response["products"].first.should have_attributes(attributes)
json_response["count"].should == 1
end
@@ -105,20 +115,7 @@ module Spree
required_attributes.should include("price")
end
- it "cannot create a new product if not an admin" do
- api_post :create, :product => { :name => "Brand new product!" }
- assert_unauthorized!
- end
-
- it "cannot update a product" do
- api_put :update, :id => product.to_param, :product => { :name => "I hacked your store!" }
- assert_unauthorized!
- end
-
- it "cannot delete a product" do
- api_delete :destroy, :id => product.to_param
- assert_unauthorized!
- end
+ it_behaves_like "modifying product actions are restricted"
end
context "as an admin" do
@@ -156,6 +153,25 @@ module Spree
response.status.should == 201
end
+ # Regression test for #2140
+ context "with authentication_required set to false" do
+ before do
+ Spree::Api::Config.requires_authentication = false
+ end
+
+ after do
+ Spree::Api::Config.requires_authentication = true
+ end
+
+ it "can still create a product" do
+ api_post :create, :product => { :name => "The Other Product",
+ :price => 19.99 },
+ :token => "fake"
+ json_response.should have_attributes(attributes)
+ response.status.should == 201
+ end
+ end
+
it "cannot create a new product with invalid attributes" do
api_post :create, :product => {}
response.status.should == 422
@@ -180,7 +196,7 @@ module Spree
it "can delete a product" do
product.deleted_at.should be_nil
api_delete :destroy, :id => product.to_param
- response.status.should == 200
+ response.status.should == 204
product.reload.deleted_at.should_not be_nil
end
end
diff --git a/api/spec/controllers/spree/api/v1/return_authorizations_controller_spec.rb b/api/spec/controllers/spree/api/v1/return_authorizations_controller_spec.rb
new file mode 100644
index 00000000000..c054c7018ea
--- /dev/null
+++ b/api/spec/controllers/spree/api/v1/return_authorizations_controller_spec.rb
@@ -0,0 +1,155 @@
+require 'spec_helper'
+
+module Spree
+ describe Api::V1::ReturnAuthorizationsController do
+ render_views
+
+ let!(:order) do
+ order = create(:order)
+ order.line_items << create(:line_item)
+ order.shipments << create(:shipment, :state => 'shipped')
+ order.finalize!
+ order.shipments.each(&:ready!)
+ order.shipments.each(&:ship!)
+ order
+ end
+
+ let(:product) { create(:product) }
+ let(:attributes) { [:id, :reason, :amount, :state] }
+ let(:resource_scoping) { { :order_id => order.to_param } }
+
+ before do
+ stub_authentication!
+ end
+
+ context "as the order owner" do
+ before do
+ Order.any_instance.stub :user => current_api_user
+ end
+
+ it "cannot see any return authorizations" do
+ api_get :index
+ assert_unauthorized!
+ end
+
+ it "cannot see a single return authorization" do
+ api_get :show, :id => 1
+ assert_unauthorized!
+ end
+
+ it "cannot learn how to create a new return authorization" do
+ api_get :new
+ assert_unauthorized!
+ end
+
+ it "cannot create a new return authorization" do
+ api_post :create
+ assert_unauthorized!
+ end
+
+ it "cannot update a return authorization" do
+ api_put :update
+ assert_unauthorized!
+ end
+
+ it "cannot delete a return authorization" do
+ api_delete :destroy
+ assert_unauthorized!
+ end
+ end
+
+ context "as an admin" do
+ sign_in_as_admin!
+
+ it "can show return authorization" do
+ order.return_authorizations << create(:return_authorization)
+ return_authorization = order.return_authorizations.first
+ api_get :show, :order_id => order.id, :id => return_authorization.id
+ response.status.should == 200
+ json_response.should have_attributes(attributes)
+ json_response["return_authorization"]["state"].should_not be_blank
+ end
+
+ it "can get a list of return authorizations" do
+ order.return_authorizations << create(:return_authorization)
+ order.return_authorizations << create(:return_authorization)
+ api_get :index, { :order_id => order.id }
+ response.status.should == 200
+ return_authorizations = json_response["return_authorizations"]
+ return_authorizations.first.should have_attributes(attributes)
+ return_authorizations.first.should_not == return_authorizations.last
+ end
+
+ it 'can control the page size through a parameter' do
+ order.return_authorizations << create(:return_authorization)
+ order.return_authorizations << create(:return_authorization)
+ api_get :index, :order_id => order.id, :per_page => 1
+ json_response['count'].should == 1
+ json_response['current_page'].should == 1
+ json_response['pages'].should == 2
+ end
+
+ it 'can query the results through a paramter' do
+ order.return_authorizations << create(:return_authorization)
+ expected_result = create(:return_authorization, :reason => 'damaged')
+ order.return_authorizations << expected_result
+ api_get :index, :q => { :reason_cont => 'damage' }
+ json_response['count'].should == 1
+ json_response['return_authorizations'].first['return_authorization']['reason'].should eq expected_result.reason
+ end
+
+ it "can learn how to create a new return authorization" do
+ api_get :new
+ json_response["attributes"].should == ["id", "number", "state", "amount", "order_id", "reason", "created_at", "updated_at"]
+ required_attributes = json_response["required_attributes"]
+ required_attributes.should include("order")
+ end
+
+ it "can update a return authorization on the order" do
+ order.return_authorizations << create(:return_authorization)
+ return_authorization = order.return_authorizations.first
+ api_put :update, :id => return_authorization.id, :return_authorization => { :amount => 19.99 }
+ response.status.should == 200
+ json_response.should have_attributes(attributes)
+ end
+
+ it "can delete a return authorization on the order" do
+ order.return_authorizations << create(:return_authorization)
+ return_authorization = order.return_authorizations.first
+ api_delete :destroy, :id => return_authorization.id
+ response.status.should == 204
+ lambda { return_authorization.reload }.should raise_error(ActiveRecord::RecordNotFound)
+ end
+
+ it "can add a new return authorization to an existing order" do
+ api_post :create, :return_autorization => { :order_id => order.id, :amount => 14.22, :reason => "Defective" }
+ response.status.should == 201
+ json_response.should have_attributes(attributes)
+ json_response["return_authorization"]["state"].should_not be_blank
+ end
+ end
+
+ context "as just another user" do
+ it "cannot add a return authorization to the order" do
+ api_post :create, :return_autorization => { :order_id => order.id, :amount => 14.22, :reason => "Defective" }
+ assert_unauthorized!
+ end
+
+ it "cannot update a return authorization on the order" do
+ order.return_authorizations << create(:return_authorization)
+ return_authorization = order.return_authorizations.first
+ api_put :update, :id => return_authorization.id, :return_authorization => { :amount => 19.99 }
+ assert_unauthorized!
+ return_authorization.reload.amount.should_not == 19.99
+ end
+
+ it "cannot delete a return authorization on the order" do
+ order.return_authorizations << create(:return_authorization)
+ return_authorization = order.return_authorizations.first
+ api_delete :destroy, :id => return_authorization.id
+ assert_unauthorized!
+ lambda { return_authorization.reload }.should_not raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/api/spec/controllers/spree/api/v1/shipments_controller_spec.rb b/api/spec/controllers/spree/api/v1/shipments_controller_spec.rb
index c5476b4b454..cc5886ba0ed 100644
--- a/api/spec/controllers/spree/api/v1/shipments_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/shipments_controller_spec.rb
@@ -1,16 +1,31 @@
require 'spec_helper'
describe Spree::Api::V1::ShipmentsController do
+ render_views
let!(:shipment) { create(:shipment) }
let!(:attributes) { [:id, :tracking, :number, :cost, :shipped_at] }
before do
- Spree::Order.any_instance.stub(:paid? => true)
+ Spree::Order.any_instance.stub(:paid? => true, :complete? => true)
stub_authentication!
end
- context "working with a shipment" do
- let!(:resource_scoping) { { :order_id => shipment.order.to_param, :id => shipment.to_param } }
+ let!(:resource_scoping) { { :order_id => shipment.order.to_param, :id => shipment.to_param } }
+
+ context "as a non-admin" do
+ it "cannot make a shipment ready" do
+ api_put :ready
+ assert_unauthorized!
+ end
+
+ it "cannot make a shipment shipped" do
+ api_put :ship
+ assert_unauthorized!
+ end
+ end
+
+ context "as an admin" do
+ sign_in_as_admin!
it "can make a shipment ready" do
api_put :ready
diff --git a/api/spec/controllers/spree/api/v1/taxonomies_controller_spec.rb b/api/spec/controllers/spree/api/v1/taxonomies_controller_spec.rb
index 32fb2bed04e..73ab462b6d7 100644
--- a/api/spec/controllers/spree/api/v1/taxonomies_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/taxonomies_controller_spec.rb
@@ -20,8 +20,23 @@ module Spree
it "gets all taxonomies" do
api_get :index
- json_response.first['taxonomy']['name'].should eq taxonomy.name
- json_response.first['taxonomy']['root']['taxons'].count.should eq 1
+ json_response["taxonomies"].first['taxonomy']['name'].should eq taxonomy.name
+ json_response["taxonomies"].first['taxonomy']['root']['taxons'].count.should eq 1
+ end
+
+ it 'can control the page size through a parameter' do
+ create(:taxonomy)
+ api_get :index, :per_page => 1
+ json_response['count'].should == 1
+ json_response['current_page'].should == 1
+ json_response['pages'].should == 2
+ end
+
+ it 'can query the results through a paramter' do
+ expected_result = create(:taxonomy, :name => 'Style')
+ api_get :index, :q => { :name_cont => 'style' }
+ json_response['count'].should == 1
+ json_response['taxonomies'].first['taxonomy']['name'].should eq expected_result.name
end
it "gets a single taxonomy" do
@@ -82,7 +97,11 @@ module Spree
json_response["error"].should == "Invalid resource. Please fix errors and try again."
errors = json_response["errors"]
end
- end
+ it "can destroy" do
+ api_delete :destroy, :id => taxonomy.id
+ response.status.should == 204
+ end
+ end
end
end
diff --git a/api/spec/controllers/spree/api/v1/taxons_controller_spec.rb b/api/spec/controllers/spree/api/v1/taxons_controller_spec.rb
index bd7de8192db..82d7d7f6d71 100644
--- a/api/spec/controllers/spree/api/v1/taxons_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/taxons_controller_spec.rb
@@ -4,9 +4,9 @@ module Spree
describe Api::V1::TaxonsController do
render_views
- let(:taxonomy) { Factory(:taxonomy) }
- let(:taxon) { Factory(:taxon, :name => "Ruby", :taxonomy => taxonomy) }
- let(:taxon2) { Factory(:taxon, :name => "Rails", :taxonomy => taxonomy) }
+ let(:taxonomy) { create(:taxonomy) }
+ let(:taxon) { create(:taxon, :name => "Ruby", :taxonomy => taxonomy) }
+ let(:taxon2) { create(:taxon, :name => "Rails", :taxonomy => taxonomy) }
let(:attributes) { ["id", "name", "permalink", "position", "parent_id", "taxonomy_id"] }
before do
@@ -76,6 +76,11 @@ module Spree
taxon.reload.children.count.should eq 1
end
+
+ it "can destroy" do
+ api_delete :destroy, :taxonomy_id => taxonomy.id, :id => taxon.id
+ response.status.should == 204
+ end
end
end
diff --git a/api/spec/controllers/spree/api/v1/unauthenticated_products_controller_spec.rb b/api/spec/controllers/spree/api/v1/unauthenticated_products_controller_spec.rb
new file mode 100644
index 00000000000..0d3a10a9a6f
--- /dev/null
+++ b/api/spec/controllers/spree/api/v1/unauthenticated_products_controller_spec.rb
@@ -0,0 +1,26 @@
+require 'shared_examples/protect_product_actions'
+require 'spec_helper'
+
+module Spree
+ describe Spree::Api::V1::ProductsController do
+ render_views
+
+ let!(:product) { create(:product) }
+ let(:attributes) { [:id, :name, :description, :price, :available_on, :permalink, :count_on_hand, :meta_description, :meta_keywords, :taxon_ids] }
+
+ context "without authentication" do
+ before { Spree::Api::Config[:requires_authentication] = false }
+
+ it "retreives a list of products" do
+ api_get :index
+ json_response["products"].first.should have_attributes(attributes)
+ json_response["count"].should == 1
+ json_response["current_page"].should == 1
+ json_response["pages"].should == 1
+ end
+
+ it_behaves_like "modifying product actions are restricted"
+ end
+ end
+end
+
diff --git a/api/spec/controllers/spree/api/v1/variants_controller_spec.rb b/api/spec/controllers/spree/api/v1/variants_controller_spec.rb
index 9b57a8fdff2..5d353c0e550 100644
--- a/api/spec/controllers/spree/api/v1/variants_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/variants_controller_spec.rb
@@ -20,16 +20,68 @@ module Spree
stub_authentication!
end
- it "can see a list of all variants" do
+ it "can see a paginated list of variants" do
api_get :index
- json_response.first.should have_attributes(attributes)
- option_values = json_response.last["variant"]["option_values"]
+ json_response["variants"].first.should have_attributes(attributes)
+ json_response["count"].should == 1
+ json_response["current_page"].should == 1
+ json_response["pages"].should == 1
+ end
+
+ it 'can control the page size through a parameter' do
+ create(:variant)
+ api_get :index, :per_page => 1
+ json_response['count'].should == 1
+ json_response['current_page'].should == 1
+ json_response['pages'].should == 3
+ end
+
+ it 'can query the results through a paramter' do
+ expected_result = create(:variant, :sku => 'FOOBAR')
+ api_get :index, :q => { :sku_cont => 'FOO' }
+ json_response['count'].should == 1
+ json_response['variants'].first['variant']['sku'].should eq expected_result.sku
+ end
+
+ it "variants returned contain option values data" do
+ api_get :index
+ option_values = json_response["variants"].last["variant"]["option_values"]
option_values.first.should have_attributes([:name,
:presentation,
:option_type_name,
:option_type_id])
end
+ # Regression test for #2141
+ context "a deleted variant" do
+ before do
+ variant.update_column(:deleted_at, Time.now)
+ end
+
+ it "is not returned in the results" do
+ api_get :index
+ json_response["variants"].count.should == 0
+ end
+
+ it "is not returned even when show_deleted is passed" do
+ api_get :index, :show_deleted => true
+ json_response["variants"].count.should == 0
+ end
+ end
+
+ context "pagination" do
+ default_per_page(1)
+
+ it "can select the next page of variants" do
+ second_variant = create(:variant)
+ api_get :index, :page => 2
+ json_response["variants"].first.should have_attributes(attributes)
+ json_response["total_count"].should == 3
+ json_response["current_page"].should == 2
+ json_response["pages"].should == 3
+ end
+ end
+
it "can see a single variant" do
api_get :show, :id => variant.to_param
json_response.should have_attributes(attributes)
@@ -66,6 +118,18 @@ module Spree
sign_in_as_admin!
let(:resource_scoping) { { :product_id => variant.product.to_param } }
+ # Test for #2141
+ context "deleted variants" do
+ before do
+ variant.update_column(:deleted_at, Time.now)
+ end
+
+ it "are visible by admin" do
+ api_get :index, :show_deleted => 1
+ json_response["variants"].count.should == 1
+ end
+ end
+
it "can create a new variant" do
api_post :create, :variant => { :sku => "12345" }
json_response.should have_attributes(attributes)
@@ -81,7 +145,7 @@ module Spree
it "can delete a variant" do
api_delete :destroy, :id => variant.to_param
- response.status.should == 200
+ response.status.should == 204
lambda { variant.reload }.should raise_error(ActiveRecord::RecordNotFound)
end
end
diff --git a/api/spec/controllers/spree/api/v1/zones_controller_spec.rb b/api/spec/controllers/spree/api/v1/zones_controller_spec.rb
index 6b8b5572543..787054b8ed9 100644
--- a/api/spec/controllers/spree/api/v1/zones_controller_spec.rb
+++ b/api/spec/controllers/spree/api/v1/zones_controller_spec.rb
@@ -13,7 +13,22 @@ module Spree
it "gets list of zones" do
api_get :index
- json_response.first.should have_attributes(attributes)
+ json_response['zones'].first.should have_attributes(attributes)
+ end
+
+ it 'can control the page size through a parameter' do
+ create(:zone)
+ api_get :index, :per_page => 1
+ json_response['count'].should == 1
+ json_response['current_page'].should == 1
+ json_response['pages'].should == 2
+ end
+
+ it 'can query the results through a paramter' do
+ expected_result = create(:zone, :name => 'South America')
+ api_get :index, :q => { :name_cont => 'south' }
+ json_response['count'].should == 1
+ json_response['zones'].first['zone']['name'].should eq expected_result.name
end
it "gets a zone" do
@@ -27,26 +42,46 @@ module Spree
sign_in_as_admin!
it "can create a new zone" do
- api_post :create, :zone => { :name => "North Pole",
- :zone_members => [ :zone_member => {
- :zoneable_id => 1 }] }
+ params = {
+ :zone => {
+ :name => "North Pole",
+ :zone_members => [
+ {
+ :zoneable_type => "Spree::Country",
+ :zoneable_id => 1
+ }
+ ]
+ }
+ }
+
+ api_post :create, params
response.status.should == 201
json_response.should have_attributes(attributes)
+ json_response["zone"]["zone_members"].should_not be_empty
end
it "updates a zone" do
- api_put :update, :id => @zone.id,
- :zone => { :name => "Americas",
- :zone_members => [ :zone_member => {
- :zoneable_type => 'Spree::Country',
- :zoneable_id => 1 }]}
+ params = { :id => @zone.id,
+ :zone => {
+ :name => "North Pole",
+ :zone_members => [
+ {
+ :zoneable_type => "Spree::Country",
+ :zoneable_id => 1
+ }
+ ]
+ }
+ }
+
+ api_put :update, params
response.status.should == 200
- json_response['zone']['name'].should eq 'Americas'
+ json_response['zone']['name'].should eq 'North Pole'
+ json_response['zone']['zone_members'].should_not be_blank
end
it "can delete a zone" do
api_delete :destroy, :id => @zone.id
- response.status.should == 200
+ response.status.should == 204
lambda { @zone.reload }.should raise_error(ActiveRecord::RecordNotFound)
end
end
diff --git a/api/spec/shared_examples/protect_product_actions.rb b/api/spec/shared_examples/protect_product_actions.rb
new file mode 100644
index 00000000000..0dc3c2607d8
--- /dev/null
+++ b/api/spec/shared_examples/protect_product_actions.rb
@@ -0,0 +1,17 @@
+shared_examples "modifying product actions are restricted" do
+ it "cannot create a new product if not an admin" do
+ api_post :create, :product => { :name => "Brand new product!" }
+ assert_unauthorized!
+ end
+
+ it "cannot update a product" do
+ api_put :update, :id => product.to_param, :product => { :name => "I hacked your store!" }
+ assert_unauthorized!
+ end
+
+ it "cannot delete a product" do
+ api_delete :destroy, :id => product.to_param
+ assert_unauthorized!
+ end
+end
+
diff --git a/api/spec/spec_helper.rb b/api/spec/spec_helper.rb
index 01c364898c3..b2f1b6903ae 100644
--- a/api/spec/spec_helper.rb
+++ b/api/spec/spec_helper.rb
@@ -19,4 +19,9 @@
config.include FactoryGirl::Syntax::Methods
config.include Spree::Api::TestingSupport::Helpers, :type => :controller
config.extend Spree::Api::TestingSupport::Setup, :type => :controller
+ config.include Spree::Core::TestingSupport::Preferences, :type => :controller
+
+ config.before do
+ Spree::Api::Config[:requires_authentication] = true
+ end
end
diff --git a/api/spree_api.gemspec b/api/spree_api.gemspec
index 3d42a469866..3596f6ee847 100644
--- a/api/spree_api.gemspec
+++ b/api/spree_api.gemspec
@@ -16,7 +16,6 @@ Gem::Specification.new do |gem|
gem.version = version
gem.add_dependency 'spree_core', version
- gem.add_dependency 'rabl', '0.6.5'
gem.add_development_dependency 'rspec-rails', '2.9.0'
gem.add_development_dependency 'database_cleaner'
diff --git a/cmd/lib/spree_cmd.rb b/cmd/lib/spree_cmd.rb
index 69c8eae1f73..ef3ea40140b 100644
--- a/cmd/lib/spree_cmd.rb
+++ b/cmd/lib/spree_cmd.rb
@@ -1,19 +1,15 @@
require 'thor'
require 'thor/group'
-module SpreeCmd
- class Command
-
- if ARGV.first == 'version'
- puts Gem.loaded_specs['spree_cmd'].version
- elsif ARGV.first == 'extension'
- ARGV.shift
- require 'spree_cmd/extension'
- SpreeCmd::Extension.start
- else
- ARGV.shift
- require 'spree_cmd/installer'
- SpreeCmd::Installer.start
- end
- end
-end
\ No newline at end of file
+case ARGV.first
+ when 'version', '-v', '--version'
+ puts Gem.loaded_specs['spree_cmd'].version
+ when 'extension'
+ ARGV.shift
+ require 'spree_cmd/extension'
+ SpreeCmd::Extension.start
+ else
+ ARGV.shift
+ require 'spree_cmd/installer'
+ SpreeCmd::Installer.start
+end
diff --git a/cmd/lib/spree_cmd/extension.rb b/cmd/lib/spree_cmd/extension.rb
index 2044dd0d88d..788b7009d65 100644
--- a/cmd/lib/spree_cmd/extension.rb
+++ b/cmd/lib/spree_cmd/extension.rb
@@ -51,7 +51,7 @@ def class_name
end
def spree_version
- '1.2.0.rc1'
+ '1.2.2'
end
def use_prefix(prefix)
diff --git a/cmd/lib/spree_cmd/installer.rb b/cmd/lib/spree_cmd/installer.rb
index 6cd6fbea8e2..038c5c0ea5a 100644
--- a/cmd/lib/spree_cmd/installer.rb
+++ b/cmd/lib/spree_cmd/installer.rb
@@ -1,4 +1,5 @@
require 'rbconfig'
+require 'active_support/core_ext/string'
module SpreeCmd
@@ -24,9 +25,6 @@ class Installer < Thor::Group
class_option :branch, :type => :string, :desc => 'Spree gem git branch'
class_option :tag, :type => :string, :desc => 'Spree gem git tag'
- class_option :precompile_assets, :type => :boolean, :default => true,
- :desc => 'Precompile spree assets to public/assets'
-
def verify_rails
unless rails_project?
say "#{@app_path} is not a rails project."
@@ -56,14 +54,22 @@ def prepare_options
elsif options[:version]
@spree_gem_options[:version] = options[:version]
else
- require 'spree/core/version'
- @spree_gem_options[:version] = Spree.version
+ version = Gem.loaded_specs['spree_cmd'].version
+ @spree_gem_options[:version] = version.to_s
end
end
def ask_questions
@install_default_gateways = ask_with_default('Would you like to install the default gateways?')
@install_default_auth = ask_with_default('Would you like to install the default authentication system?')
+ unless @install_default_auth
+ @user_class = ask("What is the name of the class representing users within your application? [User]")
+ if @user_class.blank?
+ @user_class = "User"
+ end
+ else
+ @user_class = "Spree::User"
+ end
if options[:skip_install_data]
@run_migrations = false
@@ -79,8 +85,6 @@ def ask_questions
@load_sample_data = false
end
end
-
- @precompile_assets = options[:precompile_assets] && ask_with_default('Would you like to precompile assets?')
end
def add_gems
@@ -94,7 +98,7 @@ def add_gems
end
if @install_default_auth
- gem :spree_auth_devise, :git => "git://github.com/radar/spree_auth_devise"
+ gem :spree_auth_devise, :github => "spree/spree_auth_devise", :branch => "1-2-stable"
end
run 'bundle install', :capture => true
@@ -106,6 +110,7 @@ def initialize_spree
spree_options << "--migrate=#{@run_migrations}"
spree_options << "--seed=#{@load_seed_data}"
spree_options << "--sample=#{@load_sample_data}"
+ spree_options << "--user_class=#{@user_class}"
spree_options << "--auto_accept" if options[:auto_accept]
inside @app_path do
@@ -113,15 +118,6 @@ def initialize_spree
end
end
- def precompile_assets
- if @precompile_assets
- say_status :precompiling, 'assets'
- inside @app_path do
- run 'bundle exec rake assets:precompile', :verbose => false
- end
- end
- end
-
private
def gem(name, gem_options={})
@@ -181,10 +177,16 @@ def windows?
end
def image_magick_installed?
+ cmd = 'identify -version'
+ if RUBY_PLATFORM =~ /mingw|mswin/ #windows
+ cmd += " >nul"
+ else
+ cmd += " >/dev/null"
+ end
# true if command executed succesfully
# false for non zero exit status
# nil if command execution fails
- system('identify -version')
+ system(cmd)
end
end
end
diff --git a/cmd/lib/spree_cmd/templates/extension/Gemfile b/cmd/lib/spree_cmd/templates/extension/Gemfile
index d65e2a66952..17dbc22025e 100644
--- a/cmd/lib/spree_cmd/templates/extension/Gemfile
+++ b/cmd/lib/spree_cmd/templates/extension/Gemfile
@@ -1,3 +1,6 @@
source 'http://rubygems.org'
+# Provides basic authentication functionality for testing parts of your engine
+gem 'spree_auth_devise', :git => "git://github.com/spree/spree_auth_devise"
+
gemspec
diff --git a/cmd/lib/spree_cmd/templates/extension/gitignore b/cmd/lib/spree_cmd/templates/extension/gitignore
index 2b6b0de6feb..e8a3f9c27e6 100644
--- a/cmd/lib/spree_cmd/templates/extension/gitignore
+++ b/cmd/lib/spree_cmd/templates/extension/gitignore
@@ -4,6 +4,7 @@
.DS_Store
.idea
.project
+.sass-cache
coverage
Gemfile.lock
tmp
diff --git a/cmd/lib/spree_cmd/templates/extension/spec/spec_helper.rb.tt b/cmd/lib/spree_cmd/templates/extension/spec/spec_helper.rb.tt
index 8d6355dee89..2bc8b0923d4 100644
--- a/cmd/lib/spree_cmd/templates/extension/spec/spec_helper.rb.tt
+++ b/cmd/lib/spree_cmd/templates/extension/spec/spec_helper.rb.tt
@@ -12,6 +12,9 @@ Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f }
# Requires factories defined in spree_core
require 'spree/core/testing_support/factories'
+require 'spree/core/testing_support/env'
+require 'spree/core/testing_support/controller_requests'
+require 'spree/core/testing_support/authorization_helpers'
require 'spree/core/url_helpers'
RSpec.configure do |config|
diff --git a/common_spree_dependencies.rb b/common_spree_dependencies.rb
index 259a631c20c..eb97ab1dc4b 100644
--- a/common_spree_dependencies.rb
+++ b/common_spree_dependencies.rb
@@ -1,4 +1,4 @@
-# By placing all of Spree's shared dependencies in this file and then loading
+# By placing all of Spree's shared dependencies in this file and then loading
# it for each component's Gemfile, we can be sure that we're only testing just
# the one component of Spree.
source 'http://rubygems.org'
@@ -24,21 +24,13 @@
gem 'ffaker'
gem 'shoulda-matchers', '~> 1.0.0'
- gem 'capybara'
- gem 'selenium-webdriver', '2.22.2'
+ gem 'capybara', '1.1.3'
+ gem 'selenium-webdriver', '2.25.0'
gem 'database_cleaner', '0.7.1'
gem 'launchy'
# gem 'debugger'
end
-# platform :ruby_18 do
-# gem "ruby-debug"
-# end
-
-# platform :ruby_19 do
-# gem "ruby-debug19"
-# end
-
gemspec
diff --git a/core/app/assets/images/noimage/large.png b/core/app/assets/images/noimage/large.png
new file mode 100644
index 00000000000..1caf12fea9f
Binary files /dev/null and b/core/app/assets/images/noimage/large.png differ
diff --git a/core/app/assets/javascripts/admin/admin.js.erb b/core/app/assets/javascripts/admin/admin.js.erb
index d2de82a07fa..c0a3223c9af 100644
--- a/core/app/assets/javascripts/admin/admin.js.erb
+++ b/core/app/assets/javascripts/admin/admin.js.erb
@@ -1,5 +1,7 @@
+//<%#encoding: UTF-8%>
//= require_self
//= require admin/product_autocomplete
+//= require admin/taxon_autocomplete
//= require select2
/**
@@ -49,9 +51,9 @@ $.fn.radioControlsVisibilityOfElement = function(dependentElementSelector){
$.fn.objectPicker = function(url){
$(this).tokenInput(url + "&authenticity_token=" + escape(AUTH_TOKEN), {
searchDelay : 600,
- hintText : strings.type_to_search,
- noResultsText : strings.no_results,
- searchingText : strings.searching,
+ hintText : Spree.translations.type_to_search,
+ noResultsText : Spree.translations.no_results,
+ searchingText : Spree.translations.searching,
prePopulateFromInput : true
});
};
@@ -62,12 +64,12 @@ $.fn.productPicker = function(){
handle_date_picker_fields = function(){
$('.datepicker').datepicker({
- dateFormat: "<%= ::I18n.t(:format, :scope => 'spree.date_picker', :default => 'yy/mm/dd') %>",
- dayNames: <%= ::I18n.t(:day_names, :scope => :date).to_json %>,
- dayNamesMin: <%= ::I18n.t(:abbr_day_names, :scope => :date).to_json %>,
- monthNames: <%= (::I18n.t(:month_names, :scope => :date).delete_if(&:blank?)).to_json %>,
- prevText: '<%= ::I18n.t(:previous) %>',
- nextText: '<%= ::I18n.t(:next) %>',
+ dateFormat: Spree.translations.date_picker,
+ dayNames: Spree.translations.abbr_day_names,
+ dayNamesMin: Spree.translations.abbr_day_names,
+ monthNames: Spree.translations.month_names,
+ prevText: Spree.translations.previous,
+ nextText: Spree.translations.next,
showOn: "button",
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>",
buttonImageOnly: true
diff --git a/core/app/assets/javascripts/admin/checkouts/edit.js b/core/app/assets/javascripts/admin/checkouts/edit.js
index 7324e8783b2..f89f72798a9 100644
--- a/core/app/assets/javascripts/admin/checkouts/edit.js
+++ b/core/app/assets/javascripts/admin/checkouts/edit.js
@@ -33,7 +33,7 @@ $(document).ready(function(){
}
prep_user_autocomplete_data = function(data){
- return $.map(eval(data), function(row) {
+ return $.map(eval(data['users']), function(row) {
return {
data: row['user'],
value: row['user']['email'],
@@ -45,10 +45,10 @@ $(document).ready(function(){
if ($("#customer_search").length > 0) {
$("#customer_search").autocomplete({
minChars: 5,
- delay: 1500,
+ delay: 500,
source: function(request, response) {
var params = { q: $('#customer_search').val(),
- authenticity_token: encodeURIComponent($('meta[name=csrf-token]').attr("content")) }
+ authenticity_token: AUTH_TOKEN }
$.get(Spree.routes.user_search + '&' + jQuery.param(params), function(data) {
result = prep_user_autocomplete_data(data)
response(result);
@@ -81,7 +81,7 @@ $(document).ready(function(){
$('#user_id').val(ui.item.data['id']);
$('#guest_checkout_true').prop("checked", false);
$('#guest_checkout_false').prop("checked", true);
- $('#guest_checkout_false').prob("disabled", false);
+ $('#guest_checkout_false').prop("disabled", false);
return true;
}
}).data("autocomplete")._renderItem = function(ul, item) {
diff --git a/core/app/assets/javascripts/admin/product_autocomplete.js.erb b/core/app/assets/javascripts/admin/product_autocomplete.js.erb
index 5fe05e762f4..1d2789d97aa 100644
--- a/core/app/assets/javascripts/admin/product_autocomplete.js.erb
+++ b/core/app/assets/javascripts/admin/product_autocomplete.js.erb
@@ -1,3 +1,4 @@
+<%#encoding: UTF-8%>
// Product autocompletion
image_html = function(item){
return "";
@@ -15,9 +16,9 @@ format_product_autocomplete = function(item){
html += "
\1
')) end - def variant_images_hash(product) - product.variant_images.inject({}) { |h, img| (h[img.viewable_id] ||= []) << img; h } - end - def line_item_description(variant) description = variant.product.description if description.present? diff --git a/core/app/models/spree/ability.rb b/core/app/models/spree/ability.rb index 3b8ff4e49e3..a6864a7fb55 100644 --- a/core/app/models/spree/ability.rb +++ b/core/app/models/spree/ability.rb @@ -36,13 +36,13 @@ def initialize(user) can :manage, :all else ############################# - can :read, LegacyUser do |resource| + can :read, Spree.user_class do |resource| resource == user end - can :update, LegacyUser do |resource| + can :update, Spree.user_class do |resource| resource == user end - can :create, LegacyUser + can :create, Spree.user_class ############################# can :read, Order do |order, token| order.user == user || order.token && token == order.token @@ -51,6 +51,11 @@ def initialize(user) order.user == user || order.token && token == order.token end can :create, Order + + can :read, Address do |address| + address.user == user + end + ############################# can :read, Product can :index, Product diff --git a/core/app/models/spree/address.rb b/core/app/models/spree/address.rb index bc3cad421d0..ad090e80a1c 100644 --- a/core/app/models/spree/address.rb +++ b/core/app/models/spree/address.rb @@ -36,7 +36,7 @@ def full_name end def state_text - state.nil? ? state_name : (state.abbr.blank? ? state.name : state.abbr) + state.try(:abbr) || state.try(:name) || state_name end def same_as?(other) diff --git a/core/app/models/spree/adjustment.rb b/core/app/models/spree/adjustment.rb index db4083f09a2..a4d778ba60d 100644 --- a/core/app/models/spree/adjustment.rb +++ b/core/app/models/spree/adjustment.rb @@ -65,6 +65,10 @@ def update!(src = nil) set_eligibility end + def display_amount + Spree::Money.new(amount).to_s + end + private def update_adjustable diff --git a/core/app/models/spree/app_configuration.rb b/core/app/models/spree/app_configuration.rb index bcc8d34be51..3a355b11b29 100644 --- a/core/app/models/spree/app_configuration.rb +++ b/core/app/models/spree/app_configuration.rb @@ -41,6 +41,9 @@ class AppConfiguration < Preferences::Configuration preference :checkout_zone, :string, :default => nil # replace with the name of a zone if you would like to limit the countries preference :company, :boolean, :default => false # Request company field for billing and shipping addr preference :create_inventory_units, :boolean, :default => true # should only be false when track_inventory_levels is false, also disables RMA's + preference :currency, :string, :default => "USD" + preference :currency_symbol_position, :string, :default => "before" + preference :display_currency, :boolean, :default => false preference :default_country_id, :integer, :default => 214 preference :default_locale, :string, :default => Rails.application.config.i18n.default_locale || :en preference :default_meta_description, :string, :default => 'Spree demo site' @@ -48,7 +51,7 @@ class AppConfiguration < Preferences::Configuration preference :default_seo_title, :string, :default => '' preference :dismissed_spree_alerts, :string, :default => '' preference :last_check_for_spree_alerts, :string, :default => nil - preference :layout, :string, :default => '/spree/layouts/spree_application' + preference :layout, :string, :default => 'spree/layouts/spree_application' preference :logo, :string, :default => 'admin/bg/spree_50.png' preference :max_level_in_taxons_menu, :integer, :default => 1 # maximum nesting level in taxons menu preference :orders_per_page, :integer, :default => 15 diff --git a/core/app/models/spree/calculator/price_sack.rb b/core/app/models/spree/calculator/price_sack.rb index e45703abc7c..a3ea0c18bc9 100644 --- a/core/app/models/spree/calculator/price_sack.rb +++ b/core/app/models/spree/calculator/price_sack.rb @@ -1,4 +1,6 @@ require_dependency 'spree/calculator' +# For #to_d method on Ruby 1.8 +require 'bigdecimal/util' module Spree class Calculator::PriceSack < Calculator @@ -17,12 +19,12 @@ def self.description # as object we always get line items, as calculable we have Coupon, ShippingMethod def compute(object) if object.is_a?(Array) - base = object.map { |o| o.respond_to?(:amount) ? o.amount : o.to_d }.sum + base = object.map { |o| o.respond_to?(:amount) ? o.amount : BigDecimal(o.to_s) }.sum else - base = object.respond_to?(:amount) ? object.amount : object.to_d + base = object.respond_to?(:amount) ? object.amount : BigDecimal(object.to_s) end - if base >= self.preferred_minimal_amount + if base < self.preferred_minimal_amount self.preferred_normal_amount else self.preferred_discount_amount diff --git a/core/app/models/spree/credit_card.rb b/core/app/models/spree/credit_card.rb index bd850c170f5..d85ffca341a 100644 --- a/core/app/models/spree/credit_card.rb +++ b/core/app/models/spree/credit_card.rb @@ -1,6 +1,6 @@ module Spree class CreditCard < ActiveRecord::Base - has_many :payments + has_many :payments, :as => :source before_save :set_last_digits after_validation :set_card_type @@ -30,7 +30,7 @@ class << self # sets self.cc_type while we still have the card number def set_card_type - self.cc_type ||= CardDetector.type?(number) + self.cc_type ||= CardDetector.brand?(number) end def name? @@ -63,7 +63,7 @@ def brand cc_type end - scope :with_payment_profile, where('gateway_customer_profile_id IS NOT NULL') + scope :with_payment_profile, lambda { where('gateway_customer_profile_id IS NOT NULL') } def actions %w{capture void credit} diff --git a/core/app/models/spree/image.rb b/core/app/models/spree/image.rb index 089360948bf..d52138fbffc 100644 --- a/core/app/models/spree/image.rb +++ b/core/app/models/spree/image.rb @@ -9,7 +9,8 @@ class Image < Asset :styles => { :mini => '48x48>', :small => '100x100>', :product => '240x240>', :large => '600x600>' }, :default_style => :product, :url => '/spree/products/:id/:style/:basename.:extension', - :path => ':rails_root/public/spree/products/:id/:style/:basename.:extension' + :path => ':rails_root/public/spree/products/:id/:style/:basename.:extension', + :convert_options => { :all => '-strip' } # save the w,h of the original image (from which others can be calculated) # we need to look at the write-queue for images which have not been saved yet after_post_process :find_dimensions diff --git a/core/app/models/spree/inventory_unit.rb b/core/app/models/spree/inventory_unit.rb index 8eab2ad4b6c..cd020232471 100644 --- a/core/app/models/spree/inventory_unit.rb +++ b/core/app/models/spree/inventory_unit.rb @@ -5,7 +5,13 @@ class InventoryUnit < ActiveRecord::Base belongs_to :shipment belongs_to :return_authorization - scope :backorder, where(:state => 'backordered') + scope :backordered, lambda { where(:state => 'backordered') } + scope :shipped, lambda { where(:state => 'shipped') } + + def self.backorder + warn "[SPREE] Spree::InventoryUnit.backorder will be deprecated in Spree 1.3. Please use Spree::Product.backordered instead." + backordered + end attr_accessible :shipment @@ -107,8 +113,10 @@ def update_order end def restock_variant - variant.on_hand = (variant.on_hand + 1) - variant.save + if Spree::Config[:track_inventory_levels] + variant.on_hand += 1 + variant.save + end end end end diff --git a/core/app/models/spree/line_item.rb b/core/app/models/spree/line_item.rb index f481f6fdb54..9bcd364fc36 100644 --- a/core/app/models/spree/line_item.rb +++ b/core/app/models/spree/line_item.rb @@ -10,7 +10,7 @@ class LineItem < ActiveRecord::Base before_validation :copy_price validates :variant, :presence => true - validates :quantity, :numericality => { :only_integer => true, :message => I18n.t('validation.must_be_int') } + validates :quantity, :numericality => { :only_integer => true, :message => I18n.t('validation.must_be_int'), :greater_than => -1 } validates :price, :numericality => true validate :stock_availability validate :quantity_no_less_than_shipped @@ -80,6 +80,7 @@ def remove_inventory def update_order # update the order totals, etc. + order.create_tax_charge! order.update! end @@ -97,7 +98,7 @@ def stock_availability end def quantity_no_less_than_shipped - already_shipped = order.shipments.reduce(0) { |acc,s| acc + s.inventory_units.count { |i| i.variant == variant } } + already_shipped = order.shipments.reduce(0) { |acc, s| acc + s.inventory_units.shipped.where(:variant_id => variant_id).count } unless quantity >= already_shipped errors.add(:quantity, I18n.t('validation.cannot_be_less_than_shipped_units')) end diff --git a/core/app/models/spree/order.rb b/core/app/models/spree/order.rb index ebb24d4a09e..ab7847a4367 100644 --- a/core/app/models/spree/order.rb +++ b/core/app/models/spree/order.rb @@ -1,8 +1,17 @@ require 'spree/core/validators/email' +require 'spree/order/checkout' module Spree class Order < ActiveRecord::Base - include Checkout + # TODO: + # Need to use fully qualified name here because during sandbox migration + # there is a class called Checkout which conflicts if you use this: + # + # include Checkout + # + # rather than the qualified name. This will most likely be fixed with the + # 1.3 release. + include Spree::Order::Checkout checkout_flow do go_to_state :address go_to_state :delivery @@ -54,7 +63,6 @@ class Order < ActiveRecord::Base before_create :link_by_email after_create :create_tax_charge! - # TODO: validate the format of the email as well (but we can't rely on authlogic anymore to help with validation) validates :email, :presence => true, :email => true, :if => :require_email validate :has_available_shipment validate :has_available_payment @@ -98,6 +106,10 @@ def amount line_items.sum(&:amount) end + def display_total + Spree::Money.new(total) + end + def to_param number.to_s.to_url.upcase end @@ -115,6 +127,7 @@ def checkout_allowed? # Is this a free order in which case the payment step should be skipped def payment_required? + update_totals total.to_f > 0.0 end @@ -131,7 +144,7 @@ def item_count # Indicates whether there are any backordered InventoryUnits associated with the Order. def backordered? return false unless Spree::Config[:track_inventory_levels] - inventory_units.backorder.present? + inventory_units.backordered.present? end # Returns the relevant zone (if any) to be used for taxation purposes. Uses default tax zone @@ -217,7 +230,7 @@ def clone_billing_address def allow_cancel? return false unless completed? and state != 'canceled' - %w{ready backorder pending}.include? shipment_state + shipment_state.nil? || %w{ready backorder pending}.include?(shipment_state) end def allow_resume? @@ -226,6 +239,10 @@ def allow_resume? true end + def awaiting_returns? + return_authorizations.any? { |return_authorization| return_authorization.authorized? } + end + def add_variant(variant, quantity = 1) current_item = find_line_item_by_variant(variant) if current_item @@ -337,8 +354,16 @@ def credit_cards def finalize! touch :completed_at InventoryUnit.assign_opening_inventory(self) - # lock any optional adjustments (coupon promotions, etc.) - adjustments.optional.each { |adjustment| adjustment.update_column('locked', true) } + + # lock all adjustments (coupon promotions, etc.) + adjustments.each { |adjustment| adjustment.update_column('locked', true) } + + # update payment and shipment(s) states, and save + update_payment_state + shipments.each { |shipment| shipment.update!(self) } + update_shipment_state + save + deliver_order_confirmation_email self.state_changes.create({ @@ -366,8 +391,23 @@ def available_shipping_methods(display_on = nil) end def rate_hash - @rate_hash ||= available_shipping_methods(:front_end).collect do |ship_method| - next unless cost = ship_method.calculator.compute(self) + return @rate_hash if @rate_hash.present? + + # reserve one slot for each shipping method computation + computed_costs = Array.new(available_shipping_methods(:front_end).size) + + # create all the threads and kick off their execution + threads = available_shipping_methods(:front_end).each_with_index.map do |ship_method, index| + Thread.new { computed_costs[index] = [ship_method, ship_method.calculator.compute(self)] } + end + + # wait for all threads to finish + threads.map(&:join) + + # now consolidate and memoize the threaded results + @rate_hash ||= computed_costs.map do |pair| + ship_method,cost = *pair + next unless cost ShippingRate.new( :id => ship_method.id, :shipping_method => ship_method, :name => ship_method.name, @@ -395,9 +435,21 @@ def payment_method end end + def pending_payments + payments.select {|p| p.state == "checkout"} + end + def process_payments! begin - payments.each(&:process!) + pending_payments.each do |payment| + break if payment_total >= total + + payment.process! + + if payment.completed? + self.payment_total += payment.amount + end + end rescue Core::GatewayError !!Spree::Config[:allow_checkout_on_gateway_error] end @@ -415,6 +467,10 @@ def products line_items.map { |li| li.variant.product } end + def variants + line_items.map(&:variant) + end + def insufficient_stock_lines line_items.select &:insufficient_stock? end @@ -492,7 +548,7 @@ def update_shipment_state # # The +payment_state+ value helps with reporting, etc. since it provides a quick and easy way to locate Orders needing attention. def update_payment_state - + #line_item are empty when user empties cart if self.line_items.empty? || round_money(payment_total) < round_money(total) self.payment_state = 'balance_due' diff --git a/core/app/models/spree/order/checkout.rb b/core/app/models/spree/order/checkout.rb index 51d1d00ddb3..908abd3f03b 100644 --- a/core/app/models/spree/order/checkout.rb +++ b/core/app/models/spree/order/checkout.rb @@ -3,26 +3,40 @@ class Order < ActiveRecord::Base module Checkout def self.included(klass) klass.class_eval do - cattr_accessor :next_event_transitions - cattr_accessor :previous_states - cattr_accessor :checkout_flow - cattr_accessor :checkout_steps + class_attribute :next_event_transitions + class_attribute :previous_states + class_attribute :checkout_flow + class_attribute :checkout_steps def self.checkout_flow(&block) if block_given? @checkout_flow = block + define_state_machine! else @checkout_flow end end def self.define_state_machine! - self.checkout_steps = [] + # Needs to be an ordered hash to preserve flow order + self.checkout_steps = ActiveSupport::OrderedHash.new self.next_event_transitions = [] self.previous_states = [:cart] + + # Build the checkout flow using the checkout_flow defined either + # within the Order class, or a decorator for that class. + # + # This method may be called multiple times depending on if the + # checkout_flow is re-defined in a decorator or not. instance_eval(&checkout_flow) + klass = self + # To avoid a ton of warnings when the state machine is re-defined + StateMachine::Machine.ignore_method_conflicts = true + # To avoid multiple occurrences of the same transition being defined + # On first definition, state_machines will not be defined + state_machines.clear if respond_to?(:state_machines) state_machine :state, :initial => :cart do klass.next_event_transitions.each { |t| transition(t.merge(:on => :next)) } @@ -37,7 +51,7 @@ def self.define_state_machine! end event :return do - transition :to => :returned, :from => :awaiting_return + transition :to => :returned, :from => :awaiting_return, :unless => :awaiting_returns? end event :resume do @@ -50,7 +64,7 @@ def self.define_state_machine! before_transition :to => :complete do |order| begin - order.process_payments! + order.process_payments! if order.payment_required? rescue Spree::Core::GatewayError !!Spree::Config[:allow_checkout_on_gateway_error] end @@ -69,15 +83,12 @@ def self.define_state_machine! def self.go_to_state(name, options={}) self.checkout_steps[name] = options + previous_states.each do |state| + add_transition({:from => state, :to => name}.merge(options)) + end if options[:if] - previous_states.each do |state| - add_transition({:from => state, :to => name}.merge(options)) - end self.previous_states << name else - previous_states.each do |state| - add_transition({:from => state, :to => name}.merge(options)) - end self.previous_states = [name] end end diff --git a/core/app/models/spree/payment.rb b/core/app/models/spree/payment.rb index 61fb0c72c7e..5d2b818465c 100644 --- a/core/app/models/spree/payment.rb +++ b/core/app/models/spree/payment.rb @@ -18,7 +18,7 @@ class Payment < ActiveRecord::Base attr_accessible :amount, :payment_method_id, :source_attributes - scope :from_credit_card, where(:source_type => 'Spree::CreditCard') + scope :from_credit_card, lambda { where(:source_type => 'Spree::CreditCard') } scope :with_state, lambda { |s| where(:state => s) } scope :completed, with_state('completed') scope :pending, with_state('pending') diff --git a/core/app/models/spree/payment/processing.rb b/core/app/models/spree/payment/processing.rb index c4d53be97ba..64208d9f975 100644 --- a/core/app/models/spree/payment/processing.rb +++ b/core/app/models/spree/payment/processing.rb @@ -28,6 +28,7 @@ def purchase! end def capture! + return true if completed? protect_from_connection_error do check_environment @@ -47,6 +48,7 @@ def capture! end def void_transaction! + return true if void? protect_from_connection_error do check_environment @@ -104,6 +106,25 @@ def partial_credit(amount) credit!(amount) end + def gateway_options + options = { :email => order.email, + :customer => order.email, + :ip => '192.168.1.100', # TODO: Use an actual IP + :order_id => order.number } + + options.merge!({ :shipping => order.ship_total * 100, + :tax => order.tax_total * 100, + :subtotal => order.item_total * 100 }) + + options.merge!({ :currency => payment_method.preferences[:currency_code] }) if payment_method && payment_method.preferences[:currency_code] + + options.merge!({ :billing_address => order.bill_address.try(:active_merchant_hash), + :shipping_address => order.ship_address.try(:active_merchant_hash) }) + + options.merge!(:discount => promo_total) if respond_to?(:promo_total) + options + end + private def gateway_action(source, action, success_state) @@ -136,22 +157,6 @@ def record_response(response) log_entries.create({:details => response.to_yaml}, :without_protection => true) end - def gateway_options - options = { :email => order.email, - :customer => order.email, - :ip => '192.168.1.100', # TODO: Use an actual IP - :order_id => order.number } - - options.merge!({ :shipping => order.ship_total * 100, - :tax => order.tax_total * 100, - :subtotal => order.item_total * 100 }) - - options.merge!({ :currency => payment_method.preferences[:currency_code] }) if payment_method && payment_method.preferences[:currency_code] - - options.merge({ :billing_address => order.bill_address.try(:active_merchant_hash), - :shipping_address => order.ship_address.try(:active_merchant_hash) }) - end - def protect_from_connection_error begin yield diff --git a/core/app/models/spree/payment_method.rb b/core/app/models/spree/payment_method.rb index f74995214ad..3f3b32edeae 100644 --- a/core/app/models/spree/payment_method.rb +++ b/core/app/models/spree/payment_method.rb @@ -3,7 +3,7 @@ class PaymentMethod < ActiveRecord::Base DISPLAY = [:both, :front_end, :back_end] default_scope where(:deleted_at => nil) - scope :production, where(:environment => 'production') + scope :production, lambda { where(:environment => 'production') } attr_accessible :name, :description, :environment, :display_on, :active diff --git a/core/app/models/spree/preference.rb b/core/app/models/spree/preference.rb index 6e83f6cead7..ce555f088f8 100644 --- a/core/app/models/spree/preference.rb +++ b/core/app/models/spree/preference.rb @@ -4,7 +4,7 @@ class Spree::Preference < ActiveRecord::Base validates :key, :presence => true validates :value_type, :presence => true - scope :valid, where(Spree::Preference.arel_table[:key].not_eq(nil)).where(Spree::Preference.arel_table[:value_type].not_eq(nil)) + scope :valid, lambda { where(Spree::Preference.arel_table[:key].not_eq(nil)).where(Spree::Preference.arel_table[:value_type].not_eq(nil)) } # The type conversions here should match # the ones in spree::preferences::preferrable#convert_preference_value diff --git a/core/app/models/spree/preferences/preferable_class_methods.rb b/core/app/models/spree/preferences/preferable_class_methods.rb index dcc0f6a5048..f24a391db9b 100644 --- a/core/app/models/spree/preferences/preferable_class_methods.rb +++ b/core/app/models/spree/preferences/preferable_class_methods.rb @@ -15,6 +15,8 @@ def preference(name, type, *args) else if get_pending_preference(name) get_pending_preference(name) + elsif Spree::Preference.table_exists? && preference = Spree::Preference.find_by_name(name) + preference.value else send self.class.preference_default_getter_method(name) end diff --git a/core/app/models/spree/preferences/store.rb b/core/app/models/spree/preferences/store.rb index 3c2f5c44380..c4022d02508 100644 --- a/core/app/models/spree/preferences/store.rb +++ b/core/app/models/spree/preferences/store.rb @@ -23,11 +23,29 @@ def set(key, value, type) end def exist?(key) - @cache.exist? key + @cache.exist?(key) || + should_persist? && Spree::Preference.where(:key => key).exists? end def get(key) - @cache.read(key) + # return the retrieved value, if it's in the cache + if (val = @cache.read(key)).present? + return val + end + + return nil unless should_persist? + + # If it's not in the cache, maybe it's in the database, but + # has been cleared from the cache + + # does it exist in the database? + if preference = Spree::Preference.find_by_key(key) + # it does exist, so let's put it back into the cache + @cache.write(preference.key, preference.value) + + # and return the value + preference.value + end end def delete(key) diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index 4bbfe5d293a..4b399c6a1d9 100755 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -30,8 +30,21 @@ class Product < ActiveRecord::Base belongs_to :shipping_category has_one :master, - :class_name => "Spree::Variant", - :conditions => { :is_master => true } + :class_name => 'Spree::Variant', + :conditions => { :is_master => true }, + :dependent => :destroy + + has_many :variants, + :class_name => 'Spree::Variant', + :conditions => { :is_master => false, :deleted_at => nil }, + :order => :position + + has_many :variants_including_master, + :class_name => 'Spree::Variant', + :conditions => { :deleted_at => nil }, + :dependent => :destroy + + has_many :variants_including_master_and_deleted, :class_name => 'Spree::Variant' delegate_belongs_to :master, :sku, :price, :weight, :height, :width, :depth, :is_master delegate_belongs_to :master, :cost_price if Variant.table_exists? && Variant.column_names.include?('cost_price') @@ -43,33 +56,13 @@ class Product < ActiveRecord::Base after_save :save_master after_save :set_master_on_hand_to_zero_when_product_has_variants - has_many :variants, - :class_name => "Spree::Variant", - :conditions => { :is_master => false, :deleted_at => nil }, - :order => 'position ASC' - - has_many :variants_including_master, - :class_name => "Spree::Variant", - :conditions => { :deleted_at => nil }, - :dependent => :destroy + delegate :images, :to => :master, :prefix => true + alias_method :images, :master_images - has_many :variants_with_only_master, - :class_name => "Spree::Variant", - :conditions => { :is_master => true, :deleted_at => nil }, - :dependent => :destroy + has_many :variant_images, :source => :images, :through => :variants_including_master, :order => :position accepts_nested_attributes_for :variants, :allow_destroy => true - def variant_images - Image.joins("LEFT JOIN #{Variant.quoted_table_name} ON #{Variant.quoted_table_name}.id = #{Spree::Asset.quoted_table_name}.viewable_id"). - where("(#{Spree::Asset.quoted_table_name}.viewable_type = ? AND #{Spree::Asset.quoted_table_name}.viewable_id = ?) OR - (#{Spree::Asset.quoted_table_name}.viewable_type = ? AND #{Spree::Asset.quoted_table_name}.viewable_id = ?)", Variant.name, self.master.id, Product.name, self.id). - order("#{Spree::Asset.quoted_table_name}.position"). - extend(Spree::Core::RelationSerialization) - end - - alias_method :images, :variant_images - validates :name, :price, :permalink, :presence => true attr_accessor :option_values_hash @@ -91,18 +84,29 @@ def variant_images after_initialize :ensure_master - def ensure_master - return unless new_record? - self.master ||= Variant.new + def variants_with_only_master + ActiveSupport::Deprecation.warn("[SPREE] Spree::Product#variants_with_only_master will be deprecated in Spree 1.3. Please use Spree::Product#master instead.") + master end + def to_param permalink.present? ? permalink : (permalink_was || name.to_s.to_url) end # returns true if the product has any variants (the master variant is not a member of the variants array) def has_variants? - variants.exists? + variants.any? + end + + # should product be displayed on products pages and search + def on_display? + has_stock? || Spree::Config[:show_zero_stock_products] + end + + # is this product actually available for purchase + def on_sale? + has_stock? || Spree::Config[:allow_backorders] end # returns the number of inventory units "on_hand" for this product @@ -130,6 +134,13 @@ def tax_category end end + # override the delete method to set deleted_at value + # instead of actually deleting the product. + def delete + self.update_column(:deleted_at, Time.now) + variants_including_master.update_all(:deleted_at => Time.now) + end + # Adding properties and option types on creation based on a chosen prototype attr_reader :prototype_id def prototype_id=(value) @@ -180,6 +191,10 @@ def deleted? !!deleted_at end + def available? + !(available_on.nil? || available_on.future?) + end + # split variants list into hash which shows mapping of opt value onto matching variants # eg categorise_variants_from_option(color) => {"red" -> [...], "blue" -> [...]} def categorise_variants_from_option(opt_type) @@ -215,6 +230,10 @@ def set_property(property_name, property_value) end end + def display_price + Spree::Money.new(price).to_s + end + private # Builds variants from a hash of option types & values @@ -260,6 +279,11 @@ def set_master_variant_defaults def save_master master.save if master && (master.changed? || master.new_record?) end + + def ensure_master + return unless new_record? + self.master ||= Variant.new + end end end diff --git a/core/app/models/spree/product/scopes.rb b/core/app/models/spree/product/scopes.rb index 08b5e907485..4601e4e4db1 100644 --- a/core/app/models/spree/product/scopes.rb +++ b/core/app/models/spree/product/scopes.rb @@ -27,11 +27,11 @@ def self.simple_scopes end add_search_scope :ascend_by_master_price do - joins(:variants_with_only_master).order("#{variant_table_name}.price ASC") + joins(:master).order("#{variant_table_name}.price ASC") end add_search_scope :descend_by_master_price do - joins(:variants_with_only_master).order("#{variant_table_name}.price DESC") + joins(:master).order("#{variant_table_name}.price DESC") end add_search_scope :price_between do |low, high| @@ -50,8 +50,23 @@ def self.simple_scopes # If you need products only within one taxon use # # Spree::Product.taxons_id_eq(x) + # + # If you're using count on the result of this scope, you must use the + # `:distinct` option as well: + # + # Spree::Product.in_taxon(taxon).count(:distinct => true) + # + # This is so that the count query is distinct'd: + # + # SELECT COUNT(DISTINCT "spree_products"."id") ... + # + # vs. + # + # SELECT COUNT(*) ... add_search_scope :in_taxon do |taxon| - joins(:taxons).where(Taxon.table_name => { :id => taxon.self_and_descendants.map(&:id) }) + select("DISTINCT(spree_products.id), spree_products.*"). + joins(:taxons). + where(Taxon.table_name => { :id => taxon.self_and_descendants.map(&:id) }) end # This scope selects products in all taxons AND all its descendants @@ -63,10 +78,6 @@ def self.simple_scopes taxons.first ? prepare_taxon_conditions(taxons) : scoped end - # def self.in_cached_group(product_group) - # joins(:product_groups).where('spree_product_groups_products.product_group_id' => product_group) - # end - # a scope that finds all products having property specified by name, object or id add_search_scope :with_property do |property| properties = Property.table_name @@ -113,7 +124,7 @@ def self.simple_scopes end conditions = "#{option_values}.name = ? AND #{option_values}.option_type_id = ?", value, option_type_id - select("DISTINCT spree_products.id").joins(:variants_including_master => :option_values).where(conditions) + group("spree_products.id").joins(:variants_including_master => :option_values).where(conditions) end # Finds all products which have either: @@ -188,11 +199,11 @@ def self.available(available_on = nil) add_search_scope :on_hand do variants_table = Variant.table_name - where("#{table_name}.id in (select product_id from #{variants_table} where product_id = #{table_name}.id group by product_id having sum(count_on_hand) > 0)") + where("#{table_name}.id in (select product_id from #{variants_table} where product_id = #{table_name}.id and #{variants_table}.deleted_at IS NULL group by product_id having sum(count_on_hand) > 0)") end add_search_scope :taxons_name_eq do |name| - select("DISTINCT spree_products.id").joins(:taxons).where(Taxon.arel_table[:name].eq(name)) + group("spree_products.id").joins(:taxons).where(Taxon.arel_table[:name].eq(name)) end if (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL') diff --git a/core/app/models/spree/return_authorization.rb b/core/app/models/spree/return_authorization.rb index be7eca34beb..760e48b968c 100644 --- a/core/app/models/spree/return_authorization.rb +++ b/core/app/models/spree/return_authorization.rb @@ -72,6 +72,7 @@ def process_return credit.source = self credit.adjustable = order credit.save + order.return if inventory_units.all?(&:returned?) end def allow_receive? diff --git a/core/app/models/spree/shipment.rb b/core/app/models/spree/shipment.rb index 2c6b727061c..9808c2cbad4 100644 --- a/core/app/models/spree/shipment.rb +++ b/core/app/models/spree/shipment.rb @@ -26,9 +26,10 @@ class Shipment < ActiveRecord::Base make_permalink :field => :number - scope :shipped, where(:state => 'shipped') - scope :ready, where(:state => 'ready') - scope :pending, where(:state => 'pending') + scope :with_state, lambda { |s| where(:state => s) } + scope :shipped, with_state('shipped') + scope :ready, with_state('ready') + scope :pending, with_state('pending') def to_param number if number @@ -114,10 +115,11 @@ def validate_shipping_method # Determines the appropriate +state+ according to the following logic: # - # pending unless +order.payment_state+ is +paid+ + # pending unless order is complete and +order.payment_state+ is +paid+ # shipped if already shipped (ie. does not change the state) # ready all other cases def determine_state(order) + return 'pending' unless order.complete? return 'pending' if inventory_units.any? &:backordered? return 'shipped' if state == 'shipped' order.paid? ? 'ready' : 'pending' diff --git a/core/app/models/spree/shipping_method.rb b/core/app/models/spree/shipping_method.rb index 5b1537ee89c..b22e9eb3d9f 100644 --- a/core/app/models/spree/shipping_method.rb +++ b/core/app/models/spree/shipping_method.rb @@ -1,6 +1,9 @@ module Spree class ShippingMethod < ActiveRecord::Base DISPLAY = [:both, :front_end, :back_end] + + default_scope where(:deleted_at => nil) + has_many :shipments validates :name, :zone, :presence => true @@ -13,17 +16,13 @@ class ShippingMethod < ActiveRecord::Base calculated_adjustments def available?(order, display_on = nil) - displayable? && calculator.available?(order) + displayable?(display_on) && calculator.available?(order) end - def displayable? + def displayable?(display_on) (self.display_on == display_on.to_s || self.display_on.blank?) end - def calculator_available?(order) - caluclator.available?(order) - end - def within_zone?(order) zone && zone.include?(order.ship_address) end diff --git a/core/app/models/spree/shipping_rate.rb b/core/app/models/spree/shipping_rate.rb index aba92108a41..35668556af6 100644 --- a/core/app/models/spree/shipping_rate.rb +++ b/core/app/models/spree/shipping_rate.rb @@ -5,5 +5,15 @@ def initialize(attributes = {}) self.send("#{k}=", v) end end + + def display_price + if Spree::Config[:shipment_inc_vat] + price = (1 + Spree::TaxRate.default) * cost + else + price = cost + end + + Spree::Money.new(price) + end end end diff --git a/core/app/models/spree/tax_rate.rb b/core/app/models/spree/tax_rate.rb index 5affe2666c3..bebe82eda32 100644 --- a/core/app/models/spree/tax_rate.rb +++ b/core/app/models/spree/tax_rate.rb @@ -20,7 +20,7 @@ class TaxRate < ActiveRecord::Base calculated_adjustments scope :by_zone, lambda { |zone| where(:zone_id => zone) } - attr_accessible :amount, :tax_category_id, :calculator, :zone_id, :included_in_price + attr_accessible :amount, :tax_category_id, :calculator, :zone_id, :included_in_price, :name, :show_rate_in_label # Gets the array of TaxRates appropriate for the specified order def self.match(order) @@ -73,7 +73,9 @@ def adjust(order) private def create_label - "#{tax_category.name} #{amount * 100}%" + label = "" + label << (name.present? ? name : tax_category.name) + " " + label << (show_rate_in_label? ? "#{amount * 100}%" : "") end end end diff --git a/core/app/models/spree/taxon.rb b/core/app/models/spree/taxon.rb index 6c939671dde..ecd861da1aa 100644 --- a/core/app/models/spree/taxon.rb +++ b/core/app/models/spree/taxon.rb @@ -51,5 +51,12 @@ def active_products scope end + def pretty_name + ancestor_chain = self.ancestors.inject("") do |name, ancestor| + name += "#{ancestor.name} -> " + end + ancestor_chain + "#{name}" + end + end end diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index aee12b30a3d..9a87cae7c7a 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -6,7 +6,7 @@ class Variant < ActiveRecord::Base :tax_category_id, :shipping_category_id, :meta_description, :meta_keywords, :tax_category - attr_accessible :name, :presentation, :cost_price, + attr_accessible :name, :presentation, :cost_price, :lock_version, :position, :on_hand, :option_value_ids, :product_id, :option_values_attributes, :price, :weight, :height, :width, :depth, :sku @@ -21,48 +21,33 @@ class Variant < ActiveRecord::Base validates :cost_price, :numericality => { :greater_than_or_equal_to => 0, :allow_nil => true } if self.table_exists? && self.column_names.include?('cost_price') validates :count_on_hand, :numericality => true + after_save :process_backorders after_save :recalculate_product_on_hand, :if => :is_master? # default variant scope only lists non-deleted variants - scope :active, where(:deleted_at => nil) - scope :deleted, where('deleted_at IS NOT NULL') + scope :active, lambda { where(:deleted_at => nil) } + scope :deleted, lambda { where('deleted_at IS NOT NULL') } # Returns number of inventory units for this variant (new records haven't been saved to database, yet) def on_hand Spree::Config[:track_inventory_levels] ? count_on_hand : (1.0 / 0) # Infinity end - # Adjusts the inventory units to match the given new level. + # set actual attribute def on_hand=(new_level) if Spree::Config[:track_inventory_levels] - new_level = new_level.to_i - - # increase Inventory when - if new_level > on_hand - # fill backordered orders before creating new units - backordered_units = inventory_units.with_state('backordered') - backordered_units.slice(0, new_level).each(&:fill_backorder) - new_level -= backordered_units.length - end - self.count_on_hand = new_level else raise 'Cannot set on_hand value when Spree::Config[:track_inventory_levels] is false' end end - # strips all non-price-like characters from the price. def price=(price) - if price.present? - self[:price] = price.to_s.gsub(/[^0-9\.-]/, '').to_f - end + self[:price] = parse_price(price) if price.present? end - # and cost_price def cost_price=(price) - if price.present? - self[:cost_price] = price.to_s.gsub(/[^0-9\.-]/, '').to_f - end + self[:cost_price] = parse_price(price) if price.present? end # returns number of units currently on backorder for this variant. @@ -140,6 +125,38 @@ def option_value(opt_name) private + def process_backorders + if count_changes = changes['count_on_hand'] + new_level = count_changes.last + + if Spree::Config[:track_inventory_levels] + new_level = new_level.to_i + + # update backorders if level is positive + if new_level > 0 + # fill backordered orders before creating new units + backordered_units = inventory_units.with_state('backordered') + backordered_units.slice(0, new_level).each(&:fill_backorder) + new_level -= backordered_units.length + end + + self.update_attribute_without_callbacks(:count_on_hand, new_level) + end + end + end + + # strips all non-price-like characters from the price, taking into account locale settings + def parse_price(price) + return price unless price.is_a?(String) + + separator, delimiter = I18n.t([:'number.currency.format.separator', :'number.currency.format.delimiter']) + non_price_characters = /[^0-9\-#{separator}]/ + price.gsub!(non_price_characters, '') # strip everything else first + price.gsub!(separator, '.') unless separator == '.' # then replace the locale-specific decimal separator with the standard separator if necessary + + price.to_d + end + # Ensures a new variant takes the product master price when price is not supplied def check_price if price.nil? diff --git a/core/app/views/spree/admin/adjustments/_adjustments_table.html.erb b/core/app/views/spree/admin/adjustments/_adjustments_table.html.erb index 250f8a55ded..efd4257cbcd 100644 --- a/core/app/views/spree/admin/adjustments/_adjustments_table.html.erb +++ b/core/app/views/spree/admin/adjustments/_adjustments_table.html.erb @@ -12,7 +12,7 @@+ <%= f.check_box :inventory_units_shipment_id_null, { }, '1', '0' %> + <%= label_tag nil, t(:show_only_unfulfilled_orders) %> +
<%= button t(:search) %>
<%= @order.outstanding_balance < 0 ? t(:credit_owed) : t(:balance_due) %> <%= number_to_currency @order.outstanding_balance %>
+<%= @order.outstanding_balance < 0 ? t(:credit_owed) : t(:balance_due) %> <%= money(@order.outstanding_balance) %>
<% end %><%= f.label :price, t(:price) %>:
- <%= f.text_field :price, :value => number_with_precision(@variant.price, :precision => 2) %>
<%= f.label :cost_price, t(:cost_price) %>:
- <%= f.text_field :cost_price, :value => number_with_precision(@variant.cost_price, :precision => 2) %>
<%= f.label :on_hand, t(:on_hand) %>:
diff --git a/core/app/views/spree/admin/variants/index.html.erb b/core/app/views/spree/admin/variants/index.html.erb
index 9083e705345..f81fce2a8f7 100644
--- a/core/app/views/spree/admin/variants/index.html.erb
+++ b/core/app/views/spree/admin/variants/index.html.erb
@@ -16,7 +16,7 @@
<% next if variant.option_values.empty? %>
- <%= form.label :email %>
- <%= form.text_field :email %>
-