spree_address_book 0.40.0.rc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +12 -0
- data/LICENSE +23 -0
- data/README.md +10 -0
- data/Rakefile +77 -0
- data/app/controllers/addresses_controller.rb +12 -0
- data/app/controllers/checkout_controller_decorator.rb +35 -0
- data/app/models/address_decorator.rb +26 -0
- data/app/models/order_decorator.rb +60 -0
- data/app/models/user_decorator.rb +3 -0
- data/app/views/addresses/_form.html.erb +46 -0
- data/app/views/addresses/destroy.js.erb +2 -0
- data/app/views/checkout/_address.html.erb +82 -0
- data/config/initializers/address_fields.rb +1 -0
- data/config/locales/en.yml +2 -0
- data/config/locales/ru.yml +2 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20110302102208_add_user_id_and_deleted_at_to_addresses.rb +15 -0
- data/features/address_book.feature +70 -0
- data/features/step_definitions/address_book_steps.rb +21 -0
- data/features/support/env.rb +22 -0
- data/features/support/paths.rb +40 -0
- data/lib/spree_address_book.rb +17 -0
- data/lib/spree_address_book_hooks.rb +3 -0
- data/lib/tasks/install.rake +25 -0
- data/lib/tasks/spree_address_book.rake +1 -0
- data/public/javascripts/checkout.js +73 -0
- data/spec/models/address_spec.rb +5 -0
- data/spec/spec_helper.rb +32 -0
- data/spree_address_book.gemspec +23 -0
- metadata +122 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
2
|
+
are permitted provided that the following conditions are met:
|
|
3
|
+
|
|
4
|
+
* Redistributions of source code must retain the above copyright notice,
|
|
5
|
+
this list of conditions and the following disclaimer.
|
|
6
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
7
|
+
this list of conditions and the following disclaimer in the documentation
|
|
8
|
+
and/or other materials provided with the distribution.
|
|
9
|
+
* Neither the name of the Rails Dog LLC nor the names of its
|
|
10
|
+
contributors may be used to endorse or promote products derived from this
|
|
11
|
+
software without specific prior written permission.
|
|
12
|
+
|
|
13
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
14
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
15
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
16
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
17
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
18
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
19
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
20
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
21
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
22
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
23
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
data/Rakefile
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
require 'rake/testtask'
|
|
4
|
+
require 'rake/packagetask'
|
|
5
|
+
require 'rake/gempackagetask'
|
|
6
|
+
|
|
7
|
+
gemfile = File.expand_path('../spec/test_app/Gemfile', __FILE__)
|
|
8
|
+
if File.exists?(gemfile) && (%w(spec cucumber).include?(ARGV.first.to_s) || ARGV.size == 0)
|
|
9
|
+
require 'bundler'
|
|
10
|
+
ENV['BUNDLE_GEMFILE'] = gemfile
|
|
11
|
+
Bundler.setup
|
|
12
|
+
|
|
13
|
+
require 'rspec'
|
|
14
|
+
require 'rspec/core/rake_task'
|
|
15
|
+
RSpec::Core::RakeTask.new
|
|
16
|
+
|
|
17
|
+
require 'cucumber/rake/task'
|
|
18
|
+
Cucumber::Rake::Task.new do |t|
|
|
19
|
+
t.cucumber_opts = %w{--format progress}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc "Default Task"
|
|
24
|
+
task :default => [:spec, :cucumber ]
|
|
25
|
+
|
|
26
|
+
spec = eval(File.read('spree_address_book.gemspec'))
|
|
27
|
+
|
|
28
|
+
Rake::GemPackageTask.new(spec) do |p|
|
|
29
|
+
p.gem_spec = spec
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
desc "Release to gemcutter"
|
|
33
|
+
task :release => :package do
|
|
34
|
+
require 'rake/gemcutter'
|
|
35
|
+
Rake::Gemcutter::Tasks.new(spec).define
|
|
36
|
+
Rake::Task['gem:push'].invoke
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
desc "Default Task"
|
|
40
|
+
task :default => [ :spec ]
|
|
41
|
+
|
|
42
|
+
desc "Regenerates a rails 3 app for testing"
|
|
43
|
+
task :test_app do
|
|
44
|
+
require '../spree/lib/generators/spree/test_app_generator'
|
|
45
|
+
class AddressBookTestAppGenerator < Spree::Generators::TestAppGenerator
|
|
46
|
+
|
|
47
|
+
def install_gems
|
|
48
|
+
inside "test_app" do
|
|
49
|
+
run 'rake spree_core:install'
|
|
50
|
+
run 'rake spree_auth:install'
|
|
51
|
+
run 'rake spree_address_book:install'
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def migrate_db
|
|
56
|
+
run_migrations
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
protected
|
|
60
|
+
def full_path_for_local_gems
|
|
61
|
+
<<-gems
|
|
62
|
+
gem 'spree_core', :path => \'#{File.join(File.dirname(__FILE__), "../spree/", "core")}\'
|
|
63
|
+
gem 'spree_auth', :path => \'#{File.join(File.dirname(__FILE__), "../spree/", "auth")}\'
|
|
64
|
+
gem 'spree_address_book', :path => \'#{File.dirname(__FILE__)}\'
|
|
65
|
+
gems
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
AddressBookTestAppGenerator.start
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
namespace :test_app do
|
|
73
|
+
desc 'Rebuild test and cucumber databases'
|
|
74
|
+
task :rebuild_dbs do
|
|
75
|
+
system("cd spec/test_app && rake db:drop db:migrate RAILS_ENV=test && rake db:drop db:migrate RAILS_ENV=cucumber")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class AddressesController < Spree::BaseController
|
|
2
|
+
def destroy
|
|
3
|
+
@address = Address.find(params[:id])
|
|
4
|
+
if @address && @address.user == current_user
|
|
5
|
+
if @address.can_be_deleted?
|
|
6
|
+
@address.destroy
|
|
7
|
+
else
|
|
8
|
+
@address.update_attribute(:deleted_at, Time.now)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
CheckoutController.class_eval do
|
|
2
|
+
after_filter :normalize_addresses, :only => :update
|
|
3
|
+
before_filter :set_addresses, :only => :update
|
|
4
|
+
|
|
5
|
+
protected
|
|
6
|
+
|
|
7
|
+
def set_addresses
|
|
8
|
+
return unless params[:order] && params[:state] == "address"
|
|
9
|
+
|
|
10
|
+
if params[:order][:ship_address_id].to_i > 0
|
|
11
|
+
params[:order].delete(:ship_address_attributes)
|
|
12
|
+
else
|
|
13
|
+
params[:order].delete(:ship_address_id)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
if params[:order][:bill_address_id].to_i > 0
|
|
17
|
+
params[:order].delete(:bill_address_attributes)
|
|
18
|
+
else
|
|
19
|
+
params[:order].delete(:bill_address_id)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def normalize_addresses
|
|
24
|
+
return unless params[:state] == "address" && @order.bill_address_id && @order.ship_address_id
|
|
25
|
+
@order.bill_address.reload
|
|
26
|
+
@order.ship_address.reload
|
|
27
|
+
if @order.bill_address_id != @order.ship_address_id && @order.bill_address.same_as?(@order.ship_address)
|
|
28
|
+
@order.bill_address.destroy
|
|
29
|
+
@order.update_attribute(:bill_address_id, @order.ship_address.id)
|
|
30
|
+
else
|
|
31
|
+
@order.bill_address.update_attribute(:user_id, current_user.try(:id))
|
|
32
|
+
end
|
|
33
|
+
@order.ship_address.update_attribute(:user_id, current_user.try(:id))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Address.class_eval do
|
|
2
|
+
belongs_to :user
|
|
3
|
+
|
|
4
|
+
# can modify an address if it's not been used in an order
|
|
5
|
+
def editable?
|
|
6
|
+
new_record? || (shipments.empty? && (Order.where("bill_address_id = ?", self.id).count + Order.where("bill_address_id = ?", self.id).count <= 1) && Order.complete.where("bill_address_id = ? OR ship_address_id = ?", self.id, self.id).count == 0)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def can_be_deleted?
|
|
10
|
+
shipments.empty? && Order.where("bill_address_id = ? OR ship_address_id = ?", self.id, self.id).count == 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
"#{firstname} #{lastname}: #{zipcode}, #{country}, #{state || state_name}, #{city}, #{address1} #{address2}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def destroy_with_saving_used
|
|
18
|
+
if can_be_deleted?
|
|
19
|
+
destroy_without_saving_used
|
|
20
|
+
else
|
|
21
|
+
update_attribute(:deleted_at, Time.now)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
alias_method_chain :destroy, :saving_used
|
|
25
|
+
|
|
26
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Order.class_eval do
|
|
2
|
+
attr_accessible :bill_address_id, :ship_address_id
|
|
3
|
+
|
|
4
|
+
def clone_billing_address
|
|
5
|
+
if self.bill_address
|
|
6
|
+
self.ship_address = self.bill_address
|
|
7
|
+
end
|
|
8
|
+
true
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def bill_address_id=(id)
|
|
12
|
+
address = Address.find(id)
|
|
13
|
+
if address && address.user_id == self.user_id
|
|
14
|
+
self["bill_address_id"] = address.id
|
|
15
|
+
self.bill_address.reload
|
|
16
|
+
else
|
|
17
|
+
self["bill_address_id"] = nil
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def bill_address_attributes=(attributes)
|
|
22
|
+
self.bill_address = update_or_create_address(attributes)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def ship_address_id=(id)
|
|
26
|
+
address = Address.find(id)
|
|
27
|
+
if address && address.user_id == self.user_id
|
|
28
|
+
self["ship_address_id"] = address.id
|
|
29
|
+
self.ship_address.reload
|
|
30
|
+
else
|
|
31
|
+
self["ship_address_id"] = nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def ship_address_attributes=(attributes)
|
|
36
|
+
self.ship_address = update_or_create_address(attributes)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def update_or_create_address(attributes)
|
|
42
|
+
address = nil
|
|
43
|
+
if attributes[:id]
|
|
44
|
+
address = Address.find(attributes[:id])
|
|
45
|
+
if address && address.editable?
|
|
46
|
+
address.update_attributes(attributes)
|
|
47
|
+
else
|
|
48
|
+
attributes.delete(:id)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if !attributes[:id]
|
|
53
|
+
address = Address.new(attributes)
|
|
54
|
+
address.save
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
address
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<% ADDRESS_FIELDS.each do |field| %>
|
|
2
|
+
<p id="<%= [address_name, field].join('_') %>" class="field">
|
|
3
|
+
<% if field == "country" %>
|
|
4
|
+
<%= address_form.label :country_id, t(field, :scope => [:activerecord, :attributes, :address]) %>
|
|
5
|
+
<span><%= address_form.collection_select :country_id, available_countries, :id, :name, {}, {:class => 'required'} %></span>
|
|
6
|
+
<span class="req">*</span>
|
|
7
|
+
<% elsif field == "state" && Spree::Config[:address_requires_state] %>
|
|
8
|
+
<%= address_form.label :state_id, t(field, :scope => [:activerecord, :attributes, :address]) %>
|
|
9
|
+
<span id="state">
|
|
10
|
+
<% have_states = !address.country.states.empty? %>
|
|
11
|
+
<noscript>
|
|
12
|
+
<%= address_form.text_field :state_name, :class => 'required' %>
|
|
13
|
+
</noscript>
|
|
14
|
+
<% state_elements = [
|
|
15
|
+
address_form.collection_select(:state_id, address.country.states,
|
|
16
|
+
:id, :name,
|
|
17
|
+
{:include_blank => true},
|
|
18
|
+
{:class => have_states ? "required" : "hidden",
|
|
19
|
+
:disabled => !have_states}) +
|
|
20
|
+
address_form.text_field(:state_name,
|
|
21
|
+
:class => !have_states ? "required" : "hidden",
|
|
22
|
+
:disabled => have_states)
|
|
23
|
+
].join.gsub('"', "'").gsub("\n", "")
|
|
24
|
+
%>
|
|
25
|
+
<script type="text/javascript" language="javascript" charset="utf-8">
|
|
26
|
+
// <![CDATA[
|
|
27
|
+
document.write("<%= raw state_elements %>");
|
|
28
|
+
// ]]>
|
|
29
|
+
</script>
|
|
30
|
+
</span>
|
|
31
|
+
<span class="req">*</span>
|
|
32
|
+
<% elsif field == "address2" %>
|
|
33
|
+
<%= address_form.label field, t(field, :scope => [:activerecord, :attributes, :address]) %>
|
|
34
|
+
<%= address_form.text_field field %>
|
|
35
|
+
<% else %>
|
|
36
|
+
<%= address_form.label field, t(field, :scope => [:activerecord, :attributes, :address]) %>
|
|
37
|
+
<%= address_form.text_field field, :class => 'required' %><span class="req">*</span>
|
|
38
|
+
<% end %>
|
|
39
|
+
</p>
|
|
40
|
+
<% end %>
|
|
41
|
+
<% if Spree::Config["alternative_#{address_name}_phone"] %>
|
|
42
|
+
<p id="altphone">
|
|
43
|
+
<%= address_form.label :alternative_phone, t(:alternative_phone) %>
|
|
44
|
+
<%= address_form.text_field :alternative_phone %>
|
|
45
|
+
</p>
|
|
46
|
+
<% end %>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<% @addresses = current_user ? current_user.addresses : [] %>
|
|
2
|
+
<style>
|
|
3
|
+
div.inner input[type=text], div.inner select { width: 80%; }
|
|
4
|
+
.hidden { display: none; }
|
|
5
|
+
div#checkout #checkout_form_address #billing .select_address label { float:none; }
|
|
6
|
+
div#checkout #checkout_form_address #shipping .select_address label { float:none; }
|
|
7
|
+
</style>
|
|
8
|
+
|
|
9
|
+
<% ['billing', 'shipping'].each do |address_type| %>
|
|
10
|
+
<% address_name = "#{address_type[0...4]}_address" %>
|
|
11
|
+
<fieldset id="<%= address_type %>">
|
|
12
|
+
<legend><%= t(address_type + "_address")%></legend>
|
|
13
|
+
<% if address_type == 'shipping' %>
|
|
14
|
+
<p class="field checkbox">
|
|
15
|
+
<label for="order_use_billing" id="use_billing">
|
|
16
|
+
<%= check_box_tag 'order[use_billing]', '1', (!(@order.bill_address.empty? && @order.ship_address.empty?) && @order.bill_address.eql?(@order.ship_address)) %> <%= t("use_billing_address") %>
|
|
17
|
+
</label>
|
|
18
|
+
</p>
|
|
19
|
+
<% end %>
|
|
20
|
+
<div class="select_address">
|
|
21
|
+
<p class="field">
|
|
22
|
+
<% if @addresses.present? %>
|
|
23
|
+
<% @addresses.each do |address| %>
|
|
24
|
+
<span id="<%= [address_type, dom_id(address)].join('_') %>">
|
|
25
|
+
<label><%= form.radio_button "#{address_name}_id", address.id %> <%= address %></label> <%= link_to t(:remove), address, :method => :delete, :remote => true %><br />
|
|
26
|
+
</span>
|
|
27
|
+
<% end %>
|
|
28
|
+
<label><%= form.radio_button "#{address_name}_id", 0 %> <%= t('other_address') %></label>
|
|
29
|
+
<% end %>
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
<%= form.fields_for address_name do |address_form| %>
|
|
33
|
+
<div class="inner">
|
|
34
|
+
<p class="field"> </p>
|
|
35
|
+
<%= render :partial => 'addresses/form', :locals => {
|
|
36
|
+
:address_name => address_name,
|
|
37
|
+
:address_form => address_form,
|
|
38
|
+
:address => @order.send(address_name)
|
|
39
|
+
} %>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
</fieldset>
|
|
43
|
+
<% end %>
|
|
44
|
+
|
|
45
|
+
<hr class="space" />
|
|
46
|
+
<div class="form-buttons">
|
|
47
|
+
<input type="submit" class="continue button primary" value="<%=t("save_and_continue") %>" />
|
|
48
|
+
</div>
|
|
49
|
+
<% if @addresses.present? %>
|
|
50
|
+
<%= javascript_tag do %>
|
|
51
|
+
$(document).ready(function(){
|
|
52
|
+
$(".inner input").attr('disabled', 'disabled');
|
|
53
|
+
$(".inner select").attr('disabled', 'disabled');
|
|
54
|
+
$(".inner").hide();
|
|
55
|
+
|
|
56
|
+
$("input[name='order[bill_address_id]']:radio").change(function(){
|
|
57
|
+
if ($("input[name='order[bill_address_id]']:checked").val() == '0') {
|
|
58
|
+
$("#billing .inner input").removeAttr('disabled');
|
|
59
|
+
$("#billing .inner select").removeAttr('disabled');
|
|
60
|
+
$("#billing .inner").fadeIn();
|
|
61
|
+
} else {
|
|
62
|
+
$("#billing .inner input").attr('disabled', 'disabled');
|
|
63
|
+
$("#billing .inner select").attr('disabled', 'disabled');
|
|
64
|
+
$("#billing .inner").fadeOut();
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
$("input[name='order[ship_address_id]']:radio").change(function(){
|
|
69
|
+
if ($("input[name='order[ship_address_id]']:checked").val() == '0') {
|
|
70
|
+
$("#shipping .inner input").removeAttr('disabled');
|
|
71
|
+
$("#shipping .inner select").removeAttr('disabled');
|
|
72
|
+
$("#shipping .inner").fadeIn();
|
|
73
|
+
} else {
|
|
74
|
+
$("#shipping .inner input").attr('disabled', 'disabled');
|
|
75
|
+
$("#shipping .inner select").attr('disabled', 'disabled');
|
|
76
|
+
$("#shipping .inner").fadeOut();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
});
|
|
81
|
+
<% end %>
|
|
82
|
+
<% end %>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ADDRESS_FIELDS = ["firstname", "lastname", "address1", "address2", "city", "state", "zipcode", "country", "phone"]
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class AddUserIdAndDeletedAtToAddresses < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
change_table :addresses do |t|
|
|
4
|
+
t.integer :user_id
|
|
5
|
+
t.datetime :deleted_at
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.down
|
|
10
|
+
change_table :addresses do |t|
|
|
11
|
+
t.remove :deleted_at
|
|
12
|
+
t.remove :user_id
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
Feature: Address Book
|
|
2
|
+
|
|
3
|
+
@selenium @wip @stop
|
|
4
|
+
Scenario: User fill new address
|
|
5
|
+
Given an order from registered user "email@person.com/password", who has 0 addresses, at address step
|
|
6
|
+
When I fill billing address with correct data
|
|
7
|
+
And check "order_use_billing"
|
|
8
|
+
And press "Save and Continue"
|
|
9
|
+
Then user "email@person.com" should have 1 address
|
|
10
|
+
When I choose "UPS Ground" as shipping method and "Check" as payment method
|
|
11
|
+
Then I should see "Your order has been processed successfully"
|
|
12
|
+
|
|
13
|
+
@selenium @wip @stop
|
|
14
|
+
Scenario: User select address
|
|
15
|
+
Given an order from registered user "email@person.com/password", who has 1 addresses, at address step
|
|
16
|
+
When I choose "order_bill_address_id_1"
|
|
17
|
+
And I choose "order_ship_address_id_1"
|
|
18
|
+
And press "Save and Continue"
|
|
19
|
+
Then user "email@person.com" should have 1 address
|
|
20
|
+
When I choose "UPS Ground" as shipping method and "Check" as payment method
|
|
21
|
+
Then I should see "Your order has been processed successfully"
|
|
22
|
+
|
|
23
|
+
@selenium @wip @stop
|
|
24
|
+
Scenario: User select address
|
|
25
|
+
Given an order from registered user "email@person.com/password", who has 1 addresses, at address step
|
|
26
|
+
When I choose "order_bill_address_id_1"
|
|
27
|
+
And check "order_use_billing"
|
|
28
|
+
And press "Save and Continue"
|
|
29
|
+
Then user "email@person.com" should have 1 address
|
|
30
|
+
When I choose "UPS Ground" as shipping method and "Check" as payment method
|
|
31
|
+
Then I should see "Your order has been processed successfully"
|
|
32
|
+
|
|
33
|
+
@selenium @wip @stop
|
|
34
|
+
Scenario: User select address
|
|
35
|
+
Given an order from registered user "email@person.com/password", who has 1 addresses, at address step
|
|
36
|
+
When I choose "order_bill_address_id_0"
|
|
37
|
+
And I fill billing address with correct data
|
|
38
|
+
And I choose "order_ship_address_id_0"
|
|
39
|
+
And I fill shipping address with correct data
|
|
40
|
+
And press "Save and Continue"
|
|
41
|
+
Then user "email@person.com" should have 2 address
|
|
42
|
+
When I choose "UPS Ground" as shipping method and "Check" as payment method
|
|
43
|
+
Then I should see "Your order has been processed successfully"
|
|
44
|
+
|
|
45
|
+
@selenium @wip @stop
|
|
46
|
+
Scenario: User select address
|
|
47
|
+
Given an order from registered user "email@person.com/password", who has 1 addresses, at address step
|
|
48
|
+
When I choose "order_bill_address_id_0"
|
|
49
|
+
And I fill billing address with correct data
|
|
50
|
+
And I check "order_use_billing"
|
|
51
|
+
And press "Save and Continue"
|
|
52
|
+
Then user "email@person.com" should have 2 address
|
|
53
|
+
When I choose "UPS Ground" as shipping method and "Check" as payment method
|
|
54
|
+
Then I should see "Your order has been processed successfully"
|
|
55
|
+
|
|
56
|
+
@selenium @wip @stop
|
|
57
|
+
Scenario: User can edit address, if it was not used by another order
|
|
58
|
+
Given an order from registered user "email@person.com/password", who has 1 addresses, at address step
|
|
59
|
+
When I choose "order_bill_address_id_1"
|
|
60
|
+
And I choose "order_ship_address_id_1"
|
|
61
|
+
And press "Save and Continue"
|
|
62
|
+
Then user "email@person.com" should have 1 address
|
|
63
|
+
When I follow "Address"
|
|
64
|
+
Then I should see "10 Lovely Street Northwest"
|
|
65
|
+
When I choose "order_bill_address_id_0"
|
|
66
|
+
And fill in "order_bill_address_attributes_zipcode" with "125000"
|
|
67
|
+
And press "Save and Continue"
|
|
68
|
+
Then user "email@person.com" should have 2 address
|
|
69
|
+
When I choose "UPS Ground" as shipping method and "Check" as payment method
|
|
70
|
+
Then I should see "Your order has been processed successfully"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Given /^an order from registered user "([^"]*)", who has (\d+) addresses, at address step$/ do |user_data, addresses_count|
|
|
2
|
+
Given "a shipping method exists"
|
|
3
|
+
Given "a payment method exists"
|
|
4
|
+
Given "I am signed up as \"#{user_data}\""
|
|
5
|
+
Given "user \"#{user_data}\" has #{addresses_count} addresses"
|
|
6
|
+
Given "I add a product with name: \"RoR Mug\" to cart"
|
|
7
|
+
Given "I follow \"Checkout\""
|
|
8
|
+
Given "I sign in as \"email@person.com/password\""
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
Then /^user "([^"]*)" should have (\d+) address$/ do |email, count|
|
|
12
|
+
User.find_by_email(email).addresses.count.should == count.to_i
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Given /^user "([^"]*)" has (\d+) addresses$/ do |user_data, count|
|
|
16
|
+
user = User.find_by_email(user_data.split('/')[0])
|
|
17
|
+
state = State.first
|
|
18
|
+
count.to_i.times do
|
|
19
|
+
Factory(:address, :state => state, :user => user)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
FEATURES_PATH = File.expand_path('../..', __FILE__)
|
|
2
|
+
|
|
3
|
+
# load shared env with features
|
|
4
|
+
require File.expand_path('../../../../spree/features/support/env', __FILE__)
|
|
5
|
+
|
|
6
|
+
# load the rest of files for support and step definitions
|
|
7
|
+
directories = [ File.expand_path('../../../../spree/features/support', __FILE__),
|
|
8
|
+
File.expand_path('../../../../spree/features/step_definitions', __FILE__),
|
|
9
|
+
File.expand_path('../../../spec/factories', __FILE__) ]
|
|
10
|
+
|
|
11
|
+
files = directories.map do |dir|
|
|
12
|
+
Dir["#{dir}/**/*.rb"]
|
|
13
|
+
end.flatten.uniq
|
|
14
|
+
|
|
15
|
+
files.each do |path|
|
|
16
|
+
if path !~ /env.rb$/
|
|
17
|
+
fp = File.expand_path(path)
|
|
18
|
+
#puts fp
|
|
19
|
+
load(fp)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module NavigationHelpers
|
|
2
|
+
# Maps a name to a path. Used by the
|
|
3
|
+
#
|
|
4
|
+
# When /^I go to (.+)$/ do |page_name|
|
|
5
|
+
#
|
|
6
|
+
# step definition in web_steps.rb
|
|
7
|
+
#
|
|
8
|
+
def path_to(page_name)
|
|
9
|
+
case page_name
|
|
10
|
+
|
|
11
|
+
when "admin promotions page"
|
|
12
|
+
admin_promotions_path
|
|
13
|
+
when /the home\s?page/
|
|
14
|
+
'/'
|
|
15
|
+
when /the sign in page/
|
|
16
|
+
new_user_session_path
|
|
17
|
+
when /the sign up page/
|
|
18
|
+
new_user_registration_path
|
|
19
|
+
|
|
20
|
+
# Add more mappings here.
|
|
21
|
+
# Here is an example that pulls values out of the Regexp:
|
|
22
|
+
#
|
|
23
|
+
# when /^(.*)'s profile page$/i
|
|
24
|
+
# user_profile_path(User.find_by_login($1))
|
|
25
|
+
|
|
26
|
+
else
|
|
27
|
+
begin
|
|
28
|
+
page_name =~ /the (.*) page/
|
|
29
|
+
path_components = $1.split(/\s+/)
|
|
30
|
+
self.send(path_components.push('path').join('_').to_sym)
|
|
31
|
+
rescue Object => e
|
|
32
|
+
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
|
33
|
+
"Now, go and add a mapping in #{__FILE__}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
World(NavigationHelpers)
|
|
40
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'spree_core'
|
|
2
|
+
require 'spree_address_book_hooks'
|
|
3
|
+
|
|
4
|
+
module SpreeAddressBook
|
|
5
|
+
class Engine < Rails::Engine
|
|
6
|
+
|
|
7
|
+
config.autoload_paths += %W(#{config.root}/lib)
|
|
8
|
+
|
|
9
|
+
def self.activate
|
|
10
|
+
Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
|
|
11
|
+
Rails.env.production? ? require(c) : load(c)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
config.to_prepare &method(:activate).to_proc
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
namespace :spree_address_book do
|
|
2
|
+
desc "Copies all migrations and assets (NOTE: This will be obsolete with Rails 3.1)"
|
|
3
|
+
task :install do
|
|
4
|
+
Rake::Task['spree_address_book:install:migrations'].invoke
|
|
5
|
+
Rake::Task['spree_address_book:install:assets'].invoke
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
namespace :install do
|
|
9
|
+
desc "Copies all migrations (NOTE: This will be obsolete with Rails 3.1)"
|
|
10
|
+
task :migrations do
|
|
11
|
+
source = File.join(File.dirname(__FILE__), '..', '..', 'db')
|
|
12
|
+
destination = File.join(Rails.root, 'db')
|
|
13
|
+
Spree::FileUtilz.mirror_files(source, destination)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc "Copies all assets (NOTE: This will be obsolete with Rails 3.1)"
|
|
17
|
+
task :assets do
|
|
18
|
+
source = File.join(File.dirname(__FILE__), '..', '..', 'public')
|
|
19
|
+
destination = File.join(Rails.root, 'public')
|
|
20
|
+
puts "INFO: Mirroring assets from #{source} to #{destination}"
|
|
21
|
+
Spree::FileUtilz.mirror_files(source, destination)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# add custom rake tasks here
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
(function($){
|
|
2
|
+
$(document).ready(function(){
|
|
3
|
+
|
|
4
|
+
$('#checkout_form_address').validate();
|
|
5
|
+
|
|
6
|
+
var get_states = function(region){
|
|
7
|
+
var country = $('p#' + region + 'country' + ' span#' + region + 'country :only-child').val();
|
|
8
|
+
return state_mapper[country];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
var update_state = function(region) {
|
|
12
|
+
var states = get_states(region);
|
|
13
|
+
|
|
14
|
+
var state_select = $('span#' + region + 'state select');
|
|
15
|
+
var state_input = $('span#' + region + 'state input');
|
|
16
|
+
|
|
17
|
+
if(states) {
|
|
18
|
+
var selected = state_select.val();
|
|
19
|
+
state_select.html('');
|
|
20
|
+
var states_with_blank = [["",""]].concat(states);
|
|
21
|
+
$.each(states_with_blank, function(pos,id_nm) {
|
|
22
|
+
var opt = $(document.createElement('option'))
|
|
23
|
+
.attr('value', id_nm[0])
|
|
24
|
+
.html(id_nm[1]);
|
|
25
|
+
if(selected==id_nm[0]){
|
|
26
|
+
opt.attr('selected', 'selected');
|
|
27
|
+
}
|
|
28
|
+
state_select.append(opt);
|
|
29
|
+
});
|
|
30
|
+
state_select.removeAttr('disabled').show();
|
|
31
|
+
state_input.hide().attr('disabled', 'disabled');
|
|
32
|
+
|
|
33
|
+
} else {
|
|
34
|
+
state_input.removeAttr('disabled').show();
|
|
35
|
+
state_select.hide().attr('disabled', 'disabled');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Show fields for the selected payment method
|
|
41
|
+
$("input[type='radio'][name='order[payments_attributes][][payment_method_id]']").click(function(){
|
|
42
|
+
$('#payment-methods li').hide();
|
|
43
|
+
if(this.checked){ $('#payment_method_'+this.value).show(); }
|
|
44
|
+
}).triggerHandler('click');
|
|
45
|
+
|
|
46
|
+
$('p#bcountry span#bcountry select').change(function() { update_state('b'); });
|
|
47
|
+
$('p#scountry span#scountry select').change(function() { update_state('s'); });
|
|
48
|
+
update_state('b');
|
|
49
|
+
update_state('s');
|
|
50
|
+
|
|
51
|
+
$('input#order_use_billing').click(function() {
|
|
52
|
+
if($(this).is(':checked')) {
|
|
53
|
+
$("#shipping .inner input").attr('disabled', 'disabled');
|
|
54
|
+
$("#shipping .inner select").attr('disabled', 'disabled');
|
|
55
|
+
$("#shipping .inner").fadeOut();
|
|
56
|
+
$("#shipping .select_address").fadeOut();
|
|
57
|
+
} else {
|
|
58
|
+
if ($("input[name='order[ship_address_id]']:checked").val() == '0') {
|
|
59
|
+
$("#shipping .inner input").removeAttr('disabled');
|
|
60
|
+
$("#shipping .inner select").removeAttr('disabled');
|
|
61
|
+
$("#shipping .inner").fadeIn();
|
|
62
|
+
}
|
|
63
|
+
$("#shipping .select_address").fadeIn();
|
|
64
|
+
}
|
|
65
|
+
}).triggerHandler('click');
|
|
66
|
+
|
|
67
|
+
$('form.edit_checkout').submit(function() {
|
|
68
|
+
$(this).find(':submit, :image').attr('disabled', true).removeClass('primary').addClass('disabled');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
});
|
|
73
|
+
})(jQuery);
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
|
|
2
|
+
# from the project root directory.
|
|
3
|
+
ENV["RAILS_ENV"] ||= 'test'
|
|
4
|
+
require File.expand_path("../test_app/config/environment", __FILE__)
|
|
5
|
+
require 'rspec/rails'
|
|
6
|
+
|
|
7
|
+
# Requires supporting files with custom matchers and macros, etc,
|
|
8
|
+
# in ./support/ and its subdirectories.
|
|
9
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
|
10
|
+
|
|
11
|
+
require 'spree_core/testing_support/factories'
|
|
12
|
+
|
|
13
|
+
RSpec.configure do |config|
|
|
14
|
+
# == Mock Framework
|
|
15
|
+
#
|
|
16
|
+
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
|
17
|
+
#
|
|
18
|
+
# config.mock_with :mocha
|
|
19
|
+
# config.mock_with :flexmock
|
|
20
|
+
# config.mock_with :rr
|
|
21
|
+
config.mock_with :rspec
|
|
22
|
+
|
|
23
|
+
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
|
24
|
+
|
|
25
|
+
#config.include Devise::TestHelpers, :type => :controller
|
|
26
|
+
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
|
27
|
+
# examples within a transaction, comment the following line or assign false
|
|
28
|
+
# instead of true.
|
|
29
|
+
config.use_transactional_fixtures = true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@configuration ||= AppConfiguration.find_or_create_by_name("Default configuration")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Gem::Specification.new do |s|
|
|
2
|
+
s.platform = Gem::Platform::RUBY
|
|
3
|
+
s.name = 'spree_address_book'
|
|
4
|
+
s.version = '0.40.0.rc'
|
|
5
|
+
s.summary = 'Adds address book for users to Spree'
|
|
6
|
+
#s.description = 'Add (optional) gem description here'
|
|
7
|
+
s.required_ruby_version = '>= 1.8.7'
|
|
8
|
+
|
|
9
|
+
s.author = 'Roman Smirnov'
|
|
10
|
+
s.email = 'roman@railsdog.com'
|
|
11
|
+
s.homepage = 'http://github.com/romul/spree_address_book'
|
|
12
|
+
# s.rubyforge_project = 'actionmailer'
|
|
13
|
+
|
|
14
|
+
s.files = `git ls-files`.split("\n")
|
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
16
|
+
s.require_path = 'lib'
|
|
17
|
+
s.requirements << 'none'
|
|
18
|
+
|
|
19
|
+
s.has_rdoc = true
|
|
20
|
+
|
|
21
|
+
s.add_dependency('spree_core', '>= 0.40.0')
|
|
22
|
+
s.add_dependency('spree_auth', '>= 0.40.0')
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: spree_address_book
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: true
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 40
|
|
8
|
+
- 0
|
|
9
|
+
- rc
|
|
10
|
+
version: 0.40.0.rc
|
|
11
|
+
platform: ruby
|
|
12
|
+
authors:
|
|
13
|
+
- Roman Smirnov
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2011-03-08 00:00:00 +03:00
|
|
19
|
+
default_executable:
|
|
20
|
+
dependencies:
|
|
21
|
+
- !ruby/object:Gem::Dependency
|
|
22
|
+
name: spree_core
|
|
23
|
+
prerelease: false
|
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
segments:
|
|
29
|
+
- 0
|
|
30
|
+
- 40
|
|
31
|
+
- 0
|
|
32
|
+
version: 0.40.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
version_requirements: *id001
|
|
35
|
+
- !ruby/object:Gem::Dependency
|
|
36
|
+
name: spree_auth
|
|
37
|
+
prerelease: false
|
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
segments:
|
|
43
|
+
- 0
|
|
44
|
+
- 40
|
|
45
|
+
- 0
|
|
46
|
+
version: 0.40.0
|
|
47
|
+
type: :runtime
|
|
48
|
+
version_requirements: *id002
|
|
49
|
+
description:
|
|
50
|
+
email: roman@railsdog.com
|
|
51
|
+
executables: []
|
|
52
|
+
|
|
53
|
+
extensions: []
|
|
54
|
+
|
|
55
|
+
extra_rdoc_files: []
|
|
56
|
+
|
|
57
|
+
files:
|
|
58
|
+
- .gitignore
|
|
59
|
+
- LICENSE
|
|
60
|
+
- README.md
|
|
61
|
+
- Rakefile
|
|
62
|
+
- app/controllers/addresses_controller.rb
|
|
63
|
+
- app/controllers/checkout_controller_decorator.rb
|
|
64
|
+
- app/models/address_decorator.rb
|
|
65
|
+
- app/models/order_decorator.rb
|
|
66
|
+
- app/models/user_decorator.rb
|
|
67
|
+
- app/views/addresses/_form.html.erb
|
|
68
|
+
- app/views/addresses/destroy.js.erb
|
|
69
|
+
- app/views/checkout/_address.html.erb
|
|
70
|
+
- config/initializers/address_fields.rb
|
|
71
|
+
- config/locales/en.yml
|
|
72
|
+
- config/locales/ru.yml
|
|
73
|
+
- config/routes.rb
|
|
74
|
+
- db/migrate/20110302102208_add_user_id_and_deleted_at_to_addresses.rb
|
|
75
|
+
- features/address_book.feature
|
|
76
|
+
- features/step_definitions/address_book_steps.rb
|
|
77
|
+
- features/support/env.rb
|
|
78
|
+
- features/support/paths.rb
|
|
79
|
+
- lib/spree_address_book.rb
|
|
80
|
+
- lib/spree_address_book_hooks.rb
|
|
81
|
+
- lib/tasks/install.rake
|
|
82
|
+
- lib/tasks/spree_address_book.rake
|
|
83
|
+
- public/javascripts/checkout.js
|
|
84
|
+
- spec/models/address_spec.rb
|
|
85
|
+
- spec/spec_helper.rb
|
|
86
|
+
- spree_address_book.gemspec
|
|
87
|
+
has_rdoc: true
|
|
88
|
+
homepage: http://github.com/romul/spree_address_book
|
|
89
|
+
licenses: []
|
|
90
|
+
|
|
91
|
+
post_install_message:
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
|
|
94
|
+
require_paths:
|
|
95
|
+
- lib
|
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
segments:
|
|
101
|
+
- 1
|
|
102
|
+
- 8
|
|
103
|
+
- 7
|
|
104
|
+
version: 1.8.7
|
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
segments:
|
|
110
|
+
- 1
|
|
111
|
+
- 3
|
|
112
|
+
- 1
|
|
113
|
+
version: 1.3.1
|
|
114
|
+
requirements:
|
|
115
|
+
- none
|
|
116
|
+
rubyforge_project:
|
|
117
|
+
rubygems_version: 1.3.6
|
|
118
|
+
signing_key:
|
|
119
|
+
specification_version: 3
|
|
120
|
+
summary: Adds address book for users to Spree
|
|
121
|
+
test_files: []
|
|
122
|
+
|