Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor zones #530

Merged
merged 3 commits into from Jan 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions core/app/models/spree/zone.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ class Zone < Spree::Base
after_save :remove_defunct_members
after_save :remove_previous_default

scope :with_member_ids,
->(state_ids, country_ids) do
joins(:zone_members).where(
"(spree_zone_members.zoneable_type = 'Spree::State' AND
spree_zone_members.zoneable_id IN (?))
OR (spree_zone_members.zoneable_type = 'Spree::Country' AND
spree_zone_members.zoneable_id IN (?))",
state_ids,
country_ids
).uniq
end

alias :members :zone_members
accepts_nested_attributes_for :zone_members, allow_destroy: true, reject_if: proc { |a| a['zoneable_id'].blank? }

Expand All @@ -24,12 +36,13 @@ def self.default_tax
where(default_tax: true).first
end

# Returns the matching zone with the highest priority zone type (State, Country, Zone.)
# Returns nil in the case of no matches.
# Returns the most specific matching zone for an address. Specific means:
# A State zone wins over a country zone, and a zone with few members wins
# over one with many members. If there is no match, returns nil.
def self.match(address)
return unless address and matches = self.includes(:zone_members).
return unless address and matches = self.
with_member_ids(address.state_id, address.country_id).
order(:zone_members_count, :created_at, :id).
where("(spree_zone_members.zoneable_type = 'Spree::Country' AND spree_zone_members.zoneable_id = ?) OR (spree_zone_members.zoneable_type = 'Spree::State' AND spree_zone_members.zoneable_id = ?)", address.country_id, address.state_id).
references(:zones)

['state', 'country'].each do |zone_kind|
Expand All @@ -40,6 +53,20 @@ def self.match(address)
matches.first
end


# Returns all zones that contain any of the zone members of the zone passed
# in. This also includes any country zones that contain the state of the
# current zone, if it's a state zone. If the passed-in zone has members, it
# will also be in the result set.
def self.with_shared_members(zone)
states_and_state_country_ids = zone.states.pluck(:id, :country_id).to_a
state_ids = states_and_state_country_ids.map(&:first)
state_country_ids = states_and_state_country_ids.map(&:second)
country_ids = zone.countries.pluck(:id).to_a

with_member_ids(state_ids, country_ids + state_country_ids).uniq
end

def kind
if members.any? && !members.any? { |member| member.try(:zoneable_type).nil? }
members.last.zoneable_type.demodulize.underscore
Expand Down
77 changes: 77 additions & 0 deletions core/spec/models/spree/zone_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -336,4 +336,81 @@
end
end
end

context ".with_shared_members" do
let!(:country) { create(:country) }
let!(:country2) { create(:country, name: 'OtherCountry') }
let!(:country3) { create(:country, name: 'TaxCountry') }

subject(:zones_with_shared_members) { Spree::Zone.with_shared_members(zone) }

context 'when passing a zone with no members' do
let!(:zone) { create :zone }

it 'will return an empty set' do
expect(subject).to eq([])
end
end

context "finding potential matches for a country zone" do
let!(:zone) do
create(:zone).tap do |z|
z.members.create(zoneable: country)
z.members.create(zoneable: country2)
z.save!
end
end

let!(:zone2) do
create(:zone).tap { |z| z.members.create(zoneable: country) && z.save! }
end

let!(:zone3) do
create(:zone).tap { |z| z.members.create(zoneable: country3) && z.save! }
end

it "will find all zones with countries covered by the passed in zone" do
expect(zones_with_shared_members).to include(zone, zone2)
end

it "will not return zones with countries not covered in the passed in zone" do
expect(zones_with_shared_members).not_to include(zone3)
end

it "only returns each zone once" do
expect(zones_with_shared_members.select { |z| z == zone }.size).to be 1
end
end

context "finding potential matches for a state zone" do
let!(:state) { create(:state, country: country) }
let!(:state2) { create(:state, country: country2, name: 'OtherState') }
let!(:state3) { create(:state, country: country2, name: 'State') }
let!(:zone) do
create(:zone).tap do |z|
z.members.create(zoneable: state)
z.members.create(zoneable: state2)
z.save!
end
end
let!(:zone2) do
create(:zone).tap { |z| z.members.create(zoneable: state) && z.save! }
end
let!(:zone3) do
create(:zone).tap { |z| z.members.create(zoneable: state2) && z.save! }
end

it "will find all zones which share states covered by passed in zone" do
expect(zones_with_shared_members).to include(zone, zone2)
end

it "will find zones that share countries with any states of the passed in zone" do
expect(zones_with_shared_members).to include(zone3)
end

it "only returns each zone once" do
expect(zones_with_shared_members.select { |z| z == zone }.size).to be 1
end
end
end
end