frecon 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3695507c98a8055222ee241b01a4811d02f0aa96
4
- data.tar.gz: 152e3d581b3f941762b1cba4b6dfb871408b042d
3
+ metadata.gz: 4e2c810e2a8cf4f910ac9a90d52a449b61285df9
4
+ data.tar.gz: 8a3f9339a0b6539ec355458f8e1b9e7aa3a995da
5
5
  SHA512:
6
- metadata.gz: e8da7107dd168433d62a49c08104e3c4f675543e0837c6c3041b67edcfd78710ea2b9ec3c0608f46ac4fc2c39269c005e4536258bd6a17be324cea4f6502757b
7
- data.tar.gz: f9811d2b31a5c30fc0597a9357be77122df2d052686857e506a7945b0e85b8635e7626e14b4b1f7312e3774c464508b23c2b6bdc62056609905d35251aa9ca7c
6
+ metadata.gz: 52ce69ae4af6d7137426fd79a6ea39deffdc0d71900a18cbff137ee986a6ff74dfad0aad45284dbc5840629670088e9b1c8c5a50911822382d354a7b843d22ed
7
+ data.tar.gz: 362cea6a6e6110246ba69320b856d9439c52c0c14c659734f61eeb78fadc0f5bbdb25127239b10bd3ffc9646498ca0f5cffc780fba4ccda21db6883b30ad8152
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Dependencies loaded from frecon.gemspec
4
+ gemspec
@@ -0,0 +1,15 @@
1
+ require "yard"
2
+
3
+ YARD::Config.load_plugin('tomdoc')
4
+ YARD::Config.load_plugin('mongoid')
5
+
6
+ namespace :docs do
7
+ YARD::Rake::YardocTask.new :generate do |task|
8
+ task.files = ['lib/**/*.rb', 'bin/**/*']
9
+ end
10
+
11
+ YARD::Rake::YardocTask.new :list_undoc do |task|
12
+ task.files = ['lib/**/*.rb', 'bin/**/*']
13
+ task.stats_options = ['--list-undoc']
14
+ end
15
+ end
@@ -11,10 +11,20 @@ require "mongoid"
11
11
 
12
12
  require "frecon/base"
13
13
 
14
+ require "frecon/controller"
15
+ require "frecon/controllers"
16
+ require "frecon/model"
17
+ require "frecon/models"
18
+ require "frecon/scraper"
19
+ require "frecon/scrapers"
20
+
14
21
  require "frecon/configuration"
15
22
  require "frecon/configuration_file"
23
+ require "frecon/match_number"
24
+ require "frecon/position"
25
+ require "frecon/request_error"
26
+ require "frecon/routes"
27
+
16
28
  require "frecon/database"
17
29
  require "frecon/server"
18
30
  require "frecon/console"
19
-
20
- require "frecon/scrapers"
@@ -7,8 +7,17 @@
7
7
  # license with this program. If not, please see
8
8
  # <http://opensource.org/licenses/MIT>.
9
9
 
10
+ # Public: An extension for the BSON module.
10
11
  module BSON
12
+ # Public: A monkey-patch for the BSON::ObjectId class which introduces an
13
+ # #as_json method.
11
14
  class ObjectId
15
+ # Public: Get produce a JSON representation of this ObjectId.
16
+ #
17
+ # Since we don't want to produce a JSON Object for every ID, this method
18
+ # instead just returns the String _id value within this object.
19
+ #
20
+ # Returns a String containing the value of this ObjectId.
12
21
  def as_json(*args)
13
22
  to_s
14
23
  end
@@ -7,15 +7,23 @@
7
7
  # license with this program. If not, please see
8
8
  # <http://opensource.org/licenses/MIT>.
9
9
 
10
+ # Public: The FReCon API module.
10
11
  module FReCon
11
- VERSION ||= "0.5.0"
12
+ # Public: A String representing the current version of FReCon.
13
+ VERSION = "0.5.2"
12
14
 
13
15
  @environment_variable = :development
14
16
 
17
+ # Public: Returns the current environment.
15
18
  def self.environment
16
19
  @environment_variable
17
20
  end
18
21
 
22
+ # Public: Sets the environment.
23
+ #
24
+ # arg - The new environment.
25
+ #
26
+ # Returns the result from setting the current environment.
19
27
  def self.environment=(arg)
20
28
  @environment_variable = arg
21
29
  end
@@ -10,13 +10,23 @@
10
10
  require "frecon/configuration_file"
11
11
 
12
12
  module FReCon
13
+ # Public: A wrapper to allow the manipulation of configurations.
13
14
  class Configuration < Hash
15
+ # Public: Initialize a Configuration.
16
+ #
17
+ # data - a Hash representing the data.
14
18
  def initialize(data)
15
19
  data.each do |key, value|
16
20
  self[key] = value
17
21
  end
18
22
  end
19
23
 
24
+ # Public: Convert self to a Hash.
25
+ #
26
+ # Recursively converts instances of Configuration within self
27
+ # to hashes by calling this method.
28
+ #
29
+ # Returns a Hash representing self.
20
30
  def to_h
21
31
  hash = {}
22
32
 
@@ -32,6 +42,11 @@ module FReCon
32
42
  hash
33
43
  end
34
44
 
45
+ # Public: Merge with another Configuration.
46
+ #
47
+ # Sets all key-value pairs within Configuration to the same within self.
48
+ #
49
+ # other - A Configuration or Hash to be merged with.
35
50
  def merge(other)
36
51
  case other
37
52
  when Configuration, Hash
@@ -49,11 +64,20 @@ module FReCon
49
64
  end
50
65
  end
51
66
 
