Bring a number of files from spree_core needed in OFN

This commit is contained in:
Luis Ramos
2020-07-11 16:04:10 +01:00
parent 56b83b6bb5
commit 2e3702550d
14 changed files with 790 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
module Spree
module Core
module CalculatedAdjustments
def self.included(klass)
klass.class_eval do
has_one :calculator, :class_name => "Spree::Calculator", :as => :calculable, :dependent => :destroy
accepts_nested_attributes_for :calculator
validates :calculator, :presence => true
def self.calculators
spree_calculators.send model_name_without_spree_namespace
end
def calculator_type
calculator.class.to_s if calculator
end
def calculator_type=(calculator_type)
klass = calculator_type.constantize if calculator_type
self.calculator = klass.new if klass && !self.calculator.is_a?(klass)
end
# Creates a new adjustment for the target object (which is any class that has_many :adjustments) and
# sets amount based on the calculator as applied to the calculable argument (Order, LineItems[], Shipment, etc.)
# By default the adjustment will not be considered mandatory
def create_adjustment(label, target, calculable, mandatory=false, state="closed")
# Adjustment calculations done on Spree::Shipment objects MUST
# be done on their to_package'd variants instead
# It's only the package that contains the correct information.
# See https://github.com/spree/spree_active_shipping/pull/96 et. al
old_calculable = calculable
calculable = calculable.to_package if calculable.is_a?(Spree::Shipment)
amount = compute_amount(calculable)
return if amount == 0 && !mandatory
target.adjustments.create(
:amount => amount,
:source => old_calculable,
:originator => self,
:label => label,
:mandatory => mandatory,
:state => state
)
end
# Updates the amount of the adjustment using our Calculator and calling the +compute+ method with the +calculable+
# referenced passed to the method.
def update_adjustment(adjustment, calculable)
# Adjustment calculations done on Spree::Shipment objects MUST
# be done on their to_package'd variants instead
# It's only the package that contains the correct information.
# See https://github.com/spree/spree_active_shipping/pull/96 et. al
calculable = calculable.to_package if calculable.is_a?(Spree::Shipment)
adjustment.update_column(:amount, compute_amount(calculable))
end
# Calculate the amount to be used when creating an adjustment
# NOTE: May be overriden by classes where this module is included into.
# Such as Spree::Promotion::Action::CreateAdjustment.
def compute_amount(calculable)
self.calculator.compute(calculable)
end
private
def self.model_name_without_spree_namespace
self.to_s.tableize.gsub('/', '_').sub('spree_', '')
end
def self.spree_calculators
Rails.application.config.spree.calculators
end
end
end
end
end
end

View File

@@ -0,0 +1,89 @@
##
# Creates methods on object which delegate to an association proxy.
# see delegate_belongs_to for two uses
#
# Todo - integrate with ActiveRecord::Dirty to make sure changes to delegate object are noticed
# Should do
# class User < ActiveRecord::Base; delegate_belongs_to :contact, :firstname; end
# class Contact < ActiveRecord::Base; end
# u = User.first
# u.changed? # => false
# u.firstname = 'Bobby'
# u.changed? # => true
#
# Right now the second call to changed? would return false
#
# Todo - add has_one support. fairly straightforward addition
##
module DelegateBelongsTo
extend ActiveSupport::Concern
module ClassMethods
@@default_rejected_delegate_columns = ['created_at','created_on','updated_at','updated_on','lock_version','type','id','position','parent_id','lft','rgt']
mattr_accessor :default_rejected_delegate_columns
##
# Creates methods for accessing and setting attributes on an association. Uses same
# default list of attributes as delegates_to_association.
# @todo Integrate this with ActiveRecord::Dirty, so if you set a property through one of these setters and then call save on this object, it will save the associated object automatically.
# delegate_belongs_to :contact
# delegate_belongs_to :contact, [:defaults] ## same as above, and useless
# delegate_belongs_to :contact, [:defaults, :address, :fullname], :class_name => 'VCard'
##
def delegate_belongs_to(association, *attrs)
opts = attrs.extract_options!
initialize_association :belongs_to, association, opts
attrs = get_association_column_names(association) if attrs.empty?
attrs.concat get_association_column_names(association) if attrs.delete :defaults
attrs.each do |attr|
class_def attr do |*args|
if args.empty?
send(:delegator_for, association).send(attr)
else
send(:delegator_for, association).send(attr, *args)
end
end
class_def "#{attr}=" do |val|
send(:delegator_for, association).send("#{attr}=", val)
end
end
end
protected
def get_association_column_names(association, without_default_rejected_delegate_columns=true)
begin
association_klass = reflect_on_association(association).klass
methods = association_klass.column_names
methods.reject!{|x|default_rejected_delegate_columns.include?(x.to_s)} if without_default_rejected_delegate_columns
return methods
rescue
return []
end
end
##
# initialize_association :belongs_to, :contact
def initialize_association(type, association, opts={})
raise 'Illegal or unimplemented association type.' unless [:belongs_to].include?(type.to_s.to_sym)
send type, association, opts if reflect_on_association(association).nil?
end
private
def class_def(name, method=nil, &blk)
class_eval { method.nil? ? define_method(name, &blk) : define_method(name, method) }
end
end
def delegator_for(association)
send("#{association}=", self.class.reflect_on_association(association).klass.new) if send(association).nil?
send(association)
end
protected :delegator_for
end
ActiveRecord::Base.send :include, DelegateBelongsTo

