我一直在与一个主要的重构器进行斗争,以减少支付控制器并且可以使用一只手。第一步我试图修理我的工厂。现在所有的工厂都能很好地工作,但是当我尝试建立关联FactoryGirl.create(:job, :purchased_with_coupon)
时,它会在优惠券上正确设置关联,而不是付款。这意味着支付的价格始终是1.我只是注意到了这一点,你可以看到其他部分被注释掉了。在我开始处理臃肿的控制器之前,我需要弄清楚我的测试。思考?
工厂
FactoryGirl.define do
factory :job do
category
company
title { FFaker::Company.position }
location { "#{FFaker::Address.city}, #{FFaker::AddressUS.state}" }
language_list { [FFaker::Lorem.word] }
short_description { FFaker::Lorem.sentence }
description { FFaker::HTMLIpsum.body }
application_process { "Please email #{FFaker::Internet.email} about the position." }
trait :featured do |job|
job.is_featured true
end
trait :reviewed do |job|
job.reviewed_at { Time.now }
end
trait :purchased do |job|
job.reviewed_at { Time.now }
job.start_at { Time.now }
job.end_at { AppConfig.product['settings']['job_active_for_day_num'].day.from_now }
job.paid_at { Time.now }
payments { |j| [j.association(:payment)] }
end
trait :purchased_with_coupon do |job|
job.reviewed_at { Time.now }
job.start_at { Time.now }
job.end_at { AppConfig.product['settings']['job_active_for_day_num'].day.from_now }
job.paid_at { Time.now }
association :coupon, factory: :coupon
payments { |j| [j.association(:payment)] }
end
trait :expired do |job|
start_at = (200..500).to_a.sample.days.ago
job.reviewed_at { start_at }
job.start_at { start_at }
job.end_at { |j| j.start_at + AppConfig.product['settings']['job_active_for_day_num'].days }
job.paid_at { start_at }
payments { |j| [j.association(:payment)] }
end
end
end
FactoryGirl.define do
factory :payment do
job
# price_paid { rand(100..150) }
price_paid { 1 }
stripe_customer_token { (0...50).map { (65 + rand(26)).chr }.join }
end
end
FactoryGirl.define do
factory :coupon do
code { rand(25**10) }
percent_discount { rand(100**1) }
start_at { 2.days.ago }
end_at { 30.day.from_now }
trait :executed do |c|
association :job, factory: [:job, :purchased]
c.executed_at { Time.now }
end
end
end
模型
class Job < ActiveRecord::Base
acts_as_paranoid
strip_attributes
acts_as_taggable
acts_as_taggable_on :languages
belongs_to :company
before_validation :find_company
belongs_to :category
has_one :coupon
has_many :payments
before_create :create_slug, :set_price
after_create :update_vanity_url
accepts_attachments_for :company
accepts_nested_attributes_for :company
accepts_nested_attributes_for :coupon
accepts_nested_attributes_for :payments
validates :title,
:location,
:short_description,
presence: true,
format: { with: /\A[\w\d .,:-@]+\z/, message: :bad_format }
validates :application_process,
presence: true,
format: { with: %r{\A[\w\d .,:/@&=?-]+\z}, message: :bad_format }
validates :title, length: { minimum: 10, maximum: 45 }
validates :location, length: { minimum: 10, maximum: 95 }
validates :short_description, length: { minimum: 10, maximum: 245 }
validates :application_process, length: { minimum: 10, maximum: 95 }
validates :description,
:category_id,
:language_list,
presence: true
validates :reviewed_at,
:start_at,
:end_at,
:paid_at,
date: { allow_blank: true }
validates :start_at, date: { before: :end_at, message: :start_at_before_end_at }, if: proc { start_at? }
validates :end_at, date: { after: :start_at, message: :end_at_after_start_at }, if: proc { end_at? }
scope :active, -> { where.not(reviewed_at: nil, paid_at: nil).where('end_at >= ?', Date.today) }
def expired?
end_at.present? && end_at < Date.today
end
def reviewed?
reviewed_at.present?
end
def paid_for?
reviewed? && paid_at.present?
end
def active?
reviewed? && paid_at.present? && end_at <= Date.today
end
private
def set_price
self.price = AppConfig.product['settings']['job_base_price']
end
def create_slug
self.slug = title.downcase.parameterize
end
def update_vanity_url
self.vanity_url = '/jobs/' + company.slug + '/' + slug + '/' + id.to_s + '/'
save
end
def find_company
existing_company = Company.where(email: company.email) if company
self.company = existing_company.first if existing_company.count > 0
end
end
class Coupon < ActiveRecord::Base
acts_as_paranoid
strip_attributes
belongs_to :job
validates :start_at, date: { before: :end_at }
validates :executed_at, date: { allow_blank: true }
validates_presence_of :job, if: proc { executed_at? }
validates_presence_of :executed_at, if: :job
validates :code,
presence: true,
length: { minimum: 10, maximum: 19 },
uniqueness: { case_sensitive: false },
numericality: { only_integer: true }
validates :percent_discount,
inclusion: { in: 1..100 },
length: { minimum: 1, maximum: 3 },
numericality: { only_integer: true },
presence: true
scope :active, -> { where('start_at < ? AND end_at > ? AND executed_at IS ?', Date.today, Date.today, nil) }
def active?
start_at < Date.today && end_at > Date.today && executed_at.nil?
end
def executed?
job_id.present?
end
end
class Payment < ActiveRecord::Base
belongs_to :job
belongs_to :coupon
validates_presence_of :job
validate :coupon_must_be_active
before_create :net_price
Numeric.include CoreExtensions::Numeric::Percentage
attr_accessor :coupon_code
def coupon_code=(code)
@coupon = Coupon.find_by_code(code)
end
def net_price
return job.price unless @coupon
job.price = @coupon.percent_discount.percent_of(job.price)
self.coupon = @coupon
end
private
def coupon_must_be_active
if @coupon
errors[:coupon] << I18n.t('flash_messages.coupons.id.inactive') unless @coupon.active?
elsif @coupon_code.present?
errors[:coupon_code] << I18n.t('flash_messages.coupons.id.not_found')
end
end
end
答案 0 :(得分:2)
问题似乎是模型之外的逻辑正在更新price_paid
上的Payment
列,并且可能还会在其上设置coupon_id
。< / p>
因此,我建议将可能来自控制器,服务类等的任何额外逻辑复制到工厂的after(:create)
回调中。
trait :purchased_with_coupon do
# ...other attributes...
association :coupon
after(:create) do |job, evaulator|
discount_value = 100 - job.coupon.percent_discount) / 100.0
calculated_price_paid = job.price * discount_value
create(:payment, price_paid: price_paid, job: job, coupon: coupon)
end
end
现在最终,该代码属于某种抽象,例如可以轻松测试(并在其他测试中使用)的服务类。但是,您提到您正在开始使用重构并希望通过测试。我认为这是一个合理的妥协,直到你准备好抽象它。最终,我会做这样的事情:
class CreatePaymentWithCoupon
attr_reader :job
def initialize(job)
@job = job
end
def call
job.payments.create(coupon: job.coupon, price_paid: discounted_price)
end
private
def discounted_price
discount_value = (100 - job.coupon.percent_discount) / 100.0
job.price * discount_value
end
end
然后,在您的规格中:
it "calculates discounted price" do
coupon = create(:coupon, percent_discount: 25)
job = create(:job, :purchased_with_coupon, price: 100)
CreatePaymentWithCoupon.new(job).call
expect(job.payments.first.price_paid).to eq(75.0)
end