67
+ # Public: Constructs a configuration.
68
+ #
69
+ # options - A Hash containing various configurations.
70
+ # :default_configuration - The default configuration's values
71
+ # :system_configuration - The system's configuration's values
72
+ # :user_configuration - The user's configuration's values
73
+ # :argument_configuration - The configuration values from command-line arguments
74
+ #
75
+ # Returns a Configuration generated by merging all of the given
76
+ # configurations together.
52
77
  def self.construct!(default_configuration: ConfigurationFile.default.read,
53
78
  system_configuration: ConfigurationFile.system.read,
54
79
  user_configuration: ConfigurationFile.user.read,
55
80
  argument_configuration: nil)
56
-
57
81
  configuration_hierarchy = [default_configuration, system_configuration, user_configuration, argument_configuration]
58
82
 
59
83
  configuration = Configuration.new({})
@@ -11,13 +11,23 @@ require "yaml"
11
11
  require "frecon/configuration"
12
12
 
13
13
  module FReCon
14
+ # Public: A class to handle configuration files.
14
15
  class ConfigurationFile
16
+ # Public: The filename for the file.
15
17
  attr_accessor :filename
16
18
 
19
+ # Public: Initialize a ConfigurationFile.
20
+ #
21
+ # filename - The name of the file.
17
22
  def initialize(filename)
18
23
  @filename = filename
19
24
  end
20
25
 
26
+ # Public: Read from the file and generate a Configuration
27
+ # from the YAML data therein.
28
+ #
29
+ # Returns a Configuration representing the file's data or nil if it didn't
30
+ # exist.
21
31
  def read
22
32
  begin
23
33
  data = open(@filename, "rb") do |io|
@@ -30,16 +40,22 @@ module FReCon
30
40
  end
31
41
  end
32
42
 
43
+ # Public: Create a new ConfigurationFile corresponding to the default
44
+ # defaults configuration location.
33
45
  def self.default
34
46
  self.new(File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "config", "default.yml")))
35
47
  end
36
48
 
49
+ # Public: Create a new ConfigurationFile corresponding to the default
50
+ # system configuration location.
37
51
  def self.system
38
52
  self.new(File.join("", "etc", "frecon", "config.yml"))
39
53
  end
40
54
 
55
+ # Public: Create a new ConfigurationFile corresponding to the default
56
+ # user configuration location.
41
57
  def self.user
42
- self.new(File.join(Dir.home, "config", "frecon.yml"))
58
+ self.new(File.join(Dir.home, ".config", "frecon.yml"))
43
59
  end
44
60
  end
45
61
  end
@@ -13,11 +13,17 @@ require "frecon/database"
13
13
  require "frecon/server"
14
14
 
15
15
  module FReCon
16
+ # Public: The wrapper system for a pry console.
16
17
  class Console
18
+ # Public: Starts the FReCon console.
19
+ #
20
+ # :configuration - The Configuration to use when starting the console.
21
+ #
22
+ # Returns the result of running pry on FReCon.
17
23
  def self.start(configuration: Configuration.construct!)
18
24
  environment = configuration["frecon"]["console"]["environment"]
19
25
  mongoid = configuration["frecon"]["database"]["mongoid"]
20
- Database.setup(environment: environment, mongoid: mongoid)
26
+ Database.setup(environment, mongoid)
21
27
 
22
28
  require "pry"
23
29
 
@@ -10,30 +10,54 @@
10
10
  require "frecon/base"
11
11
 
12
12
  module FReCon
13
+ # Public: A base class to represent a controller.
13
14
  class Controller
15
+ # Public: Converts the class's name to a Model name.
16
+ #
17
+ # Returns a Symbol that is the Model name.
14
18
  def self.model_name
15
19
  # Removes the namespace "FReCon::" and "Controller" from
16
20
  # the class name, then singularizes the result.
17
21
  self.name.gsub(/FReCon::|Controller\Z/, "").singularize
18
22
  end
19
23
 
24
+ # Public: Converts the class's name to a Model.
25
+ #
26
+ # Returns the Model's class.
20
27
  def self.model
21
28
  # Removes the trailing "Controller" from the class name,
22
29
  # singularizes the result, and turns it into the class.
23
30
  self.name.gsub(/Controller\Z/, "").singularize.constantize
24
31
  end
25
32
 
26
- # Some models have to find themselves in special ways,
27
- # so this can be overridden with those ways.
33
+ # Public: Find a model.
34
+ #
35
+ # params - A Hash containing the parameters. This should contain an
36
+ # 'id' key, which is deleted and used for the find.
37
+ #
38
+ # Returns either the found model value or nil.
28
39
  def self.find_model(params)
29
40
  model.find params.delete("id")
30
41
  end
31
42
 
32
- # The 404 error message.
43
+ # Public: Generate a could-not-find message.
44
+ #
45
+ # value - The value that was tested.
46
+ # attribute - The attribute that was used for the search.
47
+ # model - The model that the search was performed upon.
48
+ #
49
+ # Returns a String containing the error message.
33
50
  def self.could_not_find(value, attribute = "id", model = model_name.downcase)
34
51
  "Could not find #{model} of #{attribute} #{value}!"
35
52
  end
36
53
 
54
+ # Public: Process a JSON request.
55
+ #
56
+ # request - The internal Sinatra request object that is available to
57
+ # request handling.
58
+ #
59
+ # Returns a Hash corresponding to the request's body.
60
+ # Raises a RequestError if the JSON parse fails.
37
61
  def self.process_json_request(request)
38
62
  # Rewind the request body (an IO object)
39
63
  # in case someone else has already played
@@ -49,6 +73,20 @@ module FReCon
49
73
  post_data
50
74
  end
51
75
 