View File

@@ -0,0 +1,5 @@
module Spree
module Core
class GatewayError < RuntimeError; end
end
end

View File

@@ -0,0 +1,22 @@
# Allows us to intercept any outbound mail message and make last minute changes
# (such as specifying a "from" address or sending to a test email account)
#
# See http://railscasts.com/episodes/206-action-mailer-in-rails-3 for more details.
module Spree
module Core
class MailInterceptor
def self.delivering_email(message)
return unless MailSettings.override?
if Config[:intercept_email].present?
message.subject = "#{message.to} #{message.subject}"
message.to = Config[:intercept_email]
end
if Config[:mail_bcc].present?
message.bcc ||= Config[:mail_bcc]
end
end
end
end
end

View File

@@ -0,0 +1,60 @@
module Spree
module Core
class MailSettings
MAIL_AUTH = ['None', 'plain', 'login', 'cram_md5']
SECURE_CONNECTION_TYPES = ['None','SSL','TLS']
# Override the Rails application mail settings based on preferences
# This makes it possible to configure the mail settings through an admin
# interface instead of requiring changes to the Rails envrionment file
def self.init
self.new.override! if override?
end
def self.override?
Config.override_actionmailer_config
end
def override!
if Config.enable_mail_delivery
ActionMailer::Base.default_url_options[:host] ||= Config.site_url
ActionMailer::Base.smtp_settings = mail_server_settings
ActionMailer::Base.perform_deliveries = true
else
ActionMailer::Base.perform_deliveries = false
end
end
private
def mail_server_settings
settings = if need_authentication?
basic_settings.merge(user_credentials)
else
basic_settings
end
settings.merge :enable_starttls_auto => secure_connection?
end
def user_credentials
{ :user_name => Config.smtp_username,
:password => Config.smtp_password }
end
def basic_settings
{ :address => Config.mail_host,
:domain => Config.mail_domain,
:port => Config.mail_port,
:authentication => Config.mail_auth_type }
end
def need_authentication?
Config.mail_auth_type != 'None'
end
def secure_connection?
Config.secure_connection_type == 'TLS'
end
end
end
end

View File

@@ -0,0 +1,71 @@
require 'stringex'
module Spree
module Core
module Permalinks
extend ActiveSupport::Concern
included do
class_attribute :permalink_options
end
module ClassMethods
def make_permalink(options={})
options[:field] ||= :permalink
self.permalink_options = options
if self.connected?
if self.table_exists? && self.column_names.include?(permalink_options[:field].to_s)
before_validation(:on => :create) { save_permalink }
end
end
end
def find_by_param(value, *args)
self.send("find_by_#{permalink_field}", value, *args)
end
def find_by_param!(value, *args)
self.send("find_by_#{permalink_field}!", value, *args)
end
def permalink_field
permalink_options[:field]
end
def permalink_prefix
permalink_options[:prefix] || ""
end
def permalink_order
order = permalink_options[:order]
"#{order} ASC," if order
end
end
def generate_permalink
"#{self.class.permalink_prefix}#{Array.new(9){rand(9)}.join}"
end
def save_permalink(permalink_value=self.to_param)
self.with_lock do
permalink_value ||= generate_permalink
field = self.class.permalink_field
# Do other links exist with this permalink?
other = self.class.where("#{self.class.table_name}.#{field} LIKE ?", "#{permalink_value}%")
if other.any?
# Find the existing permalink with the highest number, and increment that number.
# (If none of the existing permalinks have a number, this will evaluate to 1.)
number = other.map { |o| o.send(field)[/-(\d+)$/, 1].to_i }.max + 1
permalink_value += "-#{number.to_s}"
end
write_attribute(field, permalink_value)
end
end
end
end
end
ActiveRecord::Base.send :include, Spree::Core::Permalinks
ActiveRecord::Relation.send :include, Spree::Core::Permalinks

