-
-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #624 from rubyforgood/migrate-multi-listing-submis…
…sions Migrate multi listing submissions
- Loading branch information
Showing
3 changed files
with
213 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
require_relative './support/listing_converter' | ||
|
||
namespace :one_time do | ||
desc "split listings with multiple tags into one listing for each tag" | ||
task split_listings: :environment do | ||
converter = ListingConverter.new | ||
|
||
relevant_listings = Listing.joins(:taggings).group('listings.id').having('count(taggable_id) > 1') | ||
puts "Processing #{relevant_listings.count.keys.size} listings" | ||
|
||
relevant_listings.find_each do |listing| | ||
puts "#, #{listing.id}, #{listing.tag_list}" | ||
new_listings = converter.convert listing | ||
|
||
puts "-, #{listing.id}, #{listing.reload.tag_list}" | ||
new_listings.each{ |new_listing| puts "+, #{new_listing.id}, #{new_listing.tag_list}" } | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
class ListingConverter | ||
def convert listing | ||
Listing.transaction do | ||
first_tag_set, *remaining_tag_sets = splitter.split listing.tag_list | ||
|
||
listing.update! tag_list: [first_tag_set] | ||
|
||
remaining_tag_sets.map do |tag_set| | ||
listing.dup.tap do |new_listing| | ||
new_listing.update!( | ||
tag_list: tag_set, | ||
created_at: listing.created_at, | ||
) | ||
end | ||
end | ||
end | ||
end | ||
|
||
def splitter | ||
@splitter ||= TagSplitter.new | ||
end | ||
|
||
class TagSplitter | ||
attr_reader :lineages | ||
|
||
def initialize lineages = nil | ||
@lineages = lineages || gather_lineages | ||
end | ||
|
||
def gather_lineages | ||
Category.all.map{ |category| [category.name, category.lineage] }.to_h | ||
end | ||
|
||
def split tag_list | ||
matching_lineages = lineages | ||
.values_at(*tag_list) | ||
.sort_by{ |lineage| lineage.size } | ||
.reverse | ||
|
||
matching_lineages.reduce([]) do |minimal_set, lineage| | ||
with_lineage = minimal_set + [lineage] | ||
with_lineage.flatten.uniq.size > minimal_set.flatten.uniq.size ? with_lineage : minimal_set | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
require 'rails_helper' | ||
require 'tasks/support/listing_converter' | ||
|
||
RSpec.describe ListingConverter do | ||
let(:converter) { ListingConverter.new } | ||
let(:convert) { converter.convert listing } | ||
|
||
before { Category.destroy_all } # from seeds | ||
|
||
context 'without sub-categories' do | ||
let(:tags) { %w[cash errands housing] } | ||
let!(:categories) { tags.map{ |name| create :category, name: name }} | ||
let(:listing) { create :listing, tag_list: tags } | ||
|
||
it 'removes all but one tag from the original listing' do | ||
convert | ||
expect(listing.reload.tag_list.size).to be 1 | ||
expect(tags).to include listing.tag_list.first | ||
end | ||
|
||
it 'creates a listing for each extraneous tag' do | ||
convert | ||
expect(Listing.count).to be 3 | ||
end | ||
|
||
it 'sets the tag on each new listing' do | ||
new_listings = convert | ||
expect(new_listings.map(&:tag_list).flatten).to match_array(tags - listing.reload.tag_list) | ||
end | ||
|
||
it 'copies attributes from original listing except id, tag_list, and updated_at' do | ||
original = listing.attributes.to_a | ||
new_listing = convert.last | ||
copied = new_listing.attributes.to_a | ||
expect((copied - original).to_h.keys).to match_array %w[id tag_list updated_at] | ||
end | ||
|
||
it 'keeps the association with a submission' do | ||
listing.update!(submission: create(:submission)) | ||
new_listing = convert.last | ||
expect(new_listing.submission).to be_present | ||
expect(new_listing.submission).to eq listing.submission | ||
end | ||
|
||
describe 'with matches' do | ||
let!(:provider_match) { create(:match, :with_ask_and_offer, provider: listing) } | ||
let!(:receiver_match) { create(:match, :with_ask_and_offer, receiver: listing) } | ||
|
||
it 'keeps matches only on the original listing' do | ||
new_listing = convert.last | ||
expect(listing.matches_as_provider).to_not be_empty | ||
expect(listing.matches_as_receiver).to_not be_empty | ||
expect(new_listing.matches_as_provider).to be_empty | ||
expect(new_listing.matches_as_receiver).to be_empty | ||
end | ||
end | ||
end | ||
|
||
context 'with only a parent- and sub-category' do | ||
let!(:parent) { create :category, name: 'food' } | ||
let!(:child) { create :category, name: 'meals', parent: parent } | ||
let!(:listing) { create :listing, tag_list: %w[food meals] } | ||
|
||
it 'does not create any new listings' do | ||
expect{ convert }.to_not change(Listing, :count) | ||
end | ||
|
||
it 'leaves the listing tags unchanged' do | ||
convert | ||
expect(listing.tag_list).to match_array %w[food meals] | ||
end | ||
end | ||
|
||
context 'with a mix of single tags and and nested categories' do | ||
let!(:unrelated) { create :category, name: 'cash' } | ||
let!(:parent) { create :category, name: 'food' } | ||
let!(:child) { create :category, name: 'meals', parent: parent } | ||
let!(:other_child) { create :category, name: 'groceries', parent: parent } | ||
let!(:grand_child) { create :category, name: 'delivery', parent: child } | ||
|
||
let!(:listing) { create :listing, tag_list: %w[food meals cash groceries delivery] } | ||
|
||
it 'leaves the listing with one of the tag sets' do | ||
convert | ||
expect(listing.tag_list).to match_array %w[food meals delivery] | ||
end | ||
|
||
it 'creates listing for the remaining tag sets' do | ||
expect(convert.map(&:tag_list)).to match_array [ | ||
%w[cash], | ||
%w[food groceries], | ||
] | ||
end | ||
end | ||
|
||
describe 'TagSplitter' do | ||
let(:splitter) { ListingConverter::TagSplitter.new(lineages) } | ||
let(:lineages) {{ | ||
'grandchild' => %w[parent child grandchild], | ||
'child' => %w[parent child], | ||
'other_child' => %w[parent other_child], | ||
'parent' => %w[parent], | ||
'orphan' => %w[orphan], | ||
}} | ||
|
||
subject { splitter.split tag_list } | ||
|
||
context 'single tag' do | ||
let(:tag_list) { %w[orphan] } | ||
it { is_expected.to contain_exactly %w[orphan] } | ||
end | ||
|
||
context 'unrelated tags' do | ||
let(:tag_list) { %w[parent orphan] } | ||
it { is_expected.to contain_exactly %w[parent], %w[orphan] } | ||
end | ||
|
||
context 'parent, child' do | ||
let(:tag_list) { %w[parent child] } | ||
it { is_expected.to contain_exactly %w[parent child] } | ||
end | ||
|
||
context 'when tagged out of order' do | ||
let(:tag_list) { %w[child parent] } | ||
it { is_expected.to contain_exactly %w[parent child] } | ||
end | ||
|
||
context 'parent, orphan, child' do | ||
let(:tag_list) { %w[parent child orphan] } | ||
it { is_expected.to contain_exactly %w[parent child], %w[orphan] } | ||
end | ||
|
||
context 'parent, child, other_child' do | ||
let(:tag_list) { %w[parent child other_child] } | ||
it { is_expected.to contain_exactly %w[parent child], %w[parent other_child] } | ||
end | ||
|
||
context 'parent, child, other_child, grandchild' do | ||
let(:tag_list) { %w[parent child other_child grandchild] } | ||
it { is_expected.to contain_exactly %w[parent child grandchild], %w[parent other_child] } | ||
end | ||
|
||
context 'parent, child, other_child, grandchild' do | ||
let(:tag_list) { %w[parent child other_child grandchild] } | ||
it { is_expected.to contain_exactly %w[parent child grandchild], %w[parent other_child] } | ||
end | ||
end | ||
end |