76
+ # Public: Process a creation request (HTTP POST)
77
+ #
78
+ # If `post_data` is an Array, iterates through the array and calls itself
79
+ # with each element within. Otherwise, performs the creation using
80
+ # the attribute key-value pairings within the `post_data`.
81
+ #
82
+ # request - The internal Sinatra request object that is available to
83
+ # request handling.
84
+ # params - The internal params Hash that is available to request
85
+ # handling.
86
+ # post_data - The data that was sent in the request body.
87
+ #
88
+ # Returns an Array, a formatted response that can be passed back through
89
+ # Sinatra's request processing.
52
90
  def self.create(request, params, post_data = nil)
53
91
  post_data ||= process_json_request request
54
92
 
@@ -85,6 +123,19 @@ module FReCon
85
123
  end
86
124
  end
87
125
 
126
+ # Public: Process an update request (HTTP PUT)
127
+ #
128
+ # Processes the JSON request, finds the model, then updates it.
129
+ #
130
+ # request - The internal Sinatra request object that is available to
131
+ # request handling.
132
+ # params - The internal params Hash that is available to request
133
+ # handling.
134
+ # post_data - The data that was sent in the request body.
135
+ #
136
+ # Returns a String with the JSON representation of the model.
137
+ # Raises a RequestError if the request is malformed or if the attributes
138
+ # can't be updated.
88
139
  def self.update(request, params, post_data = nil)
89
140
  raise RequestError.new(400, "Must supply a #{model_name.downcase} id!") unless params[:id]
90
141
 
@@ -101,6 +152,19 @@ module FReCon
101
152
  end
102
153
  end
103
154
 
155
+ # Public: Process a deletion request (HTTP DELETE)
156
+ #
157
+ # Processes the JSON request, finds the model, then deletes it.
158
+ #
159
+ # request - The internal Sinatra request object that is available to
160
+ # request handling.
161
+ # params - The internal params Hash that is available to request
162
+ # handling.
163
+ # post_data - The data that was sent in the request body.
164
+ #
165
+ # Returns 204 if successful.
166
+ # Raises a RequestError if the request is malformed or if the model can't be
167
+ # destroyed
104
168
  def self.delete(params)
105
169
  @model = find_model params
106
170
 
@@ -115,6 +179,20 @@ module FReCon
115
179
  end
116
180
  end
117
181
 
182
+ # Public: Process a show request (HTTP GET) for a specific instance of a
183
+ # model.
184
+ #
185
+ # Processes the JSON request, finds the model, then shows it.
186
+ #
187
+ # request - The internal Sinatra request object that is available to
188
+ # request handling.
189
+ # params - The internal params Hash that is available to request
190
+ # handling.
191
+ # post_data - The data that was sent in the request body.
192
+ #
193
+ # Returns a String with the JSON representation of the model.
194
+ # Raises a RequestError if the request is malformed or if the model can't be
195
+ # found.
118
196
  def self.show(params)
119
197
  @model = find_model params
120
198
 
@@ -125,6 +203,19 @@ module FReCon
125
203
  end
126
204
  end
127
205
 
206
+ # Public: Process an index request (HTTP GET) for all instances of a model.
207
+ #
208
+ # Processes the JSON request, and returns a filtered list of all of the
209
+ # models.
210
+ #
211
+ # request - The internal Sinatra request object that is available to
212
+ # request handling.
213
+ # params - The internal params Hash that is available to request
214
+ # handling.
215
+ # post_data - The data that was sent in the request body.
216
+ #
217
+ # Returns a String with the JSON representation of the list of models.
218
+ # Raises a RequestError if the request is malformed.
128
219
  def self.index(params)
129
220
  if params.empty?
130
221
  @models = model.all
@@ -11,6 +11,7 @@ require "json"
11
11
  require "frecon/models/competition"
12
12
 
13
13
  module FReCon
14
+ # Public: The Competitions controller.
14
15
  class CompetitionsController < Controller
15
16
  end
16
17
  end
@@ -11,7 +11,11 @@ require "json"
11
11
  require "frecon/models"
12
12
 
13
13
  module FReCon
14
+ # Public: The Dump controller.
14
15
  class DumpController
16
+ # Public: Creates a dump.
17
+ #
18
+ # Returns a String containing a dump of the database.
15
19
  def self.full(params)
16
20
  dump = {}
17
21
 
@@ -30,6 +34,14 @@ module FReCon
30
34
  dump.to_json
31
35
  end
32
36
 
37
+ # Public: Converts a Model's name to a dump-compliant name.
38
+ #
39
+ # Examples
40
+ #
41
+ # DumpController.dump_compliant_name(FReCon::Team)
42
+ # # => "teams"
43
+ #
44
+ # Returns a dump-compliant string.
33
45
  def self.dump_compliant_name(model)
34
46
  model.name.gsub(/FReCon::/, "").downcase.pluralize
35
47
  end
@@ -11,6 +11,7 @@ require "json"
11
11
  require "frecon/models/match"
12
12
 
13
13
  module FReCon
14
+ # Public: The Matches controller.
14
15
  class MatchesController < Controller
15
16
  end
16
17
  end
@@ -11,6 +11,7 @@ require "json"
11
11
  require "frecon/models/participation"
12
12
 
13
13
  module FReCon
14
+ # Public: The Participations controller.
14
15
  class ParticipationsController < Controller
15
16
  end
16
17
  end
@@ -11,6 +11,7 @@ require "json"
11
11
  require "frecon/models/record"
12
12
 
13
13
  module FReCon
14
+ # Public: The Records controller.
14
15
  class RecordsController < Controller
15
16
  end
16
17
  end
@@ -11,6 +11,7 @@ require "json"
11
11
  require "frecon/models/robot"
12
12
 
13
13
  module FReCon
14
+ # Public: The Robots controller.
14
15
  class RobotsController < Controller
15
16
  end
16
17
  end
@@ -8,10 +8,10 @@
8
8
  # <http://opensource.org/licenses/MIT>.