View File

@@ -0,0 +1,25 @@
module Spree
module Core
# This module exists to reduce duplication in S3 settings between
# the Image and Taxon models in Spree
module S3Support
extend ActiveSupport::Concern
included do
def self.supports_s3(field)
# Load user defined paperclip settings
config = Spree::Config
if config[:use_s3]
s3_creds = { :access_key_id => config[:s3_access_key], :secret_access_key => config[:s3_secret], :bucket => config[:s3_bucket] }
self.attachment_definitions[field][:storage] = :s3
self.attachment_definitions[field][:s3_credentials] = s3_creds
self.attachment_definitions[field][:s3_headers] = ActiveSupport::JSON.decode(config[:s3_headers])
self.attachment_definitions[field][:bucket] = config[:s3_bucket]
self.attachment_definitions[field][:s3_protocol] = config[:s3_protocol].downcase unless config[:s3_protocol].blank?
self.attachment_definitions[field][:s3_host_alias] = config[:s3_host_alias] unless config[:s3_host_alias].blank?
end
end
end
end
end
end

View File

@@ -0,0 +1,27 @@
module Spree
module Core
module TokenResource
module ClassMethods
def token_resource
has_one :tokenized_permission, :as => :permissable
delegate :token, :to => :tokenized_permission, :allow_nil => true
after_create :create_token
end
end
def create_token
permission = build_tokenized_permission
permission.token = token = ::SecureRandom::hex(8)
permission.save!
token
end
def self.included(receiver)
receiver.extend ClassMethods
end
end
end
end
ActiveRecord::Base.class_eval { include Spree::Core::TokenResource }

View File

@@ -0,0 +1,61 @@
module Spree
class ProductDuplicator
attr_accessor :product
def initialize(product)
@product = product
end
def duplicate
new_product = duplicate_product
# don't dup the actual variants, just the characterising types
new_product.option_types = product.option_types if product.has_variants?
# allow site to do some customization
new_product.send(:duplicate_extra, product) if new_product.respond_to?(:duplicate_extra)
new_product.save!
new_product
end
protected
def duplicate_product
product.dup.tap do |new_product|
new_product.name = "COPY OF #{product.name}"
new_product.taxons = product.taxons
new_product.created_at = nil
new_product.deleted_at = nil
new_product.updated_at = nil
new_product.product_properties = reset_properties
new_product.master = duplicate_master
end
end
def duplicate_master
master = product.master
master.dup.tap do |new_master|
new_master.sku = "COPY OF #{master.sku}"
new_master.deleted_at = nil
new_master.images = master.images.map { |image| duplicate_image image }
new_master.price = master.price
new_master.currency = master.currency
end
end
def duplicate_image(image)
new_image = image.dup
new_image.assign_attributes(:attachment => image.attachment.clone)
new_image
end
def reset_properties
product.product_properties.map do |prop|
prop.dup.tap do |new_prop|
new_prop.created_at = nil
new_prop.updated_at = nil
end
end
end
end
end

View File

