Support for SupporterAddress events and some Supporter events

This commit is contained in:
Eric Schultz 2021-01-19 14:00:23 -06:00 committed by Eric Schultz
parent 4271334311
commit cab6465e30
9 changed files with 339 additions and 37 deletions

View file

@ -5,7 +5,8 @@
module Nonprofits
class SupportersController < ApplicationController
include Controllers::Nonprofit::Current
include Controllers::Nonprofit::Authorization
include Controllers::Nonprofit::Authorization
include Controllers::Supporter::Current
before_action :authenticate_nonprofit_user!, except: %i[new create]
@ -54,13 +55,12 @@ module Nonprofits
end
def email_address
render json: Supporter.find(params[:supporter_id]).email
render json: current_supporter.email
end
def full_contact
begin
s = Supporter.find params[:id]
if s.method_defined? :full_contact_infos && (fc = s.full_contact_infos.first)
begin
if current_supporter.method_defined? :full_contact_infos && (fc = current_supporter.full_contact_infos.first)
render json: { full_contact: QueryFullContactInfos.fetch_associated_tables(fc.id) }
else
render json: { full_contact: nil }
@ -76,13 +76,12 @@ module Nonprofits
# post /nonprofits/:nonprofit_id/supporters
def create
render_json { InsertSupporter.create_or_update(params[:nonprofit_id], create_supporter_params.to_h) }
render_json { InsertSupporter.create_or_update(nonprofit, create_supporter_params.to_h) }
end
# put /nonprofits/:nonprofit_id/supporters/:id
def update
@supporter = current_nonprofit.supporters.find(params[:id])
json_saved UpdateSupporter.from_info(@supporter, update_supporter_params[:supporter])
json_saved UpdateSupporter.from_info(current_supporter, update_supporter_params[:supporter])
end
def bulk_delete

View file

@ -55,6 +55,22 @@ class Supporter < ApplicationRecord
validates :nonprofit, presence: true
scope :not_deleted, -> { where(deleted: false) }
# TODO replace with Discard gem
define_model_callbacks :discard
after_discard :publish_deleted
after_create_commit :publish_create
after_update_commit :publish_updated
# TODO replace with discard gem
def discard!
run_callbacks(:discard) do
self.deleted = true
save!
end
end
geocoded_by :full_address
reverse_geocoded_by :latitude, :longitude do |obj, results|
geo = results.first
@ -82,15 +98,96 @@ class Supporter < ApplicationRecord
end
def to_builder(*expand)
supporter_addresses = [self]
Jbuilder.new do |json|
json.object "supporter"
json.id id
json.(self, :id, :name, :organization, :phone, :anonymous, :deleted)
if expand.include? :supporter_address
json.supporter_addresses supporter_addresses do |i|
json.merge! i.to_supporter_address_builder.attributes!
end
else
json.supporter_addresses [id]
end
if expand.include? :nonprofit
json.nonprofit nonprofit.to_builder
else
json.nonprofit nonprofit.id
end
unless merged_into.nil?
if expand.include? :merged_into
json.merged_into merged_into.to_builder
else
json.merged_into merged_into.id
end
else
json.merged_into nil
end
end
end
def to_supporter_address_builder(*expand)
Jbuilder.new do |json|
json.(self, :id, :address, :state_code, :city, :country, :zip_code, :deleted)
json.object 'supporter_address'
if expand.include? :supporter
json.supporter to_builder
else
json.supporter id
end
end
end
def full_address
Format::Address.full_address(address, city, state_code)
end
private
ADDRESS_ATTRIBUTES = [:address, :city, :state_code, :zip_code, :country]
def supporter_address_updated?
ADDRESS_ATTRIBUTES.any?{|attrib| saved_change_to_attribute?(attrib)}
end
def nonsupporter_address_updated?
(saved_changes.keys.map{|i| i.to_sym} - ADDRESS_ATTRIBUTES).any?
end
def publish_create
Houdini.event_publisher.announce(:supporter_created, to_event('supporter.created', :supporter_address, :nonprofit).attributes!)
Houdini.event_publisher.announce(:supporter_address_created, to_event('supporter_address.created', :supporter, :nonprofit).attributes!)
end
def publish_updated
if !deleted
if nonsupporter_address_updated?
Houdini.event_publisher.announce(:supporter_updated, to_event('supporter.updated', :supporter_address, :nonprofit).attributes!)
end
if supporter_address_updated?
Houdini.event_publisher.announce(:supporter_address_updated, to_event('supporter_address.updated', :supporter, :nonprofit).attributes!)
end
end
end
def publish_deleted
Houdini.event_publisher.announce(:supporter_deleted, to_event('supporter.deleted', :supporter_address, :nonprofit, :merged_into).attributes!)
Houdini.event_publisher.announce(:supporter_address_deleted, to_event('supporter_address.deleted', :supporter, :nonprofit).attributes!)
end
# we do something custom here since Supporter and SupporterAddress are in the same model
def to_event(event_type, *expand)
Jbuilder.new do |event|
event.id SecureRandom.uuid
event.object 'object_event'
event.type event_type
event.data do
event.object event_type.start_with?('supporter_address') ? to_supporter_address_builder(*expand) : to_builder(*expand)
end
end
end
end
ActiveSupport.run_load_hooks(:houdini_supporter, Supporter)