9
9
 
10
10
  require "json"
11
- require "frecon/base"
12
11
  require "frecon/models/team"
13
12
 
14
13
  module FReCon
14
+ # Public: The Teams controller.
15
15
  class TeamsController < Controller
16
16
  end
17
17
  end
@@ -18,8 +18,16 @@ require "yaml"
18
18
  require "frecon/models"
19
19
 
20
20
  module FReCon
21
+ # Public: A system to set up the database.
21
22
  class Database
22
- def self.setup(environment: FReCon.environment, mongoid: nil)
23
+ # Public: Set up the database.
24
+ #
25
+ # environment - Symbol containing environment to start the database in.
26
+ # mongoid - Hash containing the configuration for Mongoid. If not
27
+ # present, the lib/frecon/mongoid.yml file is given to
28
+ # Mongoid.load!. If present, the Hash is dumped to a
29
+ # tempfile which is given to Mongoid.load!.
30
+ def self.setup(environment = FReCon.environment, mongoid = nil)
23
31
  if mongoid.is_a?(Hash)
24
32
  mongoid_tempfile = Tempfile.new("FReCon")
25
33
 
@@ -10,13 +10,46 @@
10
10
  require "frecon/base"
11
11
 
12
12
  module FReCon
13
+ # Public: A wrapper to handle converting match numbers and storing them.
13
14
  class MatchNumber
15
+ # Public: All of the possible match types for a MatchNumber to have.
14
16
  POSSIBLE_TYPES = [:practice, :qualification, :quarterfinal, :semifinal, :final]
15
- ELIMINATION_TYPES = [:quarterfinal, :semifinal, :final]
16
17
 
17
- attr_reader :number, :round
18
+ # Public: All of the elimination types for a MatchNumber to have.
19
+ ELIMINATION_TYPES = [:quarterfinal, :semifinal, :final]
18
20
 
19
- # MongoDB compatibility methods.
21
+ # Public: The numerical part of the match number
22
+ #
23
+ # Examples
24
+ #
25
+ # match_number = MatchNumber.new('qm2')
26
+ # match_number.number
27
+ # # => 2
28
+ attr_reader :number
29
+
30
+ # Public: The round part of the match number
31
+ #
32
+ # Examples
33
+ #
34
+ # match_number = MatchNumber.new('qf1m2r3')
35
+ # match_number.round
36
+ # # => 2
37
+ attr_reader :round
38
+
39
+ # Public: The type of the match.
40
+ #
41
+ # Examples
42
+ #
43
+ # match_number = MatchNumber.new('qf1m2r3')
44
+ # match_number.type
45
+ # # => :quarterfinal
46
+ attr_reader :type
47
+
48
+ # Public: Convert a stored match number to a MatchNumber object.
49
+ #
50
+ # object - String representation of a match number (mongoized)
51
+ #
52
+ # Returns MatchNumber parsed from object.
20
53
  def self.demongoize(object)
21
54
  # `object' should *always* be a string (since MatchNumber#mongoize returns a
22
55
  # String which is what is stored in the database)
@@ -25,6 +58,15 @@ module FReCon
25
58
  MatchNumber.new(object)
26
59
  end
27
60
 
61
+ # Public: Convert a MatchNumber object to a storable string representation.
62
+ #
63
+ # object - A MatchNumber, String, or Hash. If MatchNumber, run #mongoize on
64
+ # it. If String, create a new MatchNumber object for it, then run
65
+ # #mongoize on it. If Hash, convert its keys to symbols, then
66
+ # pull out the :alliance and :number keys to generate a
67
+ # MatchNumber.
68
+ #
69
+ # Returns String containing the mongo-ready value for the representation.
28
70
  def self.mongoize(object)
29
71
  case object
30
72
  when MatchNumber
@@ -36,6 +78,16 @@ module FReCon
36
78
  end
37
79
  end
38
80
 
81
+ # Public: Convert a MatchNumber object to a storable string representation
82
+ # for queries.
83
+ #
84
+ # object - A MatchNumber, String, or Hash. If MatchNumber, run #mongoize on
85
+ # it. If String, create a new MatchNumber object for it, then run
86
+ # #mongoize on it. If Hash, convert its keys to symbols, then
87
+ # pull out the :alliance and :number keys to generate a
88
+ # MatchNumber.
89
+ #
90
+ # Returns String containing the mongo-ready value for the representation.
39
91
  def self.evolve(object)
40
92
  case object
41
93
  when MatchNumber
@@ -47,6 +99,9 @@ module FReCon
47
99
  end
48
100
  end
49
101
 
102
+ # Public: Convert to a storable string representation.
103
+ #
104
+ # Returns String representing the MatchNumber's data.
50
105
  def mongoize
51
106
  to_s
52
107
  end
@@ -156,6 +211,9 @@ module FReCon
156
211
  end
157
212
  end
158
213
 
214
+ # Public: Convert to a String.
215
+ #
216
+ # Returns String representing the match number data.
159
217
  def to_s
160
218
  type_string = case @type
161
219
  when :practice
@@ -175,30 +233,38 @@ module FReCon
175
233
  "#{type_string}#{@round}#{match_string}#{replay_string}"
176
234
  end
177
235
 
236
+ # Public: Determine if MatchNumber represents a replay.
178
237
  def replay?
179
238
  !@replay_number.nil? && @replay_number > 0
180
239
  end
181
240
 
241
+ # Public: Determine if MatchNumber represents a practice match.
182
242
  def practice?
183
243
  @type == :practice
184
244
  end
185
245
 
246
+ # Public: Determine if MatchNumber represents a qualification match.
186
247
  def qualification?
187
248
  @type == :qualification
188
249
  end
189
250
 
251
+ # Public: Determine if MatchNumber represents a quarterfinal match.
190
252
  def quarterfinal?