@@ -0,0 +1,69 @@
require 'spec_helper'
# Its pretty difficult to test this module in isolation b/c it needs to work in conjunction with an actual class that
# extends ActiveRecord::Base and has a corresponding table in the database. So we'll just test it using Order and
# ShippingMethod instead since those classes are including the module.
describe Spree::Core::CalculatedAdjustments do
let(:calculator) { mock_model(Spree::Calculator, :compute => 10, :[]= => nil) }
it "should add has_one :calculator relationship" do
assert Spree::ShippingMethod.reflect_on_all_associations(:has_one).map(&:name).include?(:calculator)
end
let(:tax_rate) { Spree::TaxRate.new(:calculator => calculator) }
context "#create_adjustment and its resulting adjustment" do
let(:order) { Spree::Order.create }
let(:target) { order }
it "should be associated with the target" do
target.adjustments.should_receive(:create)
tax_rate.create_adjustment("foo", target, order)
end
it "should have the correct originator and an amount derived from the calculator and supplied calculable" do
adjustment = tax_rate.create_adjustment("foo", target, order)
adjustment.should_not be_nil
adjustment.amount.should == 10
adjustment.source.should == order
adjustment.originator.should == tax_rate
end
it "should be mandatory if true is supplied for that parameter" do
adjustment = tax_rate.create_adjustment("foo", target, order, true)
adjustment.should be_mandatory
end
context "when the calculator returns 0" do
before { calculator.stub :compute => 0 }
context "when adjustment is mandatory" do
before { tax_rate.create_adjustment("foo", target, order, true) }
it "should create an adjustment" do
Spree::Adjustment.count.should == 1
end
end
context "when adjustment is not mandatory" do
before { tax_rate.create_adjustment("foo", target, order, false) }
it "should not create an adjustment" do
Spree::Adjustment.count.should == 0
end
end
end
end
context "#update_adjustment" do
it "should update the adjustment using its calculator (and the specified source)" do
adjustment = double(:adjustment).as_null_object
calculable = double :calculable
adjustment.should_receive(:update_column).with(:amount, 10)
tax_rate.update_adjustment(adjustment, calculable)
end
end
end

View File

@@ -0,0 +1,78 @@
require 'spec_helper'
# We'll use the OrderMailer as a quick and easy way to test. IF it works here
# it works for all email (in theory.)
describe Spree::OrderMailer do
let(:order) { Spree::Order.new(:email => "customer@example.com") }
let(:message) { Spree::OrderMailer.confirm_email(order) }
before(:all) do
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries.clear
end
context "#deliver" do
before do
ActionMailer::Base.delivery_method = :test
end
after { ActionMailer::Base.deliveries.clear }
it "should use the from address specified in the preference" do
Spree::Config[:mails_from] = "no-reply@foobar.com"
message.deliver
@email = ActionMailer::Base.deliveries.first
@email.from.should == ["no-reply@foobar.com"]
end
it "should use the provided from address" do
Spree::Config[:mails_from] = "preference@foobar.com"
message.from = "override@foobar.com"
message.to = "test@test.com"
message.deliver
email = ActionMailer::Base.deliveries.first
email.from.should == ["override@foobar.com"]
email.to.should == ["test@test.com"]
end
it "should add the bcc email when provided" do
Spree::Config[:mail_bcc] = "bcc-foo@foobar.com"
message.deliver
@email = ActionMailer::Base.deliveries.first
@email.bcc.should == ["bcc-foo@foobar.com"]
end
context "when intercept_email is provided" do
it "should strip the bcc recipients" do
message.bcc.should be_blank
end
it "should strip the cc recipients" do
message.cc.should be_blank
end
it "should replace the receipient with the specified address" do
Spree::Config[:intercept_email] = "intercept@foobar.com"
message.deliver
@email = ActionMailer::Base.deliveries.first
@email.to.should == ["intercept@foobar.com"]
end
it "should modify the subject to include the original email" do
Spree::Config[:intercept_email] = "intercept@foobar.com"
message.deliver
@email = ActionMailer::Base.deliveries.first
@email.subject.match(/customer@example\.com/).should be_true
end
end
context "when intercept_mode is not provided" do
it "should not modify the recipient" do
Spree::Config[:intercept_email] = ""
message.deliver
@email = ActionMailer::Base.deliveries.first
@email.to.should == ["customer@example.com"]
end
end
end
end

View File