View file

@ -7,9 +7,7 @@ require 'qexpr'
require 'i18n'
module InsertSupporter
def self.create_or_update(np_id, data, update = false)
ParamValidation.new(data.merge(np_id: np_id),
np_id: { required: true, is_integer: true })
def self.create_or_update(nonprofit, data, update = false)
address_keys = %w[name address city country state_code]
custom_fields = data['customFields']
data = HashWithIndifferentAccess.new(Format::RemoveDiacritics.from_hash(data, address_keys))
@ -17,7 +15,7 @@ module InsertSupporter
supporter = Qx.select('*').from(:supporters)
.where('name = $n AND email = $e', n: data[:name], e: data[:email])
.and_where('nonprofit_id=$id', id: np_id)
.and_where('nonprofit_id=$id', id: nonprofit.id)
.and_where('coalesce(deleted, FALSE)=FALSE')
.execute.last
if supporter && update
@ -28,20 +26,13 @@ module InsertSupporter
.timestamps
.execute.last
else
supporter = Qx.insert_into(:supporters)
.values(defaults(data).merge(nonprofit_id: np_id))
.returning('*')
.timestamps
.execute.last
supporter = nonprofit.supporters.create(defaults(data))
end
if custom_fields
InsertCustomFieldJoins.find_or_create(np_id, [supporter['id']], custom_fields)
InsertCustomFieldJoins.find_or_create(nonprofit.id, [supporter.id], custom_fields)
end
# GeocodeModel.delay.supporter(supporter['id'])
Houdini.event_publisher.announce(:supporter_create, Supporter.find(supporter['id']))
supporter
end

View file

@ -117,6 +117,8 @@ describe InsertDonation do
end
it 'process campaign donation' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:campaign_create, any_args)
process_campaign_donation(sepa: true) { InsertDonation.with_sepa(amount: charge_amount, nonprofit_id: nonprofit.id, supporter_id: supporter.id, direct_debit_detail_id: direct_debit_detail.id, campaign_id: campaign.id, date: (Time.now + 1.day).to_s, dedication: 'dedication', designation: 'designation') }
end

View file

@ -287,6 +287,8 @@ describe InsertTickets do
expect(QueryRoles).to receive(:is_authorized_for_nonprofit?).with(user.id, nonprofit.id).and_return true
result = nil
expect(Houdini.event_publisher).to receive(:announce).with(:ticket_level_created, any_args).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:ticket_create, any_args).ordered
result = InsertTickets.create(tickets: [{ quantity: 1, ticket_level_id: ticket_level.id }], nonprofit_id: nonprofit.id, supporter_id: supporter.id, token: source_token.token, event_id: event.id, kind: 'offsite', offsite_payment: { kind: 'check', check_number: 'fake_checknumber' }, current_user: user)