191
253
  @type == :quarterfinal
192
254
  end
193
255
 
256
+ # Public: Determine if MatchNumber represents a semifinal match.
194
257
  def semifinal?
195
258
  @type == :semifinal
196
259
  end
197
260
 
261
+ # Public: Determine if MatchNumber represents a final match.
198
262
  def final?
199
263
  @type == :final
200
264
  end
201
265
 
266
+ # Public: Determine if MatchNumber represents a match of any elimination
267
+ # type.
202
268
  def elimination?
203
269
  ELIMINATION_TYPES.include?(@type)
204
270
  end
@@ -11,46 +11,101 @@ require "mongoid"
11
11
  require "frecon/mongoid/criteria"
12
12
 
13
13
  module FReCon
14
+ # Public: A base class designed to assist with creating MongoDB Models
15
+ # elsewhere in the project.
14
16
  class Model
17
+ # Public: Bootstraps inheritors of this class as working
18
+ # Models, also providing class methods for them to use.
19
+ #
20
+ # child - The class that is inheriting this class.
21
+ #
22
+ # Returns the result of bootstrapping the child.
15
23
  def self.inherited(child)
16
24
  child.class_eval do
25
+ # Include the various Mongoid modules that we want to use.
17
26
  include Mongoid::Document
18
27
  include Mongoid::Timestamps
19
28
  include Mongoid::Attributes::Dynamic
20
29
 
30
+ # Ensure that no invalid relations exist.
21
31
  validate :no_invalid_relations
22
32
 
23
33
  self.class_variable_set(:@@attributes, [])
24
34
 
35
+ # Public: Register a method as a routable relation method.
36
+ #
37
+ # Models can register relation methods that they have defined
38
+ # (e.g. team.robots) as routable methods. The Routes module reads
39
+ # these routable relations, and generates routes for them.
40
+ #
41
+ # method - A Symbol containing the name of the relation method.
42
+ # attribute - A String representing the attribute that the Routes
43
+ # module should route this method under.
44
+ #
45
+ # Examples
46
+ #
47
+ # # (Taken from the Team model)
48
+ # register_routable_relation :matches, "matches"
49
+ #
50
+ # Returns the result of pushing an object to class's attributes
51
+ # class variable.
25
52
  def self.register_routable_relation(method, attribute)
26
53
  self.class_variable_get(:@@attributes) << {method: method, type: :relation, attribute: attribute}
27
54
  end
28
55
 
56
+ # Public: Register a method as a routable attribute method.
57
+ #
58
+ # Models can register attribute methods that they have defined
59
+ # (e.g. team.number) as attribute methods. The Routes module reads
60
+ # these routable attributes, and generates routes for them.
61
+ #
62
+ # method - A Symbol containing the name of the attribute method.
63
+ # attribute - A String representing the attribute that the Routes
64
+ # module should route this method under.
65
+ #
66
+ # Returns the result of pushing an object to class's attributes
67
+ # class variable.
29
68
  def self.register_routable_attribute(method, attribute)
30
69
  self.class_variable_get(:@@attributes) << {method: method, type: :attribute, attribute: attribute}
31
70
  end
32
71
  end
33
72
  end
34
73
 
74
+ # Public: Gets the descendants for the Model class.
75
+ #
76
+ # Returns all of the descendants for the Model class.
35
77
  def self.descendants
36
- # Basically lists all of the models in this database.
37
- ObjectSpace.each_object(Class).select { |possibleChild| possibleChild < self }
78
+ ObjectSpace.each_object(Class).select { |possible_child| possible_child < self }
38
79
  end
39
80
 
81
+ # Public: Converts this Model to its associated Controller.
82
+ #
83
+ # Returns the associated Controller if it exists, else nil.
40
84
  def self.controller
41
85
  (self.name.pluralize + "Controller").constantize
42
86
  end
43
87
 
88
+ # Public: Validate that no invalid relations exist within this Model
44
89
  def no_invalid_relations
45
90
  # Get all of the belongs_to fields (ends with "_id" and not "_id" because that is the id).
46
- attributes.keys.select { |attribute| attribute.end_with?("_id") && attribute != "_id" }.each do |relation|
91
+ attributes.keys.select do |attribute|
92
+ attribute.end_with?("_id") && attribute != "_id"
93
+ end.each do |relation|
47
94
  # Get the model for the belongs_to association.
48
95
  model = "FReCon::".concat(relation.gsub(/_id\Z/, "").capitalize).constantize
49
- errors.add(relation.to_sym, "is invalid") if relation_invalid(model, send(relation))
96
+ errors.add(relation.to_sym, "is invalid") if relation_invalid?(model, send(relation))
50
97
  end
51
98
  end
52
99
 
53
- def relation_invalid(class_constant, id)
100
+ protected
101
+
102
+ # Internal: Determine if a relation is invalid.
103
+ #
104
+ # class_constant - The Model Class to test.
105
+ # id - The ID to check for extance.
106
+ #
107
+ # Returns true if the relation is invalid, false if not.
108
+ def relation_invalid?(class_constant, id)
54
109
  class_constant.find_by(id: id).nil?
55
110
  end
56
111
  end
@@ -10,6 +10,7 @@
10
10
  require "frecon/model"
11
11
 
12
12
  module FReCon
13
+ # Public: The Competition model.
13
14
  class Competition < Model
14
15
  field :location, type: String
15
16
  field :name, type: String
@@ -20,14 +21,17 @@ module FReCon
20
21
  validates :location, :name, presence: true
21
22
  validates :name, uniqueness: true
22
23
 
24
+ # Public: Get this Competition's Matches' Records
23
25
  def records
24
26
  Record.in match_id: matches.map(&:id)
25
27
  end
26
28
 