@@ -0,0 +1,89 @@
require 'spec_helper'
module Spree
module Core
describe MailSettings do
let!(:subject) { MailSettings.new }
context "override option is true" do
before { Config.override_actionmailer_config = true }
context "init" do
it "calls override!" do
MailSettings.should_receive(:new).and_return(subject)
subject.should_receive(:override!)
MailSettings.init
end
end
context "enable delivery" do
before { Config.enable_mail_delivery = true }
context "overrides appplication defaults" do
context "authentication method is none" do
before do
Config.mail_host = "smtp.example.com"
Config.mail_domain = "example.com"
Config.mail_port = 123
Config.mail_auth_type = MailSettings::SECURE_CONNECTION_TYPES[0]
Config.smtp_username = "schof"
Config.smtp_password = "hellospree!"
Config.secure_connection_type = "TLS"
subject.override!
end
it { ActionMailer::Base.smtp_settings[:address].should == "smtp.example.com" }
it { ActionMailer::Base.smtp_settings[:domain].should == "example.com" }
it { ActionMailer::Base.smtp_settings[:port].should == 123 }
it { ActionMailer::Base.smtp_settings[:authentication].should == "None" }
it { ActionMailer::Base.smtp_settings[:enable_starttls_auto].should be_true }
it "doesnt touch user name config" do
ActionMailer::Base.smtp_settings[:user_name].should == nil
end
it "doesnt touch password config" do
ActionMailer::Base.smtp_settings[:password].should == nil
end
end
end
context "when mail_auth_type is other than none" do
before do
Config.mail_auth_type = "login"
Config.smtp_username = "schof"
Config.smtp_password = "hellospree!"
subject.override!
end
context "overrides user credentials" do
it { ActionMailer::Base.smtp_settings[:user_name].should == "schof" }
it { ActionMailer::Base.smtp_settings[:password].should == "hellospree!" }
end
end
end
context "do not enable delivery" do
before do
Config.enable_mail_delivery = false
subject.override!
end
it { ActionMailer::Base.perform_deliveries.should be_false }
end
end
context "override option is false" do
before { Config.override_actionmailer_config = false }
context "init" do
it "doesnt calls override!" do
subject.should_not_receive(:override!)
MailSettings.init
end
end
end
end
end
end

View File

@@ -0,0 +1,32 @@
require 'spec_helper'
# Its pretty difficult to test this module in isolation b/c it needs to work in conjunction with an actual class that
# extends ActiveRecord::Base and has a corresponding table in the database. So we'll just test it using Order instead
# since those classes are including the module.
describe Spree::Core::TokenResource do
let(:order) { Spree::Order.new }
let(:permission) { mock_model(Spree::TokenizedPermission) }
it 'should add has_one :tokenized_permission relationship' do
assert Spree::Order.reflect_on_all_associations(:has_one).map(&:name).include?(:tokenized_permission)
end
context '#token' do
it 'should return the token of the associated permission' do
order.stub :tokenized_permission => permission
permission.stub :token => 'foo'
order.token.should == 'foo'
end
it 'should return nil if there is no associated permission' do
order.token.should be_nil
end
end
context '#create_token' do
it 'should create a randomized 16 character token' do
token = order.create_token
token.size.should == 16
end
end
end

View File

@@ -0,0 +1,87 @@
require 'spec_helper'
module Spree
describe Spree::ProductDuplicator do
let(:product) do
double 'Product',
:name => "foo",
:taxons => [],
:product_properties => [property],
:master => variant,
:has_variants? => false
end
let(:new_product) do
double 'New Product',
:save! => true
end
let(:property) do
double 'Property'
end
let(:new_property) do
double 'New Property'
end
let(:variant) do
double 'Variant',
:sku => "12345",
:price => 19.99,
:currency => "AUD",
:images => [image]
end
let(:new_variant) do
double 'New Variant',
:sku => "12345"
end
let(:image) do
double 'Image',
:attachment => double('Attachment')
end
let(:new_image) do
double 'New Image'
end
before do
product.should_receive(:dup).and_return(new_product)
variant.should_receive(:dup).and_return(new_variant)
image.should_receive(:dup).and_return(new_image)
property.should_receive(:dup).and_return(new_property)
end
it "can duplicate a product" do
duplicator = Spree::ProductDuplicator.new(product)
new_product.should_receive(:name=).with("COPY OF foo")
new_product.should_receive(:taxons=).with([])
new_product.should_receive(:product_properties=).with([new_property])
new_product.should_receive(:created_at=).with(nil)
new_product.should_receive(:updated_at=).with(nil)
new_product.should_receive(:deleted_at=).with(nil)
new_product.should_receive(:master=).with(new_variant)
new_variant.should_receive(:sku=).with("COPY OF 12345")
new_variant.should_receive(:deleted_at=).with(nil)
new_variant.should_receive(:images=).with([new_image])
new_variant.should_receive(:price=).with(variant.price)
new_variant.should_receive(:currency=).with(variant.currency)
image.attachment.should_receive(:clone).and_return(image.attachment)
new_image.should_receive(:assign_attributes).
with(:attachment => image.attachment).
and_return(new_image)
new_property.should_receive(:created_at=).with(nil)
new_property.should_receive(:updated_at=).with(nil)
duplicator.duplicate
end
end
end