View file

@ -6,7 +6,7 @@ require 'rails_helper'
describe RetrieveActiveRecordItems do
describe '.retrieve' do
let(:item) { force_create(:supporter) }
let(:item) { force_create(:supporter, nonprofit: item2) }
let(:item2) { force_create(:nm_justice) }
it 'raises if not a class for key' do
expect { RetrieveActiveRecordItems.retrieve('item' => 1) }.to raise_error(ArgumentError)
@ -41,7 +41,7 @@ describe RetrieveActiveRecordItems do
end
describe '.retrieve_from_keys' do
let(:item) { force_create(:supporter) }
let(:item) { force_create(:supporter, nonprofit: item2) }
let(:item2) { force_create(:nm_justice) }
it 'raises if not a class for key' do
expect { RetrieveActiveRecordItems.retrieve_from_keys({}, 'item' => 1) }.to raise_error(ArgumentError)

View file

@ -9,7 +9,7 @@ describe UpdateRecurringDonations do
describe '.cancel' do
let(:np) { force_create(:nm_justice) }
let(:s) { force_create(:supporter) }
let(:s) { force_create(:supporter, nonprofit: np) }
let(:donation) { force_create(:donation, nonprofit_id: np.id, supporter_id: s.id) }
let(:email) { 'test@test.com' }
let!(:rd) do

View file

@ -10,11 +10,29 @@ RSpec.describe SupporterNote, type: :model do
let(:content2) {"CONTENT2"}
let(:supporter_note) { supporter.supporter_notes.create(content: content, user: user) }
let(:supporter_base) do
{
'anonymous' => false,
'deleted' => false,
'name' => name,
'organization' => nil,
'phone' => nil,
'supporter_addresses' => [kind_of(Numeric)],
'id'=> kind_of(Numeric),
'merged_into' => nil,
'nonprofit'=> nonprofit.id,
'object' => 'supporter'
}
end
it 'creates' do
expect(supporter_note.errors).to be_empty
end
it 'announces created' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_note_created, {
'id' => kind_of(String),
'object' => 'object_event',
@ -34,10 +52,7 @@ RSpec.describe SupporterNote, type: :model do
'id' => user.id,
'object' => 'user'
},
'supporter' => {
'id' => supporter.id,
'object' => 'supporter'
}
'supporter' => supporter_base.merge({'name' => "Fake Supporter Name"})
}
}
})
@ -46,6 +61,8 @@ RSpec.describe SupporterNote, type: :model do
end
it 'announces updated' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_note_created, anything).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_note_updated, {
'id' => kind_of(String),
@ -66,10 +83,7 @@ RSpec.describe SupporterNote, type: :model do
'id' => user.id,
'object' => 'user'
},
'supporter' => {
'id' => supporter.id,
'object' => 'supporter'
}
'supporter' => supporter_base.merge({'name' => "Fake Supporter Name"})
}
}
}).ordered
@ -80,6 +94,8 @@ RSpec.describe SupporterNote, type: :model do
end
it 'announces deleted' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_note_created, anything).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_note_deleted, {
'id' => kind_of(String),
@ -100,10 +116,7 @@ RSpec.describe SupporterNote, type: :model do
'id' => user.id,
'object' => 'user'
},
'supporter' => {
'id' => supporter.id,
'object' => 'supporter'
}
'supporter' => supporter_base.merge({'name' => "Fake Supporter Name"})
}
}
}).ordered

View file