29
+ # Public: Get this Competition's Participations' Robots
27
30
  def robots
28
31
  Robot.in id: participations.map(&:robot_id)
29
32
  end
30
33
 
34
+ # Public: Get this Competition's Participations' Robots' Teams
31
35
  def teams
32
36
  Team.in id: robots.map(&:team_id)
33
37
  end
@@ -11,6 +11,7 @@ require "frecon/model"
11
11
  require "frecon/match_number"
12
12
 
13
13
  module FReCon
14
+ # Public: The Match model.
14
15
  class Match < Model
15
16
  field :number, type: MatchNumber
16
17
 
@@ -22,14 +23,17 @@ module FReCon
22
23
 
23
24
  validates :number, :competition_id, presence: true
24
25
 
26
+ # Public: Get this Match's Participations
25
27
  def participations
26
28
  Participation.in id: records.map(&:participation_id)
27
29
  end
28
30
 
31
+ # Public: Get this Match's Participations' Robots
29
32
  def robots
30
33
  Robot.in id: participations.map(&:robot_id)
31
34
  end
32
35
 
36
+ # Public: Get this Match's Participations' Robots' Teams
33
37
  def teams
34
38
  Team.in id: robots.map(&:team_id)
35
39
  end
@@ -10,6 +10,7 @@
10
10
  require "frecon/model"
11
11
 
12
12
  module FReCon
13
+ # Public: The Participation model.
13
14
  class Participation < Model
14
15
  belongs_to :robot
15
16
  belongs_to :competition
@@ -17,10 +18,12 @@ module FReCon
17
18
 
18
19
  validates :robot_id, :competition_id, presence: true
19
20
 
21
+ # Public: Get this Participation's Robot's Team
20
22
  def team
21
23
  robot.team
22
24
  end
23
25
 
26
+ # Public: Get this Participation's Competition's Matches
24
27
  def matches
25
28
  competition.matches
26
29
  end
@@ -11,6 +11,7 @@ require "frecon/model"
11
11
  require "frecon/position"
12
12
 
13
13
  module FReCon
14
+ # Public: The Record model.
14
15
  class Record < Model
15
16
  field :notes, type: String
16
17
  field :position, type: Position
@@ -20,14 +21,17 @@ module FReCon
20
21
 
21
22
  validates :position, :match_id, :participation_id, presence: true
22
23
 
24
+ # Public: Get this Record's Match's Competition
23
25
  def competition
24
26
  match.competition
25
27
  end
26
28
 
29
+ # Public: Get this Record's Participation's Robot
27
30
  def robot
28
31
  participation.robot
29
32
  end
30
33
 
34
+ # Public: Get this Record's Participation's Robot's Team
31
35
  def team
32
36
  participation.robot.team
33
37
  end
@@ -10,6 +10,7 @@
10
10
  require "frecon/model"
11
11
 
12
12
  module FReCon
13
+ # Public: The Robot model.
13
14
  class Robot < Model
14
15
  # This is an optional field we included for organization.
15
16
  field :name, type: String
@@ -19,14 +20,17 @@ module FReCon
19
20
 
20
21
  validates :team_id, presence: true
21
22
 
23
+ # Public: Get this Robot's Participations' Competitions
22
24
  def competitions
23
25
  Competition.in id: participations.map(&:competition_id)
24
26
  end
25
27
 
28
+ # Public: Get this Robot's Participations' Records
26
29
  def records
27
30
  Record.in participation_id: participations.map(&:id)
28
31
  end
29
32
 
33
+ # Public: Get this Robot's Participations' Records' Matches
30
34
  def matches
31
35
  Match.in id: records.map(&:match_id).uniq
32
36
  end
@@ -10,6 +10,7 @@
10
10
  require "frecon/model"
11
11
 
12
12
  module FReCon
13
+ # Public: The Team model.
13
14
  class Team < Model
14
15
  field :number, type: Integer
15
16
 
@@ -21,23 +22,31 @@ module FReCon
21
22
 
22
23
  validates :number, presence: true, uniqueness: true, numericality: { greater_than: 0 }
23
24
 
25
+ # Public: Find a team by number.
26
+ #
27
+ # team_number - An Integer to be used to compare.
28
+ #
29
+ # Returns a Team if one exists with the given number, otherwise nil.
24
30
  def self.number(team_number)
25
- # Team.find_by number: team_number
26
31
  find_by number: team_number
27
32
  end
28
33
 
34
+ # Public: Get this Team's Robots' Participations
29
35
  def participations
30
36
  Participation.in robot_id: robots.map(&:id)
31
37
  end
32
38
 
39
+ # Public: Get this Team's Robots' Participations' Competitions
33
40
  def competitions
34
41
  Competition.in id: participations.map(&:competition_id)
35
42
  end
36
43
 
44
+ # Public: Get this Team's Robots' Participations' Records
37
45
  def records
38
46
  Record.in participation_id: participations.map(&:id)
39
47
  end
40
48
 
49
+ # Public: Get this Team's Robots' Participations' Competitions' Matches
41
50
  def matches
42
51
  Match.in competition_id: competitions.map(&:id)
43
52
  end
@@ -9,24 +9,63 @@
9
9
 
10
10
  require "mongoid"
11
11
 
12
+ # Public: An extension for the Mongoid module.
12
13
  module Mongoid
14
+ # Public: A monkey-patch for the Mongoid::Criteria class which introduces
15
+ # a #psv_filter method.
13
16
  class Criteria
