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

Transform zip zone in entity #1

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ $(function(){
render: function() {
var kind = this.$('[name="zone[kind]"]:checked').val() || 'state';

$('#zip_members').toggleClass('hidden', kind !== 'zip');
$('#zip_members :input').prop('disabled', kind !== 'zip');
$('#zip_members').toggleClass('hidden', kind !== 'zip_code');
$('#zip_members :input').prop('disabled', kind !== 'zip_code');
}
})

Expand All @@ -19,12 +19,12 @@ $(function(){
view.render()
} else {
$('#country_based, #state_based').click(function(){
$('#zip_members #zone_zipcodes').prop('disabled', true)
$('#zip_members #zone_zip_code_ids').prop('disabled', true)
$('#zip_members').hide()
})

$('#zip_based').click(function(){
$('#zip_members #zone_zipcodes').prop('disabled', false)
$('#zip_code_based').click(function(){
$('#zip_members #zone_zip_code_ids').prop('disabled', false)
$('#zip_members').show()

$('#country_members :input, #state_members :input').each(function(){
Expand All @@ -33,8 +33,8 @@ $(function(){
$('#country_members, #state_members').hide()
})

if($('#zone_zipcodes').val() != ''){
$('#zip_based').click()
if($('#zip_code_based[data-is-zip-based=true]').length > 0){
$('#zip_code_based').click()
} else {
$('#zip_members').hide()
}
Expand Down
9 changes: 9 additions & 0 deletions app/models/solidus_zip_zones/state_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module SolidusZipZones
module StateDecorator
def self.prepended(base)
base.has_many :zip_codes
end

Spree::State.prepend self
end
end
59 changes: 50 additions & 9 deletions app/models/solidus_zip_zones/zone_decorator.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
module SolidusZipZones
module ZoneDecorator
def self.prepended(base)
base.scope :with_member_ids, ->(state_ids, country_ids, zipcode) do
if !state_ids.present? && !country_ids.present? && !zipcode.present?
base.with_options through: :zone_members, source: :zoneable do
has_many :countries, source_type: "Spree::Country"
has_many :states, source_type: "Spree::State"
has_many :zip_codes, source_type: "Spree::ZipCode"
end

base.scope :with_member_ids, ->(state_ids, country_ids, zipcode_ids) do
if !state_ids.present? && !country_ids.present? && !zipcode_ids.present?
none
else
spree_zone_members_table = Spree::ZoneMember.arel_table
Expand All @@ -12,25 +18,60 @@ def self.prepended(base)
matching_country =
spree_zone_members_table[:zoneable_type].eq("Spree::Country").
and(spree_zone_members_table[:zoneable_id].in(country_ids))
spree_zones_table = Spree::Zone.arel_table
matching_zipcode = spree_zones_table[:zipcodes].matches("%#{zipcode}%")
left_joins(:zone_members).where(matching_zipcode.or(matching_state.or(matching_country))).distinct
matching_zip_code =
spree_zone_members_table[:zoneable_type].eq("Spree::ZipCode").
and(spree_zone_members_table[:zoneable_id].in(zipcode_ids))

matcher = matching_zip_code.or(matching_state.or(matching_country))
joins(:zone_members).where(matcher).distinct
end
end

base.scope :for_address, ->(address) do
if address
with_member_ids(address.state_id, address.country_id, address.zipcode)
zipcode = Spree::ZipCode.find_by name: address.zipcode
with_member_ids(address.state_id, address.country_id, zipcode.try(:id))
else
none
end
end
if SolidusSupport.solidus_gem_version < Gem::Version.new('2.4')
class << base
prepend ClassMethodMatch
end
end
end

def kind
return 'zip' if zipcodes.present?
module ClassMethodMatch
def with_shared_members(zone)
return none unless 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

super
with_member_ids(state_ids, country_ids + state_country_ids, nil).distinct
end

def match(address)
Spree::Deprecation.warn("Spree::Zone.match is deprecated. Please use Spree::Zone.for_address instead.", caller)

# This code can't be replaced with for_address because for_address
# does not sort the member in order zip_code, state, country
return unless address && (matches =
for_address(address).
order(:zone_members_count, :created_at, :id).
references(:zones))

['zip_code', 'state', 'country'].each do |zone_kind|
if match = matches.detect { |zone| zone_kind == zone.kind }
return match
end
end

matches.first
end
end

Spree::Zone.prepend self
Expand Down
9 changes: 9 additions & 0 deletions app/models/spree/zip_code.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Spree
class ZipCode < Spree::Base
belongs_to :state, class_name: 'Spree::State'
has_many :addresses, dependent: :nullify

validates :state, presence: true
validates :name, presence: true, format: { with: /\A[0-9]{5}\z/ }
end
end
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- insert_bottom '[data-hook=type] ul' -->

<li>
<%= zone_form.radio_button('kind', 'zip', { id: 'zip_based' }) %>
<%= zone_form.radio_button('kind', 'zip_code', { id: 'zip_code_based', data: { is_zip_based: zone_form.object.kind == 'zip_code' } }) %>
<%= label_tag :zip_based, t('spree.zip_based') %>
</li>
9 changes: 4 additions & 5 deletions app/views/spree/admin/zones/_zip_members.html.erb
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<div id="zip_members" data-hook="member">
<fieldset class="no-border-bottom">
<legend align="center"><%= Spree.t(:zipcode) %></legend>
<legend align="center"><%= plural_resource_name(Spree::ZipCode) %></legend>

<%= zone_form.field_container :zipcodes do %>
<%= zone_form.label :zipcodes,Spree.t(:zipcode) %><br>
<%= zone_form.hidden_field :zipcodes, value: '', id: 'hidden_zone_zipcodes' %>
<%= zone_form.text_field :zipcodes, class: "select2-taggable fullwidth" %>
<%= zone_form.field_container :zip_codes do %>
<%= zone_form.label :zip_code_ids, plural_resource_name(Spree::ZipCode) %><br>
<%= zone_form.collection_select :zip_code_ids, @zip_codes, :id, :name, {}, { multiple: true, class: "select2 fullwidth" } %>
<% end %>
</fieldset>
</div>
5 changes: 5 additions & 0 deletions db/migrate/2_remove_zipcodes_to_spree_zone.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class RemoveZipcodesToSpreeZone < SolidusSupport::Migration[4.2]
def change
remove_column :spree_zones, :zipcodes, :text
end
end
10 changes: 10 additions & 0 deletions db/migrate/3_create_spree_zip_codes.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class CreateSpreeZipCodes < SolidusSupport::Migration[4.2]
def change
create_table :spree_zip_codes do |t|
t.integer :state_id
t.string :name

t.timestamps
end
end
end
7 changes: 2 additions & 5 deletions lib/solidus_zip_zones/factories.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
FactoryBot.define do
# Define your Spree extensions Factories within this file to enable applications, and other extensions to use and override them.
#
# Example adding this to your spec_helper will load these Factories for use:
# require 'solidus_zip_zones/factories'
Dir["#{File.dirname(__FILE__)}/factories/**"].each do |f|
require File.expand_path(f)
end
13 changes: 13 additions & 0 deletions lib/solidus_zip_zones/factories/zip_code_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
require 'spree/testing_support/factories/state_factory'

FactoryBot.define do
factory :zip_code, class: 'Spree::ZipCode' do
transient do
state_code 'AL'
end

state do
Spree::State.find_by(abbr: state_code)
end
end
end
115 changes: 112 additions & 3 deletions spec/models/solidus_zip_zones/zone_decorator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,23 @@
RSpec.describe SolidusZipZones::ZoneDecorator, type: :model do
describe 'for_address' do
let(:new_york_address) { create(:address, state_code: "NY", zipcode: '11203') }
let(:alabama_address) { create(:address) }
let(:canada_address) { create(:address, country_iso_code: "CA") }
let(:alabama_address) { create(:address) }
let(:canada_address) { create(:address, country_iso_code: "CA") }
let(:nyc_zip_codes) do
[
create(:zip_code, state_code: "NY", name: '11203'),
create(:zip_code, state_code: "NY", name: '11204'),
create(:zip_code, state_code: "NY", name: '11205'),
create(:zip_code, state_code: "NY", name: '11206')
]
end

let!(:new_york_zone) { create(:zone, states: [new_york_address.state]) }
let!(:alabama_zone) { create(:zone, states: [alabama_address.state]) }
let!(:united_states_zone) { create(:zone, countries: [new_york_address.country]) }
let!(:canada_zone) { create(:zone, countries: [canada_address.country]) }
let!(:north_america_zone) { create(:zone, countries: [canada_address.country, new_york_address.country]) }
let!(:nyc_zip_zone) { create(:zone, zipcodes: "11203,11204,11205,11206") }
let!(:nyc_zip_zone) { create(:zone, zip_codes: nyc_zip_codes) }
subject { Spree::Zone.for_address(address) }

context 'when there is no address' do
Expand Down Expand Up @@ -58,4 +66,105 @@
end
end
end

if SolidusSupport.solidus_gem_version < Gem::Version.new('2.4')
context "#match" do
let(:country_zone) { create(:zone, name: 'CountryZone') }
let(:country) do
country = create(:country)
# Create at least one state for this country
create(:state, country: country)
country
end

before { country_zone.members.create(zoneable: country) }

context "when there is only one qualifying zone" do
let(:address) { create(:address, country: country, state: country.states.first) }

it "should return the qualifying zone" do
Spree::Deprecation.silence do
expect(Spree::Zone.match(address)).to eq(country_zone)
end
end
end

context "when there are two qualified zones with same member type" do
let(:address) { create(:address, country: country, state: country.states.first) }
let(:second_zone) { create(:zone, name: 'SecondZone') }

before { second_zone.members.create(zoneable: country) }

context "when both zones have the same number of members" do
it "should return the zone that was created first" do
Spree::Deprecation.silence do
expect(Spree::Zone.match(address)).to eq(country_zone)
end
end
end

context "when one of the zones has fewer members" do
let(:country2) { create(:country) }

before { country_zone.members.create(zoneable: country2) }

it "should return the zone with fewer members" do
Spree::Deprecation.silence do
expect(Spree::Zone.match(address)).to eq(second_zone)
end
end
end
end

context "when there are two qualified zones with different member types" do
let(:state_zone) { create(:zone, name: 'StateZone') }
let(:address) { create(:address, country: country, state: country.states.first) }

before { state_zone.members.create(zoneable: country.states.first) }

it "should return the zone with the more specific member type" do
Spree::Deprecation.silence do
expect(Spree::Zone.match(address)).to eq(state_zone)
end
end
end

context "when there are three qualified zones with different member types" do
let!(:zip_zone) { create(:zone, zip_codes: zip_codes) }
let(:state_zone) { create(:zone, name: 'StateZone') }

let(:zip_codes) do
[
create(:zip_code, state_code: "AL", name: '12345'),
create(:zip_code, state_code: "AL", name: '12346')
]
end

let(:address) do
create(:address,
country: country,
state: country.states.first,
zipcode: '12345')
end

before do
state_zone.members.create(zoneable: country.states.first)
end

it "should return the zone with the more specific member type" do
Spree::Deprecation.silence do
expect(Spree::Zone.match(address)).to eq(zip_zone)
end
end
end

context "when there are no qualifying zones" do
it "should return nil" do
Spree::Deprecation.silence do
expect(Spree::Zone.match(Spree::Address.new)).to be_nil
end
end
end
end
end
end
5 changes: 5 additions & 0 deletions spec/models/spree/zip_code_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require 'spec_helper'

RSpec.describe Spree::ZipCode, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end