@ -0,0 +1,198 @@
# frozen_string_literal: true
# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later
# Full license explanation at https://github.com/houdiniproject/houdini/blob/master/LICENSE
require 'rails_helper'
RSpec.describe Supporter, type: :model do
include_context :shared_donation_charge_context
let(:name) {"CUSTOM SSUPPORTER"}
let(:merged_into_supporter_name) {"I've been merged into!"}
let(:address) { "address for supporter"}
let(:supporter) { nonprofit.supporters.create(name: name, address: address)}
let(:merged_supporter) {nonprofit.supporters.create(name: name, address: address, merged_into: merged_into_supporter, deleted: true) }
let(:merged_into_supporter) {nonprofit.supporters.create(name: merged_into_supporter_name, address: address) }
let(:supporter_base) do
{
'anonymous' => false,
'deleted' => false,
'name' => name,
'organization' => nil,
'phone' => nil,
'supporter_addresses' => [kind_of(Numeric)],
'id'=> kind_of(Numeric),
'merged_into' => nil,
'nonprofit'=> nonprofit.id,
'object' => 'supporter'
}
end
let(:supporter_address_base) do
{
'id' => kind_of(Numeric),
'deleted' => false,
'address' => address,
'city' => nil,
'state_code' => nil,
'zip_code' => nil,
'country' => 'United States',
'object' => 'supporter_address',
'supporter' => kind_of(Numeric)
}
end
let(:nonprofit_base) do
{
'id' => nonprofit.id,
'name' => nonprofit.name,
'object' => 'nonprofit'
}
end
describe 'supporter' do
it 'created' do
supporter_result = supporter_base.merge({
'supporter_addresses' => [
supporter_address_base
],
'nonprofit' => nonprofit_base
})
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, {
'id' => kind_of(String),
'object' => 'object_event',
'type' => 'supporter.created',
'data' => {
'object' => supporter_result
}
}).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything).ordered
supporter
end
it 'deletes' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created,anything).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything).ordered
expect(Houdini.event_publisher).to_not receive(:announce).with(:supporter_updated)
expect(Houdini.event_publisher).to_not receive(:announce).with(:supporter_address_updated)
supporter_result = supporter_base.merge({
'deleted' => true,
'supporter_addresses' => [
supporter_address_base.merge({'deleted' => true})
],
'nonprofit' => nonprofit_base
})
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_deleted, {
'id' => kind_of(String),
'object' => 'object_event',
'type' => 'supporter.deleted',
'data' => {
'object' => supporter_result
}
}).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_deleted,anything).ordered
supporter.discard!
end
end
describe 'supporter_address events' do
it 'creates' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, {
'id' => kind_of(String),
'object' => 'object_event',
'type' => 'supporter_address.created',
'data' => {
'object' => supporter_address_base.merge({
'supporter' => supporter_base
})
}
}).ordered
supporter
end
it 'deletes' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created,anything).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything).ordered
expect(Houdini.event_publisher).to_not receive(:announce).with(:supporter_updated)
expect(Houdini.event_publisher).to_not receive(:announce).with(:supporter_address_updated)
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_deleted, anything)
supporter_address_result = supporter_address_base.merge({
'deleted' => true,
'supporter'=> supporter_base.merge({'deleted' => true})
})
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_deleted, {
'id' => kind_of(String),
'object' => 'object_event',
'type' => 'supporter_address.deleted',
'data' => {
'object' => supporter_address_result
}
}).ordered
supporter.discard!
end
end
describe 'supporter and supporter_address events update events are separate' do
it 'only fires supporter on supporter only change' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_updated, { 'id' => kind_of(String),
'object' => 'object_event',
'type' => 'supporter.updated',
'data' => {
'object' => supporter_base.merge({
'name' => merged_into_supporter_name,
'supporter_addresses' => [
supporter_address_base
],
'nonprofit' => nonprofit_base
})
}}).ordered
expect(Houdini.event_publisher).to_not receive(:announce).with(:supporter_address_updated, anything)
supporter.update(name: merged_into_supporter_name)
end
it 'only fires supporter_address on supporter_address only change' do
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_created, anything).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_created, anything).ordered
expect(Houdini.event_publisher).to receive(:announce).with(:supporter_address_updated, {
'id' => kind_of(String),
'object' => 'object_event',
'type' => 'supporter_address.updated',
'data' => {
'object' => supporter_address_base.merge({
'city' => 'new_city',
'supporter'=> supporter_base
})
}}).ordered
#expect(Houdini.event_publisher).to_not receive(:announce).with(:supporter_updated, anything)
supporter.update(city: 'new_city')
end
end
end