17
+ # Public: Filter by given PSV parameters.
18
+ #
19
+ # PSV is an introduced system that can be used within query strings to
20
+ # narrow a query. Since HTTP query strings can use "+" to act as spaces
21
+ # within a key-value pair, one can use these pluses to define nested
22
+ # query parameters when querying the database as in an indexing or
23
+ # showing request.
24
+ #
25
+ # psv_parameters - A Hash of PSV strings to comparison values.
26
+ #
27
+ # Examples
28
+ #
29
+ # Record.all.psv_filter({"participation robot team number" => "2503"})
30
+ # => #<Mongoid::Criteria ...>
31
+ #
32
+ # # Since each instance of Record has a :team shortcut method,
33
+ # # we can just filter it like so.
34
+ # Record.all.psv_filter({"team number" => "2503"})
35
+ # => #<Mongoid::Criteria ...>
36
+ #
37
+ # Returns a filtered version of self.
14
38
  def psv_filter(psv_parameters = {})
15
39
  collection = self
16
40
 
41
+ # Iterate through the Hash of query Strings to values, filtering using
42
+ # each pairing where the key is the query specifier and the value is the
43
+ # value that the last word in the query specifier is equal to. For
44
+ # multiple key-value pairs, just keep adding specifiers to the chain.
17
45
  psv_parameters.each do |psv_string, comparison_value|
18
- psv_keys = psv_string.split(" ").map do |psv_key|
46
+ # Split the query String, and convert each subsequent String
47
+ # to a symbol. Then, reverse the array to make it easy to perform
48
+ # an inside-out operation.
49
+ psv_keys = psv_string.split(/\W/).map do |psv_key|
19
50
  psv_key.to_sym
20
51
  end.reverse
21
52
 
53
+ # Get the final key in the query string.
22
54
  comparison_key = psv_keys.shift
23
55
 
56
+ # Create a comparison hash to be used to compare <attribute> to
57
+ # <expected value>.
24
58
  if comparison_value.length == 0 || comparison_value == "__nil__"
25
59
  comparison_hash = {comparison_key => nil}
26
60
  else
27
61
  comparison_hash = {comparison_key => comparison_value}
28
62
  end
29
63
 
64
+ # Each of the subsequent keys should be a model name. Generate a string
65
+ # corresponding to the "<model>" + "_id" for use as the comparison key,
66
+ # and find the model class by generating a constant.
67
+ #
68
+ # Then, nest a comparison around the current comparison hash.
30
69
  psv_keys.each do |model|
31
70
  model_id = (model.to_s + '_id').to_sym
32
71
  model_class = ("FReCon::" + model.to_s.capitalize).constantize
@@ -34,9 +73,11 @@ module Mongoid
34
73
  comparison_hash = {model_id => model_class.in(comparison_hash).map(&:id)}
35
74
  end
36
75
 
76
+ # Finally, complete this nested comparison.
37
77
  collection = collection.in(comparison_hash)
38
78
  end
39
79
 
80
+ # Return the fully-filtered collection.
40
81
  collection
41
82
  end
42
83
  end
@@ -10,20 +10,47 @@
10
10
  require "frecon/base"
11
11
 
12
12
  module FReCon
13
+ # Public: A wrapper to handle converting team positions and storing them.
13
14
  class Position
14
- attr_reader :alliance, :number
15
-
16
- # MongoDB compatibility methods.
15
+ # Public: The alliance part of the position
16
+ #
17
+ # Examples
18
+ #
19
+ # position = Position.new('r2')
20
+ # position.alliance
21
+ # # => :red
22
+ attr_reader :alliance
23
+
24
+ # Public: The slot part of the position.
25
+ #
26
+ # Examples
27
+ #
28
+ # position = Position.new('r2')
29
+ # position.number
30
+ # # => 2
31
+ attr_reader :number
32
+
33
+ # Public: Convert a stored position to a Position object.
34
+ #
35
+ # object - String representation of a position (mongoized)
36
+ #
37
+ # Returns Position parsed from object.
17
38
  def self.demongoize(object)
18
- # `object' should *always* be a string (since MatchNumber#mongoize returns a
39
+ # `object' should *always* be a String (since MatchNumber#mongoize returns a
19
40
  # String which is what is stored in the database)
20
41
  raise ArgumentError, "`object' must be a String" unless object.is_a?(String)
21
42
 
22
43
  Position.new(object)
23
44
  end
24
45
 
25
- # Allows passing a String or Hash instead of a Position.
26
- # i.e. record.position = "r3"
46
+ # Public: Convert a Position object to a storable string representation.
47
+ #
48
+ # object - A Position, String, or Hash. If Position, run #mongoize on it.
49
+ # If String, create a new Position object for it, then run
50
+ # #mongoize on it. If Hash, convert its keys to symbols, then
51
+ # pull out the :alliance and :number keys to generate a Position.
52
+ #
53
+ # Returns String containing the mongo-ready value for the representation.
27
54
  def self.mongoize(object)
28
55
  case object
29
56
  when Position
@@ -39,7 +66,15 @@ module FReCon
39
66
  end
40
67
  end
41
68
 
42
- # Used for queries.
69
+ # Public: Convert a Position object to a storable string representation for
70
+ # queries.
71
+ #
72
+ # object - A Position, String, or Hash. If Position, run #mongoize on it.
73
+ # If String, create a new Position object for it, then run
74
+ # #mongoize on it. If Hash, convert its keys to symbols, then
75
+ # pull out the :alliance and :number keys to generate a Position.
76
+ #
77
+ # Returns String containing the mongo-ready value for the representation.
43
78
  def self.evolve(object)
44
79
  case object
45
80
  when Position
@@ -55,6 +90,9 @@ module FReCon
55
90
  end
56
91
  end
57
92
 
93
+ # Public: Convert to a storable string representation.
94
+ #
95
+ # Returns String representing the Position's data.
58
96
  def mongoize
59
97
  to_s
60
98
  end
@@ -112,14 +150,19 @@ module FReCon
112
150
  end
113
151
  end
114
152
 
153
+ # Public: Convert to a String.
154
+ #
155
+ # Returns String representing the position data.
115
156
  def to_s
116
157
  "#{@alliance[0]}#{@number}"
117
158
  end
118
159
 
160
+ # Public: Determine if Position is on blue alliance.
119
161
  def is_blue?
120
162
  @alliance == :blue
121
163
  end
122
164
 
165
+ # Public: Determine if Position is on red alliance.
123
166
  def is_red?
124
167
  @alliance == :red
125
168
  end
@@ -9,9 +9,17 @@
9
9
 
10
10
  require "json"
11
11
 
12
+ # Public: A class representing errors that emanate from request handling.
12
13
  class RequestError < StandardError
14
+ # Public: The Array or Integer representing what can be returned from the
15
+ # request handler.
13
16
  attr_reader :return_value
14
17
 
18
+ # Public: Initialize a RequestError.
19
+ #
20
+ # code - An Integer representing the HTTP status code.
21
+ # message - A String representing the error message.
22
+ # context - An Object containing any necessary debugging values.
15
23
  def initialize(code, message = nil, context = nil)
16
24
  @code = code
17
25
  @message = message
@@ -10,7 +10,13 @@
10
10
  require "frecon/controllers"
11
11
 
12
12
  module FReCon
13
+ # Public: A module containing all of the routes.
13
14
  module Routes
15
+ # Public: Set up basic resource route handlers.
16
+ #
17
+ # base - Sinatra::Application to register the routes under.
18
+ # name - String containing the model name.
19
+ # controller - Controller-like object that contains key methods.
14
20
  def self.resource_routes(base, name, controller)
15
21
  base.post "/#{name}" do
16
22
  begin
@@ -53,6 +59,11 @@ module FReCon
53
59
  end
54
60
  end
55
61
 
62
+ # Public: Set up basic attribute route handlers.
63
+ #
64
+ # base - Sinatra::Application to register the routes under.
65
+ # name - String containing the model name.
66
+ # controller - Controller-like object.
56
67
  def self.attribute_routes(base, name, controller)
57
68
  model = controller.model
58
69
 
@@ -82,6 +93,9 @@ module FReCon
82
93
  end
83
94
  end
84
95
 
96
+ # Public: Bootstrap the routes into inclusors of this module.
97
+ #
98
+ # base - The child that included this module (should be a Sinatra App)
85
99
  def self.included(base)
86
100
  resource_routes base, "teams", TeamsController
87
101
  resource_routes base, "competitions", CompetitionsController
@@ -16,6 +16,7 @@ require "frecon/routes"
16
16
  require "frecon/controllers"
17
17
 
18
18
  module FReCon
19
+ # Public: The Sinatra web server.
19
20
  class Server < Sinatra::Base
20
21
  include Routes
21
22
 
@@ -23,23 +24,45 @@ module FReCon
23
24
  content_type "application/json"
24
25
  end
25
26
 
27
+ # Public: Start the Server.
28
+ #
29
+ # keyword_arguments - The Hash of arguments to use.
30
+ # :configuration - The Configuration to use when
31
+ # setting up the server.
32
+ #
33
+ # Returns the result of starting the server.
26
34
  def self.start(**keyword_arguments)
27
35
  run!(**keyword_arguments)
28
36
  end
29
37
 
30
38
  protected
31
39
 
40
+ # Internal: Set up the server.
41
+ #
42
+ # Sets the various Thin and Sinatra options, and sets up the database.
43
+ #
44
+ # :configuration - The Configuration to use when starting the server.
45
+ #
46
+ # Returns the result of setting up the database.
32
47
  def self.setup!(configuration: Configuration.construct!)
48
+ # Set the Thin and Sinatra options.
33
49
  set :server, %w[thin HTTP webrick]
34
50
  set :bind, configuration["frecon"]["server"]["host"]
35
51
  set :port, configuration["frecon"]["server"]["port"]
36
52
  set :environment, configuration["frecon"]["server"]["environment"]
37
53
 
54
+ # Grab out the mongoid configuration.
38
55
  mongoid = configuration["frecon"]["database"]["mongoid"]
39
56
 
40
- Database.setup(environment: environment, mongoid: mongoid)
57
+ # Set up the database.
58
+ Database.setup(environment, mongoid)
41
59
  end
42
60
 
61
+ # Internal: Set up the server and start it.
62
+ #
63
+ # keyword_arguments - The Hash of arguments to use.
64
+ # :configuration - The Configuration to use when
65
+ # setting up the server.
43
66
  def self.run!(**keyword_arguments)
44
67
  setup!(**keyword_arguments)
45
68
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: frecon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Craig
@@ -71,6 +71,48 @@ dependencies:
71
71
  - - "~>"
72
72
  - !ruby/object:Gem::Version
73
73
  version: '0.13'
74
+ - !ruby/object:Gem::Dependency
75
+ name: yard
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '0.8'
81
+ type: :development
82
+ prerelease: false
83
+ version_requirements: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '0.8'
88
+ - !ruby/object:Gem::Dependency
89
+ name: yard-tomdoc
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '0.7'
95
+ type: :development
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '0.7'
102
+ - !ruby/object:Gem::Dependency
103
+ name: yard-mongoid
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '0.0'
109
+ type: :development
110
+ prerelease: false
111
+ version_requirements: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '0.0'
74
116
  description: A JSON API for scouting FRC competitions that manages the database for
75
117
  the user.
76
118
  email: frc-frecon@googlegroups.com
@@ -79,6 +121,8 @@ executables:
79
121
  extensions: []
80
122
  extra_rdoc_files: []
81
123
  files:
124
+ - Gemfile
125
+ - Rakefile
82
126
  - bin/frecon
83
127
  - config/default.yml
84
128
  - lib/frecon.rb