radiant-race_results-extension 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +8 -0
- data/Rakefile +137 -0
- data/app/controllers/admin/race_clubs_controller.rb +15 -0
- data/app/controllers/admin/race_competitors_controller.rb +15 -0
- data/app/controllers/admin/race_instances_controller.rb +17 -0
- data/app/controllers/admin/races_controller.rb +35 -0
- data/app/controllers/race_instances_controller.rb +23 -0
- data/app/controllers/race_performances_controller.rb +16 -0
- data/app/controllers/races_controller.rb +13 -0
- data/app/helpers/races_helper.rb +38 -0
- data/app/models/race.rb +64 -0
- data/app/models/race_category.rb +114 -0
- data/app/models/race_checkpoint.rb +31 -0
- data/app/models/race_checkpoint_time.rb +64 -0
- data/app/models/race_club.rb +48 -0
- data/app/models/race_club_alias.rb +9 -0
- data/app/models/race_competitor.rb +20 -0
- data/app/models/race_instance.rb +178 -0
- data/app/models/race_page.rb +69 -0
- data/app/models/race_performance.rb +175 -0
- data/app/models/race_performance_status.rb +58 -0
- data/app/models/race_record.rb +27 -0
- data/app/views/admin/race_checkpoints/_checkpoint.html.haml +19 -0
- data/app/views/admin/race_club_aliases/_race_club_alias.html.haml +10 -0
- data/app/views/admin/race_clubs/_club.html.haml +19 -0
- data/app/views/admin/race_clubs/_form.html.haml +21 -0
- data/app/views/admin/race_clubs/edit.html.haml +20 -0
- data/app/views/admin/race_clubs/index.html.haml +27 -0
- data/app/views/admin/race_competitors/_competitor.html.haml +14 -0
- data/app/views/admin/race_competitors/_form.html.haml +21 -0
- data/app/views/admin/race_competitors/edit.html.haml +20 -0
- data/app/views/admin/race_competitors/index.html.haml +27 -0
- data/app/views/admin/race_instances/_form.html.haml +98 -0
- data/app/views/admin/race_instances/edit.html.haml +17 -0
- data/app/views/admin/race_instances/new.html.haml +18 -0
- data/app/views/admin/race_records/_record.html.haml +20 -0
- data/app/views/admin/races/_form.html.haml +82 -0
- data/app/views/admin/races/_race.html.haml +36 -0
- data/app/views/admin/races/edit.html.haml +19 -0
- data/app/views/admin/races/index.html.haml +26 -0
- data/app/views/admin/races/new.html.haml +19 -0
- data/app/views/race_categories/show.html.haml +36 -0
- data/app/views/race_clubs/show.html.haml +25 -0
- data/app/views/race_instances/index.html.haml +14 -0
- data/app/views/race_instances/show.html.haml +52 -0
- data/app/views/race_performances/_headings.html.haml +4 -0
- data/app/views/race_performances/_performance.html.haml +21 -0
- data/app/views/races/index.html.haml +38 -0
- data/app/views/races/show.html.haml +81 -0
- data/cucumber.yml +1 -0
- data/db/migrate/20091116130836_race_data.rb +128 -0
- data/db/migrate/20091124095157_competitor_details.rb +9 -0
- data/db/migrate/20091126110634_club_aliases.rb +17 -0
- data/db/migrate/20091223094002_instance_details.rb +11 -0
- data/db/migrate/20091224100524_race_distance.rb +11 -0
- data/db/migrate/20091224100734_race_records.rb +19 -0
- data/db/migrate/20091224105637_category_details.rb +13 -0
- data/db/migrate/20091224115909_filters.rb +11 -0
- data/db/migrate/20091228122837_record_holder.rb +11 -0
- data/db/migrate/20100106104850_remember_calculations.rb +9 -0
- data/db/migrate/20100426104801_race_attachments.rb +11 -0
- data/features/support/env.rb +16 -0
- data/features/support/paths.rb +14 -0
- data/lib/duration_extensions.rb +61 -0
- data/lib/race_results/admin_ui.rb +76 -0
- data/lib/race_results/race_tags.rb +722 -0
- data/lib/tasks/race_results_extension_tasks.rake +28 -0
- data/public/images/admin/calendar_down.png +0 -0
- data/public/images/admin/new-race.png +0 -0
- data/public/javascripts/admin/races.js +29 -0
- data/public/stylesheets/sass/admin/races.sass +131 -0
- data/race_results_extension.rb +38 -0
- data/spec/datasets/competitors_dataset.rb +81 -0
- data/spec/datasets/race_sites_dataset.rb +8 -0
- data/spec/datasets/races_dataset.rb +179 -0
- data/spec/files/dunnerdale_2009.csv +277 -0
- data/spec/files/long_duddon_2008.csv +230 -0
- data/spec/lib/duration_spec.rb +50 -0
- data/spec/models/race_category_spec.rb +65 -0
- data/spec/models/race_club_spec.rb +32 -0
- data/spec/models/race_instance_spec.rb +104 -0
- data/spec/models/race_performance_spec.rb +7 -0
- data/spec/models/race_performance_status_spec.rb +31 -0
- data/spec/models/race_spec.rb +39 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +36 -0
- data/vendor/plugins/acts_as_list/README +23 -0
- data/vendor/plugins/acts_as_list/init.rb +3 -0
- data/vendor/plugins/acts_as_list/lib/active_record/acts/list.rb +256 -0
- data/vendor/plugins/acts_as_list/test/list_test.rb +332 -0
- metadata +195 -0
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
class RaceInstance < ActiveRecord::Base
|
4
|
+
|
5
|
+
has_site if respond_to? :has_site
|
6
|
+
belongs_to :created_by, :class_name => 'User'
|
7
|
+
belongs_to :updated_by, :class_name => 'User'
|
8
|
+
belongs_to :race
|
9
|
+
has_many :performances, :class_name => 'RacePerformance'
|
10
|
+
has_many :competitors, :through => :performances, :source => :race_competitor
|
11
|
+
has_many :categories, :through => :performances, :source => :race_category, :uniq => true
|
12
|
+
|
13
|
+
has_many :finishers, :class_name => 'RacePerformance', :conditions => "race_performances.status_id >= 100"
|
14
|
+
has_many :non_finishers, :class_name => 'RacePerformance', :conditions => "race_performances.status_id < 100"
|
15
|
+
has_many :successful_competitors, :through => :finishers, :source => :race_competitor
|
16
|
+
has_many :unsuccessful_competitors, :through => :non_finishers, :source => :race_competitor
|
17
|
+
|
18
|
+
object_id_attr :filter, TextFilter
|
19
|
+
has_attached_file :results,
|
20
|
+
:url => Radiant::Config["race_results.url"] ? Radiant::Config["race_results.url"] : "/:class/:id/:basename:no_original_style.:extension",
|
21
|
+
:path => Radiant::Config["race_results.path"] ? Radiant::Config["race_results.path"] : ":rails_root/public/:class/:id/:basename:no_original_style.:extension"
|
22
|
+
|
23
|
+
after_save :process_results_file
|
24
|
+
|
25
|
+
validates_presence_of :name, :slug, :race
|
26
|
+
validates_uniqueness_of :slug, :scope => :race_id
|
27
|
+
validates_length_of :slug, :maximum => 100, :message => '{{count}}-character limit'
|
28
|
+
validates_format_of :slug, :with => %r{^([-_.A-Za-z0-9]*|)$}, :message => 'not URL-friendly'
|
29
|
+
default_scope :order => 'started_at DESC'
|
30
|
+
|
31
|
+
named_scope :with_results, {:conditions => "results_file_name IS NOT NULL"}
|
32
|
+
named_scope :without_results, {:conditions => "results_file_name IS NULL"}
|
33
|
+
named_scope :past, {:conditions => ["started_at <= :when", {:when => Time.now}]}
|
34
|
+
named_scope :future, {:conditions => ["started_at > :when", {:when => Time.now}], :order => "started_at ASC"}
|
35
|
+
|
36
|
+
def to_param
|
37
|
+
slug
|
38
|
+
end
|
39
|
+
|
40
|
+
def full_name
|
41
|
+
"#{race.name} #{name}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def path
|
45
|
+
"#{race.slug}/#{slug}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def nice_start_time
|
49
|
+
return unless started_at
|
50
|
+
if started_at.min == 0
|
51
|
+
started_at.to_datetime.strftime("%-1I%p").downcase
|
52
|
+
else
|
53
|
+
started_at.to_datetime.strftime("%-1I:%M%p").downcase
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def nice_start_date
|
58
|
+
started_at.to_datetime.strftime("%B %e %Y") if started_at
|
59
|
+
end
|
60
|
+
|
61
|
+
def past?
|
62
|
+
started_at <= Time.now
|
63
|
+
end
|
64
|
+
|
65
|
+
def future?
|
66
|
+
started_at > Time.now
|
67
|
+
end
|
68
|
+
|
69
|
+
def has_results?
|
70
|
+
performances.any?
|
71
|
+
end
|
72
|
+
|
73
|
+
def checkpoints
|
74
|
+
# we will need to accommodate races whose checkpoints are different with each instance, but for now:
|
75
|
+
race.checkpoints
|
76
|
+
end
|
77
|
+
|
78
|
+
def performance_by(competitor)
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
def performance_at(position)
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
def winning_performance(category=nil)
|
87
|
+
if category
|
88
|
+
category = RaceCategory.find_by_name(category) unless category.is_a? RaceCategory
|
89
|
+
performances.completed.eligible_for_category(category).first if category_present?(category)
|
90
|
+
else
|
91
|
+
performances.completed.first
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def winner(category=nil)
|
96
|
+
if perf = winning_performance(category)
|
97
|
+
perf.competitor
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def top(count=20)
|
102
|
+
performances.top(count)
|
103
|
+
end
|
104
|
+
|
105
|
+
def category_top(category, count=5)
|
106
|
+
performances.eligible_for_category(category).top(count) if category_present?(category)
|
107
|
+
end
|
108
|
+
|
109
|
+
def category_present?(category)
|
110
|
+
categories.include?(category)
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def process_results_file
|
116
|
+
if csv_data = read_results_file
|
117
|
+
headers = csv_data.shift.map(&:to_s)
|
118
|
+
race_data = csv_data.map {|row| row.map {|cell| cell.to_s } }.map {|row| Hash[*headers.zip(row).flatten] } # build AoA and then hash the second level
|
119
|
+
RaceInstance.transaction do
|
120
|
+
performances.destroy_all
|
121
|
+
race_data.each do |line|
|
122
|
+
runner = normalize_fields(line)
|
123
|
+
next if runner['name'].blank?
|
124
|
+
|
125
|
+
runner['elapsed_time'] ||= "0"
|
126
|
+
club = RaceClub.find_or_create_by_any_name(runner.delete('club')) unless runner['club'].blank?
|
127
|
+
competitor = RaceCompetitor.find_or_create_by_name_and_club(runner.delete('name'), club)
|
128
|
+
category = RaceCategory.find_or_create_by_normalized_name(runner.delete('category'))
|
129
|
+
competitor.update_attribute(:gender, category.gender) unless competitor.gender
|
130
|
+
status = RacePerformanceStatus.from_time(runner['elapsed_time'])
|
131
|
+
performance = self.performances.create!({
|
132
|
+
:position => runner.delete('position'),
|
133
|
+
:competitor => competitor,
|
134
|
+
:category => category,
|
135
|
+
:elapsed_time => runner.delete('elapsed_time'),
|
136
|
+
:status_id => status.id
|
137
|
+
})
|
138
|
+
|
139
|
+
headers.each do |key|
|
140
|
+
value = runner[normalize(key)]
|
141
|
+
if value && value.looks_like_duration? && cp = race.checkpoints.find_by_name(key)
|
142
|
+
cp.times.create(:race_performance_id => performance.id, :elapsed_time => value)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def read_results_file
|
151
|
+
CSV.read(results.path)
|
152
|
+
rescue
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
|
156
|
+
@@field_aliases = {
|
157
|
+
'cat' => 'category',
|
158
|
+
'time' => 'elapsed_time',
|
159
|
+
'finish' => 'elapsed_time',
|
160
|
+
'pos' => 'position'
|
161
|
+
}
|
162
|
+
|
163
|
+
def normalize_fields(line)
|
164
|
+
line.keys.each do |key|
|
165
|
+
line[normalize(key)] = line.delete(key)
|
166
|
+
end
|
167
|
+
line
|
168
|
+
end
|
169
|
+
|
170
|
+
def normalize(value)
|
171
|
+
squashed = value.strip.downcase
|
172
|
+
squashed.gsub!(/\s+/, "_")
|
173
|
+
squashed.gsub!(/\W+/, "")
|
174
|
+
@@field_aliases[squashed] || squashed
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class RacePage < Page
|
2
|
+
include WillPaginate::ViewHelpers
|
3
|
+
|
4
|
+
description %{ Takes race and race instance names in child position or as parameters and populates the necessary race objects. }
|
5
|
+
|
6
|
+
attr_accessor :race, :race_instance
|
7
|
+
|
8
|
+
def self.sphinx_indexes
|
9
|
+
[]
|
10
|
+
end
|
11
|
+
|
12
|
+
def cache?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_by_url(url, live = true, clean = false)
|
17
|
+
url = clean_url(url) if clean
|
18
|
+
my_url = self.url
|
19
|
+
return false unless url =~ /^#{Regexp.quote(my_url)}(.*)/
|
20
|
+
race_slug, instance_slug, subset, id = $1.split('/')
|
21
|
+
if race_slug && @race = Race.find_by_slug(race_slug)
|
22
|
+
if instance_slug && @race_instance = @race.instances.find_by_slug(instance_slug)
|
23
|
+
if subset && id && %w{club cat p}.include?(subset)
|
24
|
+
case subset
|
25
|
+
when "club"
|
26
|
+
@club = RaceClub.find(id)
|
27
|
+
@template = 'race_clubs/show'
|
28
|
+
when "cat"
|
29
|
+
@category = RaceCategory.find(id)
|
30
|
+
@template = 'race_categories/show'
|
31
|
+
when 'p'
|
32
|
+
@performance = RacePerformance.find(id)
|
33
|
+
@template = 'race_performances/show'
|
34
|
+
end
|
35
|
+
else
|
36
|
+
@template = 'race_instances/show'
|
37
|
+
end
|
38
|
+
else
|
39
|
+
@template = 'races/show'
|
40
|
+
end
|
41
|
+
end
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def pagination
|
46
|
+
p = request.params[:page]
|
47
|
+
p = 1 if p.blank? || p == 0
|
48
|
+
return {
|
49
|
+
:page => request.params[:page] || 1,
|
50
|
+
:per_page => Radiant::Config['race_results.per_page'] || 100
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def render
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def title
|
59
|
+
if race_instance
|
60
|
+
race_instance.full_name
|
61
|
+
elsif race
|
62
|
+
race.name
|
63
|
+
else
|
64
|
+
read_attribute(:title)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
class RacePerformance < ActiveRecord::Base
|
2
|
+
|
3
|
+
has_site if respond_to? :has_site
|
4
|
+
belongs_to :created_by, :class_name => 'User'
|
5
|
+
belongs_to :updated_by, :class_name => 'User'
|
6
|
+
belongs_to :race_instance
|
7
|
+
belongs_to :race_competitor
|
8
|
+
belongs_to :race_category
|
9
|
+
has_many :checkpoint_times, :class_name => 'RaceCheckpointTime'
|
10
|
+
|
11
|
+
delegate :name, :reader, :club, :to => :competitor
|
12
|
+
|
13
|
+
before_validation_on_create :times_from_checkpoints
|
14
|
+
validates_presence_of :race_competitor_id, :race_instance_id
|
15
|
+
|
16
|
+
default_scope :order => :elapsed_time
|
17
|
+
|
18
|
+
# has_many through doesn't work with a foreign_key setting, so we do the readable names here instead
|
19
|
+
alias :competitor :race_competitor
|
20
|
+
alias :competitor= :race_competitor=
|
21
|
+
alias :category :race_category
|
22
|
+
alias :category= :race_category=
|
23
|
+
|
24
|
+
named_scope :top, lambda {|count|
|
25
|
+
{
|
26
|
+
:order => 'elapsed_time DESC',
|
27
|
+
:limit => count
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
named_scope :by, lambda {|race_competitor|
|
32
|
+
{
|
33
|
+
:conditions => ['race_competitor_id = ?', race_competitor.id]
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
named_scope :in, lambda {|race_instance|
|
38
|
+
{
|
39
|
+
:conditions => ['race_instance_id = ?', race_instance.id]
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
named_scope :in_race, lambda {|race|
|
44
|
+
{
|
45
|
+
:conditions => ['race_instance_id in (?)', race.instances.map(&:id).join(',')]
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
named_scope :in_category, lambda {|category|
|
50
|
+
category = RaceCategory.find_by_name(category) unless category.is_a? RaceCategory
|
51
|
+
{
|
52
|
+
:conditions => ['race_category_id = ?', category.id]
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
named_scope :eligible_for_category, lambda {|category|
|
57
|
+
category = RaceCategory.find_by_name(category) unless category.is_a? RaceCategory
|
58
|
+
eligible_categories = RaceCategory.within(category)
|
59
|
+
{
|
60
|
+
:conditions => ["race_category_id in (#{eligible_categories.map{'?'}.join(',')})", *eligible_categories.map(&:id)]
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
named_scope :by_members_of, lambda { |club|
|
65
|
+
club = RaceClub.find_by_name(club) unless club.is_a? RaceClub
|
66
|
+
{
|
67
|
+
:select => 'race_performances.*',
|
68
|
+
:joins => "LEFT JOIN race_competitors AS competitors ON race_performances.race_competitor_id = competitors.id",
|
69
|
+
:conditions => ["competitors.race_club_id = ?", club.id]
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
named_scope :quicker_than, lambda { |seconds|
|
74
|
+
{
|
75
|
+
:conditions => ["elapsed_time IS NOT NULL AND elapsed_time > 0 AND elapsed_time < ?", seconds]
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
named_scope :completed, {
|
80
|
+
:conditions => 'status_id >= 100'
|
81
|
+
}
|
82
|
+
|
83
|
+
named_scope :incomplete, {
|
84
|
+
:conditions => 'status_id < 100'
|
85
|
+
}
|
86
|
+
|
87
|
+
def start
|
88
|
+
started_at || race_instance.started_at || checkpoint_times.first.elapsed_time
|
89
|
+
end
|
90
|
+
|
91
|
+
def finish
|
92
|
+
finished_at || start + elapsed_time
|
93
|
+
end
|
94
|
+
|
95
|
+
def position
|
96
|
+
read_attribute(:position) || calculate_position
|
97
|
+
end
|
98
|
+
|
99
|
+
def calculate_position
|
100
|
+
update_attribute(:position, race_instance.performances.quicker_than(time_in_seconds).count + 1)
|
101
|
+
position
|
102
|
+
end
|
103
|
+
|
104
|
+
def prizes
|
105
|
+
read_attribute(:prizes) || calculate_prizes
|
106
|
+
end
|
107
|
+
|
108
|
+
def calculate_prizes
|
109
|
+
prizelist = []
|
110
|
+
positions.each do |cat,pos|
|
111
|
+
prizelist << "#{pos.ordinalize} #{cat}" if pos <= 3
|
112
|
+
end
|
113
|
+
update_attribute(:prizes, prizelist.join(', '))
|
114
|
+
prizes
|
115
|
+
end
|
116
|
+
|
117
|
+
def positions
|
118
|
+
race_instance.categories.containing(category).inject({}) { |h, cat| h.merge( { cat.name => position_in(cat) } ) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def position_in(cat)
|
122
|
+
race_instance.performances.eligible_for_category(cat).quicker_than(time_in_seconds).count + 1
|
123
|
+
end
|
124
|
+
|
125
|
+
def time_in_seconds
|
126
|
+
read_attribute(:elapsed_time)
|
127
|
+
end
|
128
|
+
|
129
|
+
def elapsed_time
|
130
|
+
if s = time_in_seconds
|
131
|
+
s.to_timecode
|
132
|
+
else
|
133
|
+
""
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def elapsed_time=(time)
|
138
|
+
write_attribute(:elapsed_time, time.seconds) if time # numbers will pass through unchanged. strings will be timecode-parsed
|
139
|
+
end
|
140
|
+
|
141
|
+
def time_at(checkpoint)
|
142
|
+
checkpoint_times.time_at(checkpoint).first
|
143
|
+
end
|
144
|
+
|
145
|
+
def status
|
146
|
+
RacePerformanceStatus.find(self.status_id)
|
147
|
+
end
|
148
|
+
def status=(value)
|
149
|
+
self.status_id = value.id
|
150
|
+
end
|
151
|
+
def finished?
|
152
|
+
status == RacePerformanceStatus["Finished"]
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
def prized?
|
158
|
+
true if prizes.any?
|
159
|
+
end
|
160
|
+
|
161
|
+
protected
|
162
|
+
|
163
|
+
def times_from_checkpoints
|
164
|
+
# self.started_at ||= checkpoint_times.first.elapsed_time if checkpoint_times.any? && !race_instance.started_at
|
165
|
+
# self.finished_at ||= checkpoint_times.last.elapsed_time if checkpoint_times.any? && !elapsed_time
|
166
|
+
# self.elapsed_time ||= finished_at - started_at
|
167
|
+
end
|
168
|
+
|
169
|
+
def recalculate_positions
|
170
|
+
position(true)
|
171
|
+
prizes(true)
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class RacePerformanceStatus
|
2
|
+
attr_accessor :id, :name, :aliases
|
3
|
+
|
4
|
+
def initialize(options = {})
|
5
|
+
options = options.symbolize_keys
|
6
|
+
@id, @name = options[:id], options[:name]
|
7
|
+
@aliases = (options[:aliases] || []).collect {|name| name.to_s.downcase.intern}
|
8
|
+
end
|
9
|
+
|
10
|
+
def symbol
|
11
|
+
@name.to_s.downcase.intern
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_alias?(value)
|
15
|
+
@aliases.include?(value.to_s.downcase.intern)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
@name
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_i
|
23
|
+
@id
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.[](value="")
|
27
|
+
value = "?" if value.blank? || value == 0
|
28
|
+
@@statuses.find { |status| status.symbol == value.to_s.downcase.intern || status.has_alias?(value) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.find(id=0)
|
32
|
+
@@statuses.find { |status| status.id.to_s == id.to_s }
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.find_all
|
36
|
+
@@statuses.dup
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.from_time(time=nil)
|
40
|
+
time ||= ""
|
41
|
+
if time.seconds > 0 # String.seconds returns 0 for a string that doesn't parse. See DurationExtensions for method.
|
42
|
+
self["Finished"]
|
43
|
+
elsif self[time]
|
44
|
+
self[time]
|
45
|
+
else
|
46
|
+
self["?"]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@@statuses = [
|
51
|
+
RacePerformanceStatus.new(:id => 0, :name => '?', :aliases => ['unknown'] ),
|
52
|
+
RacePerformanceStatus.new(:id => 10, :name => 'Ret', :aliases => ['retired', 'r']),
|
53
|
+
RacePerformanceStatus.new(:id => 20, :name => 'DNF', :aliases => ['did not finish', 'x']),
|
54
|
+
RacePerformanceStatus.new(:id => 30, :name => 'TO', :aliases => ['timed out', 't']),
|
55
|
+
RacePerformanceStatus.new(:id => 50, :name => 'Dsq', :aliases => ['disqualified', 'disq', 'd']),
|
56
|
+
RacePerformanceStatus.new(:id => 100, :name => 'Finished')
|
57
|
+
]
|
58
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class RaceRecord < ActiveRecord::Base
|
2
|
+
|
3
|
+
has_site if respond_to? :has_site
|
4
|
+
belongs_to :created_by, :class_name => 'User'
|
5
|
+
belongs_to :updated_by, :class_name => 'User'
|
6
|
+
belongs_to :race
|
7
|
+
belongs_to :race_competitor
|
8
|
+
belongs_to :race_category
|
9
|
+
|
10
|
+
# these associations are only populated in the relatively rare case where the record is part of one of our result sets
|
11
|
+
belongs_to :race_instance
|
12
|
+
belongs_to :race_performance
|
13
|
+
|
14
|
+
def elapsed_time
|
15
|
+
if s = read_attribute(:elapsed_time)
|
16
|
+
s.to_timecode
|
17
|
+
else
|
18
|
+
""
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def elapsed_time=(time)
|
23
|
+
write_attribute(:elapsed_time, time.seconds) if time # numbers will pass through unchanged. strings will be timecode-parsed
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
- with_value ||= false
|
2
|
+
- cssclass = f.object.new_record? ? 'adding subform' : 'subform'
|
3
|
+
%div{:class => cssclass}
|
4
|
+
.name
|
5
|
+
= f.label :name, "Checkpoint Name"
|
6
|
+
= f.text_field :name, :class => 'textbox'
|
7
|
+
.location
|
8
|
+
= f.label :location, "Location"
|
9
|
+
= f.text_field :location, :class => 'textbox'
|
10
|
+
.value
|
11
|
+
- if with_value
|
12
|
+
= f.label :value, "Value"
|
13
|
+
= f.text_field :value, :class => 'textbox'
|
14
|
+
|
15
|
+
- if f.object.new_record?
|
16
|
+
= link_to_function "cancel", "this.up('.subform').remove()"
|
17
|
+
- else
|
18
|
+
= f.check_box '_delete', { :onclick=>"this.checked ? this.up('.subform').addClassName('deleting') : this.up('.subform').removeClassName('deleting')" , :class => 'record_delete'}
|
19
|
+
= f.label '_delete', 'delete', :class => 'quiet'
|
@@ -0,0 +1,10 @@
|
|
1
|
+
- cssclass = f.object.new_record? ? 'adding subform' : 'subform'
|
2
|
+
%p{:class => cssclass}
|
3
|
+
= f.label :name, "AKA:"
|
4
|
+
= f.text_field :name, :class => 'alias_name'
|
5
|
+
|
6
|
+
- if f.object.new_record?
|
7
|
+
= link_to_function "cancel", "this.up('.subform').remove()"
|
8
|
+
- else
|
9
|
+
= f.check_box '_delete', { :onclick=>"this.checked ? this.up('.subform').addClassName('deleting') : this.up('.subform').removeClassName('deleting')" , :class => 'alias_delete'}
|
10
|
+
= f.label '_delete', 'delete', :class => 'quiet'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
%tr.node.level-1
|
2
|
+
- render_region :tbody do |tbody|
|
3
|
+
- tbody.title_cell do
|
4
|
+
%td.club_name
|
5
|
+
%p
|
6
|
+
= link_to club.name, edit_admin_race_club_url(club)
|
7
|
+
|
8
|
+
- tbody.members_cell do
|
9
|
+
%td.club_members
|
10
|
+
- if club.competitors.any?
|
11
|
+
= club.competitors.count
|
12
|
+
- else
|
13
|
+
\-
|
14
|
+
|
15
|
+
- tbody.aliases_cell do
|
16
|
+
%td.club_aliases
|
17
|
+
- if club.other_names.any?
|
18
|
+
aka
|
19
|
+
= club.other_names.map(&:name).join(', ')
|
@@ -0,0 +1,21 @@
|
|
1
|
+
= render_region :form_top
|
2
|
+
|
3
|
+
- if @race_club.errors.any?
|
4
|
+
%p.haserror
|
5
|
+
Sorry: there were problems:
|
6
|
+
%br
|
7
|
+
= @race_club.errors.full_messages
|
8
|
+
|
9
|
+
- render_region :form do |form|
|
10
|
+
- form.edit_name do
|
11
|
+
%p.title
|
12
|
+
= f.label :name, "Displayed Name"
|
13
|
+
= f.text_field :name, :class => 'textbox', :maxlength => 100
|
14
|
+
|
15
|
+
- form.edit_aliases do
|
16
|
+
%h2
|
17
|
+
Aliases and abbreviations
|
18
|
+
%div#other_names
|
19
|
+
= f.fields_for :other_names do |af|
|
20
|
+
= render :partial => 'admin/race_club_aliases/race_club_alias', :locals => {:f => af}
|
21
|
+
= link_to_new_nested_form "Add an alias", f, :other_names, :partial => 'admin/race_club_aliases/race_club_alias'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
- include_stylesheet 'admin/races'
|
2
|
+
- include_javascript 'admin/races'
|
3
|
+
|
4
|
+
= render_region :top
|
5
|
+
|
6
|
+
- render_region :main do |main|
|
7
|
+
- main.edit_header do
|
8
|
+
%h1
|
9
|
+
Edit Club
|
10
|
+
|
11
|
+
- main.edit_form do
|
12
|
+
- form_for :race_club, @race_club, :url => admin_race_club_path(@race_club), :html => { :method => "put" } do |f|
|
13
|
+
.form-area
|
14
|
+
= render :partial => "form", :locals => { :f => f }
|
15
|
+
|
16
|
+
%p.buttons
|
17
|
+
= save_model_button(@race_club)
|
18
|
+
= save_model_and_continue_editing_button(@race_club)
|
19
|
+
or
|
20
|
+
= link_to "Cancel", admin_race_clubs_path
|
@@ -0,0 +1,27 @@
|
|
1
|
+
- include_stylesheet 'admin/races'
|
2
|
+
|
3
|
+
#clubs_table.outset
|
4
|
+
%table#race_clubs.index{:cellspacing=>"0", :border=>"0", :cellpadding=>"0"}
|
5
|
+
%thead
|
6
|
+
%tr
|
7
|
+
- render_region :thead do |thead|
|
8
|
+
- thead.title_header do
|
9
|
+
%th.club
|
10
|
+
Name
|
11
|
+
- thead.members_header do
|
12
|
+
%th.members
|
13
|
+
Members
|
14
|
+
- thead.aliases_header do
|
15
|
+
%th.members
|
16
|
+
Aliases
|
17
|
+
|
18
|
+
%tbody
|
19
|
+
- @race_clubs.each do |club|
|
20
|
+
= render :partial => 'club', :object => club
|
21
|
+
|
22
|
+
- render_region :bottom do |bottom|
|
23
|
+
- bottom.buttons do
|
24
|
+
#actions
|
25
|
+
= will_paginate @race_clubs
|
26
|
+
%ul
|
27
|
+
%li= link_to image('plus') + " " + "new club", new_admin_race_club_url
|
@@ -0,0 +1,14 @@
|
|
1
|
+
%tr.node.level-1
|
2
|
+
- render_region :tbody do |tbody|
|
3
|
+
- tbody.title_cell do
|
4
|
+
%td.competitor_name
|
5
|
+
%p
|
6
|
+
= link_to competitor.name, edit_admin_race_competitor_url(competitor)
|
7
|
+
|
8
|
+
- tbody.club_cell do
|
9
|
+
%td.competitor_club
|
10
|
+
= competitor.club ? competitor.club.name : "unattached"
|
11
|
+
|
12
|
+
- tbody.races_cell do
|
13
|
+
%td.competitor_races
|
14
|
+
= competitor.performances.count
|
@@ -0,0 +1,21 @@
|
|
1
|
+
= render_region :form_top
|
2
|
+
|
3
|
+
- if @race_club.errors.any?
|
4
|
+
%p.haserror
|
5
|
+
Sorry: there were problems:
|
6
|
+
%br
|
7
|
+
= @race_club.errors.full_messages
|
8
|
+
|
9
|
+
- render_region :form do |form|
|
10
|
+
- form.edit_name do
|
11
|
+
%p.title
|
12
|
+
= f.label :name, "Displayed Name"
|
13
|
+
= f.text_field :name, :class => 'textbox', :maxlength => 100
|
14
|
+
|
15
|
+
- form.edit_aliases do
|
16
|
+
%h2
|
17
|
+
Aliases and abbreviations
|
18
|
+
%div#other_names
|
19
|
+
= f.fields_for :other_names do |af|
|
20
|
+
= render :partial => 'admin/race_club_aliases/race_club_alias', :locals => {:f => af}
|
21
|
+
= link_to_new_nested_form "Add an alias", f, :other_names, :partial => 'admin/race_club_aliases/race_club_alias'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
- include_stylesheet 'admin/races'
|
2
|
+
- include_javascript 'admin/races'
|
3
|
+
|
4
|
+
= render_region :top
|
5
|
+
|
6
|
+
- render_region :main do |main|
|
7
|
+
- main.edit_header do
|
8
|
+
%h1
|
9
|
+
Edit Club
|
10
|
+
|
11
|
+
- main.edit_form do
|
12
|
+
- form_for :race_club, @race_club, :url => admin_race_club_path(@race_club), :html => { :method => "put" } do |f|
|
13
|
+
.form-area
|
14
|
+
= render :partial => "form", :locals => { :f => f }
|
15
|
+
|
16
|
+
%p.buttons
|
17
|
+
= save_model_button(@race_club)
|
18
|
+
= save_model_and_continue_editing_button(@race_club)
|
19
|
+
or
|
20
|
+
= link_to "Cancel", admin_race_clubs_path
|