-
-
Save kevinansfield/5ac5c612921418ea874a to your computer and use it in GitHub Desktop.
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
class Competition < ActiveRecord::Base | |
belongs_to :discipline_class | |
has_one :discipline, through: :discipline_class | |
belongs_to :judge | |
has_many :moves, -> { order('position ASC') }, dependent: :destroy | |
has_many :competition_entries, dependent: :destroy | |
has_many :awards, through: :competition_entries | |
has_many :competitors, through: :competition_entries | |
has_one :example_video, class_name: 'Video', as: :videoable, | |
dependent: :destroy | |
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
class Competition < ActiveRecord::Base | |
require_dependency 'competition/operations/give_awards' | |
class Create < Trailblazer::Operation | |
include CRUD | |
model Competition, :create | |
contract do | |
extend CarrierWave::Mount | |
include Reform::Form::ModelReflections | |
include Reform::Form::MultiParameterAttributes | |
include Disposable::Twin::Collection::Semantics | |
property :discipline_class_id | |
property :judge_id | |
property :name | |
property :description | |
property :start_at | |
property :end_at | |
property :pattern | |
mount_uploader :pattern, PatternUploader | |
validates :discipline_class_id, presence: true | |
validates :judge_id, presence: true | |
validates :name, presence: true, | |
uniqueness: { scope: :discipline_class_id, case_sensitive: false } | |
validates :start_at, presence: true | |
validates :end_at, presence: true | |
validate :end_at_later_than_start_at | |
validate :start_at_does_not_overlap_existing_competition | |
validate :end_at_does_not_overlap_existing_competition | |
# TODO: Need to validate that we have at least one maneuver | |
property :example_video, populate_if_empty: Video do | |
property :video_file | |
end | |
collection :moves, populate_if_empty: Move, skip_if: :all_blank do | |
delegate :new_record?, to: :model | |
property :name | |
property :position | |
validates :name, presence: true | |
end | |
private | |
def end_at_later_than_start_at | |
if end_at && start_at && end_at <= start_at | |
errors.add(:end_at, 'must be later than start date') | |
end | |
end | |
def start_at_does_not_overlap_existing_competition | |
if discipline_class_id && !start_at.blank? | |
if other_competitions_in_class.running_at(start_at).exists? | |
errors.add(:start_at, 'overlaps existing competition') | |
end | |
end | |
end | |
def end_at_does_not_overlap_existing_competition | |
if discipline_class_id && !start_at.blank? && !end_at.blank? | |
if other_competitions_in_class.running_between(start_at, end_at).exists? | |
errors.add(:end_at, 'overlaps existing competition') | |
end | |
end | |
end | |
def other_competitions_in_class | |
Competition. | |
where(discipline_class_id: discipline_class_id). | |
where.not(id: model.id) | |
end | |
end | |
def process(params) | |
validate(params[:competition]) do |f| | |
f.save | |
end | |
end | |
end | |
class Update < Create | |
action :update | |
contract do | |
collection :moves, inherit: true, skip_if: :skip_move? do | |
property :id, writeable: false | |
property :_destroy, virtual: true | |
end | |
private | |
def skip_move?(fragment, options) | |
return true if fragment['_destroy'] == '1' && moves.delete(moves.find { |m| m.id.to_s == fragment['id'] }) | |
return true if fragment['name'].blank? | |
end | |
end | |
def process(params) | |
validate(params[:competition]) do |f| | |
f.save | |
end | |
end | |
end | |
class Destroy < Trailblazer::Operation | |
include CRUD | |
model Competition, :update | |
def process(params) | |
model.destroy | |
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
require 'test_helper' | |
class CompetitionCrudTest < ActiveSupport::TestCase | |
# TODO: Replace fixtures with operations + factories | |
let(:discipline_class) { discipline_classes(:reining) } | |
let(:judge) { users(:judge) } | |
let(:create_params) {{ | |
competition: { | |
discipline_class_id: discipline_class.id, | |
judge_id: judge.id, | |
name: 'New Competition', | |
description: 'I am a description.', | |
start_at: Time.zone.parse('2015-06-12 00:00:00'), | |
end_at: Time.zone.parse('2015-06-19 23:59:59'), | |
example_video: { | |
'video_file' => '' | |
}, | |
moves: [ | |
{ 'position' => 3, 'name' => 'Last' }, | |
{ 'position' => 1, 'name' => 'First' }, | |
{ 'position' => 2, 'name' => 'Middle' } | |
] | |
} | |
}} | |
describe 'Create' do | |
it 'persists valid' do | |
res, op = Competition::Create.run(create_params) | |
res.must_equal true | |
model = op.model | |
model.must_be :persisted? | |
model.discipline_class.must_equal discipline_class | |
model.judge.must_equal judge | |
model.name.must_equal 'New Competition' | |
model.description.must_equal 'I am a description.' | |
model.start_at.must_equal Time.zone.parse('2015-06-12 00:00:00') | |
model.end_at.must_equal Time.zone.parse('2015-06-19 23:59:59') | |
model.example_video.wont_be_nil | |
model.moves.count.must_equal 3 | |
model.moves.pluck(:name).must_equal ['First', 'Middle', 'Last'] | |
end | |
it 'validates attribute presence' do | |
missing_attributes = create_params | |
missing_attributes[:competition][:discipline_class_id] = '' | |
missing_attributes[:competition][:judge_id] = '' | |
missing_attributes[:competition][:name] = '' | |
missing_attributes[:competition][:start_at] = '' | |
missing_attributes[:competition][:end_at] = '' | |
missing_attributes[:competition][:moves].first[:name] = '' | |
res, op = Competition::Create.run(missing_attributes) | |
op.errors[:discipline_class_id].must_include "can't be blank" | |
op.errors[:judge_id].must_include "can't be blank" | |
op.errors[:name].must_include "can't be blank" | |
op.errors[:start_at].must_include "can't be blank" | |
op.errors[:end_at].must_include "can't be blank" | |
op.errors[:'moves.name'].must_include "can't be blank" | |
end | |
it 'validates it ends after it starts' do | |
invalid_date = create_params | |
invalid_date[:competition][:end_at] = Time.zone.parse('2015-06-11 23:59:59') | |
res, op = Competition::Create.run(invalid_date) | |
op.errors[:end_at].must_equal ['must be later than start date'] | |
end | |
describe 'overlap validations' do | |
let(:params) {{ | |
discipline_class_id: discipline_class.id, | |
judge_id: judge.id | |
}} | |
it 'checks no others running on start date' do | |
existing_params = params.merge( | |
start_at: Time.zone.parse('2015-06-01 00:00'), | |
end_at: Time.zone.parse('2015-06-14 23:59') | |
) | |
Competition::Create[competition: FactoryGirl.build(:competition, existing_params)] | |
new_params = params.merge( | |
start_at: Time.zone.parse('2015-06-02 00:00'), | |
end_at: Time.zone.parse('2015-06-15 23:59') | |
) | |
res, op = Competition::Create.run(competition: FactoryGirl.build(:competition, new_params)) | |
op.errors[:start_at].must_equal ['overlaps existing competition'] | |
end | |
it 'checks no others running on end date' do | |
existing_params = params.merge( | |
start_at: Time.zone.parse('2015-06-14 00:00'), | |
end_at: Time.zone.parse('2015-06-21 23:59') | |
) | |
Competition::Create[competition: FactoryGirl.build(:competition, existing_params)] | |
new_params = params.merge( | |
start_at: Time.zone.parse('2015-06-01 00:00'), | |
end_at: Time.zone.parse('2015-06-15 23:59') | |
) | |
res, op = Competition::Create.run(competition: FactoryGirl.build(:competition, new_params)) | |
op.errors[:end_at].must_equal ['overlaps existing competition'] | |
end | |
it 'checks no others running between start and end date' do | |
existing_params = params.merge( | |
start_at: Time.zone.parse('2015-06-02 00:00'), | |
end_at: Time.zone.parse('2015-06-13 23:59') | |
) | |
Competition::Create[competition: FactoryGirl.build(:competition, existing_params)] | |
new_params = params.merge( | |
start_at: Time.zone.parse('2015-06-01 00:00'), | |
end_at: Time.zone.parse('2015-06-14 23:59') | |
) | |
res, op = Competition::Create.run(competition: FactoryGirl.build(:competition, new_params)) | |
op.errors[:end_at].must_equal ['overlaps existing competition'] | |
end | |
it 'allows overlapping competitions across discipline classes' do | |
existing_params = params.merge( | |
# TODO: replace fixture with operation/factory | |
discipline_class_id: discipline_classes(:finished).id, | |
start_at: Time.zone.parse('2014-06-02 00:00'), | |
end_at: Time.zone.parse('2014-06-13 23:59') | |
) | |
Competition::Create[competition: FactoryGirl.build(:competition, existing_params)] | |
new_params = params.merge( | |
start_at: Time.zone.parse('2014-06-01 00:00'), | |
end_at: Time.zone.parse('2014-06-14 23:59') | |
) | |
res, op = Competition::Create.run(competition: FactoryGirl.build(:competition, new_params)) | |
res.must_equal true | |
end | |
end | |
end | |
describe 'Update' do | |
let(:competition) { Competition::Create.(create_params).model } | |
let(:update_params) {{ | |
id: competition.id, | |
competition: { | |
name: 'Updated Competition', | |
description: 'I am a new description.', | |
start_at: Time.zone.parse('2015-07-01 00:00:00'), | |
end_at: Time.zone.parse('2015-07-30 23:59:59'), | |
example_video: { | |
'video_file' => '' | |
}, | |
'moves' => [ | |
{ 'id' => competition.moves.all[0].id.to_s, 'position' => 1, 'name' => 'First Changed' }, | |
{ 'id' => competition.moves.all[1].id.to_s, 'position' => 2, 'name' => 'Middle Changed' }, | |
{ 'id' => competition.moves.all[2].id.to_s, 'position' => 3, 'name' => 'Last Changed' } | |
] | |
} | |
}} | |
it 'persists valid' do | |
res, op = Competition::Update.run(update_params) | |
res.must_equal true | |
model = op.model | |
model.reload # needed to ensure we get updated move records | |
model.must_be :persisted? | |
model.discipline_class.must_equal discipline_class | |
model.judge.must_equal judge | |
model.name.must_equal 'Updated Competition' | |
model.description.must_equal 'I am a new description.' | |
model.start_at.must_equal Time.zone.parse('2015-07-01 00:00:00') | |
model.end_at.must_equal Time.zone.parse('2015-07-30 23:59:59') | |
model.example_video.wont_be_nil | |
model.moves.count.must_equal 3 | |
model.moves.pluck(:name).must_equal ['First Changed', 'Middle Changed', 'Last Changed'] | |
end | |
it 'allows move deletion' do | |
deleted_move = update_params | |
deleted_move[:competition]['moves'][0]['_destroy'] = '1' | |
original_moves = competition.moves.all.to_a | |
original_move_ids = competition.moves.pluck(:id) | |
res, op = Competition::Update.run(deleted_move) | |
res.must_equal true | |
model = op.model | |
model.reload # needed to ensure we get updated move records | |
pp original_moves | |
# [#<Move:0x007f9aca9992a8 | |
# id: 908005741, | |
# name: "First", | |
# position: 1, | |
# competition_id: 802290397, | |
# created_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00, | |
# updated_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00>, | |
# #<Move:0x007f9aca998df8 | |
# id: 908005742, | |
# name: "Middle", | |
# position: 2, | |
# competition_id: 802290397, | |
# created_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00, | |
# updated_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00>, | |
# #<Move:0x007f9aca998b00 | |
# id: 908005740, | |
# name: "Last", | |
# position: 3, | |
# competition_id: 802290397, | |
# created_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00, | |
# updated_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00> | |
# ] | |
pp model.moves.all.to_a | |
# [#<Move:0x007f9ad3b6ca68 | |
# id: 908005742, | |
# name: "Middle", | |
# position: 2, | |
# competition_id: 802290397, | |
# created_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00, | |
# updated_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00>, | |
# #<Move:0x007f9ad3b6c900 | |
# id: 908005740, | |
# name: "Middle Changed", | |
# position: 2, | |
# competition_id: 802290397, | |
# created_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00, | |
# updated_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00>, | |
# #<Move:0x007f9ad3b6c798 | |
# id: 908005743, | |
# name: "Last Changed", | |
# position: 3, | |
# competition_id: 802290397, | |
# created_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00, | |
# updated_at: Sat, 13 Jun 2015 12:17:03 UTC +00:00> | |
# ] | |
model.moves.pluck(:id).wont_equal original_move_ids | |
model.moves.count.must_equal 2 | |
model.moves.pluck(:name).must_equal ['Last Changed', 'Middle Changed'] | |
end | |
end | |
describe 'Destroy' do | |
it 'destroys correct record' do | |
competition = Competition::Create.(create_params).model | |
assert_difference 'Competition.count', -1 do | |
Competition::Destroy.(id: competition.id) | |
end | |
Competition.exists?(id: competition.id).must_equal false | |
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
class Move < ActiveRecord::Base | |
belongs_to :competition | |
has_many :scorecard_moves | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment