air_traffic_control 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +4 -0
- data/Gemfile.lock +17 -0
- data/LICENSE.txt +21 -0
- data/README.md +370 -0
- data/Rakefile +1 -0
- data/air_traffic_control.gemspec +32 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/air_traffic_control/directory_parser.rb +21 -0
- data/lib/air_traffic_control/form.rb +68 -0
- data/lib/air_traffic_control/input.rb +40 -0
- data/lib/air_traffic_control/value_parser/attachment.rb +9 -0
- data/lib/air_traffic_control/value_parser/belongs_to.rb +12 -0
- data/lib/air_traffic_control/value_parser/boolean.rb +14 -0
- data/lib/air_traffic_control/value_parser/date.rb +19 -0
- data/lib/air_traffic_control/value_parser/datetime.rb +17 -0
- data/lib/air_traffic_control/value_parser/float.rb +13 -0
- data/lib/air_traffic_control/value_parser/has_many.rb +40 -0
- data/lib/air_traffic_control/value_parser/has_one.rb +19 -0
- data/lib/air_traffic_control/value_parser/integer.rb +14 -0
- data/lib/air_traffic_control/value_parser/nil_class.rb +9 -0
- data/lib/air_traffic_control/value_parser/phone.rb +13 -0
- data/lib/air_traffic_control/value_parser/relationship.rb +12 -0
- data/lib/air_traffic_control/value_parser/string.rb +9 -0
- data/lib/air_traffic_control/value_parser/zip.rb +10 -0
- data/lib/air_traffic_control/value_parser.rb +35 -0
- data/lib/air_traffic_control/version.rb +3 -0
- data/lib/air_traffic_control.rb +9 -0
- metadata +30 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da38beed200c962b585f75e234f0da3e32b5e7f1
|
4
|
+
data.tar.gz: eb5d2621845d1861c0c2f5139ef558e4762f25bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c50f0df2a28c6d02aab543c78d6d9c426932c1e7ef72608431b71e1b507a4d132ce60abeb560df2403da16b92368cda9b122d6a5f02a03c6594d8519c0b0b7f
|
7
|
+
data.tar.gz: 9aa2d795de4fabba95cbebd98298274f844f7efad639f2ac52977c6a8f4d8f467193fb1afb3d8cf53657ea021e6af8f24f5721d6da152b424478dc80e505c889
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,370 @@
|
|
1
|
+
ATC
|
2
|
+
===
|
3
|
+
|
4
|
+
Installation
|
5
|
+
---
|
6
|
+
|
7
|
+
Add Gem:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem "air_traffic_control"
|
11
|
+
```
|
12
|
+
|
13
|
+
```
|
14
|
+
bundle
|
15
|
+
```
|
16
|
+
|
17
|
+
|
18
|
+
Basic Usage
|
19
|
+
---
|
20
|
+
|
21
|
+
Create a class to process your form and have it inherit from ATC::Base. I typically create the following folder to hold my form classes: app/classes/forms
|
22
|
+
|
23
|
+
**app/classes/forms/property.rb**
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
class Forms::Property < ATC::Base
|
27
|
+
|
28
|
+
input :bedrooms, :integer
|
29
|
+
input :price, :float
|
30
|
+
input :has_air_conditioning, :boolean
|
31
|
+
input :has_parking, :boolean
|
32
|
+
input :has_laundry, :boolean
|
33
|
+
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
In your controller:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
def create
|
41
|
+
@form = Forms::Property.new(params)
|
42
|
+
|
43
|
+
if @form.save
|
44
|
+
redirect_to trips_path
|
45
|
+
else
|
46
|
+
set_index_variables
|
47
|
+
render "index"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
Inheritance
|
53
|
+
---
|
54
|
+
|
55
|
+
General pattern:
|
56
|
+
|
57
|
+
- forms/property.rb
|
58
|
+
- forms/property/create.rb (inherits from forms/property.rb)
|
59
|
+
- forms/property/update.rb (inherits from forms/property.rb)
|
60
|
+
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
# app/classes/forms/property.rb
|
64
|
+
class Forms::Property < ATC::Base
|
65
|
+
|
66
|
+
input :bedrooms, :integer
|
67
|
+
input :price, :float
|
68
|
+
input :referred_by, :string
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# app/classes/forms/property/create.rb
|
74
|
+
class Forms::Property::Create < Forms::Property
|
75
|
+
|
76
|
+
# override any input, validation, or method here
|
77
|
+
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
class Forms::Property::Update < Forms::Property
|
82
|
+
|
83
|
+
# override any input, validation, or method here
|
84
|
+
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Associations
|
89
|
+
---
|
90
|
+
|
91
|
+
Let's say we are creating a web app where teacher's can create Courses with assignments and resources attached to the courses.
|
92
|
+
|
93
|
+
The modeling would look like:
|
94
|
+
|
95
|
+
class Course < ActiveRecord::Base
|
96
|
+
|
97
|
+
has_many :assignments
|
98
|
+
has_many :resources
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
Why a Library for Parsing Forms?
|
105
|
+
---
|
106
|
+
|
107
|
+
In my humble opinion, Rails is missing a tool for parsing forms on the backend. Currently the process looks something like this:
|
108
|
+
|
109
|
+
```html
|
110
|
+
# new.html.erb
|
111
|
+
<%= form_for @trip do |trip_builder| %>
|
112
|
+
<div class="field">
|
113
|
+
<%= trip_builder.label :miles %>
|
114
|
+
<%= trip_builder.text_field :miles, placeholder: '50 miles' %>
|
115
|
+
</div>
|
116
|
+
<% end %>
|
117
|
+
```
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
# trips_controller
|
121
|
+
|
122
|
+
def create
|
123
|
+
@trip = Trip.new(trip_params)
|
124
|
+
|
125
|
+
if @trip.save
|
126
|
+
redirect_to trips_path, notice: "New trip successfully created."
|
127
|
+
else
|
128
|
+
render "new"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
The problem with this approach is that there is nothing that processes the user inputs before being saved into the database. For example, if the user types "1,000 miles" into the form, Rails would store 0 instead of 1000 into our "miles" column.
|
134
|
+
|
135
|
+
The "Services" concept that many Rails developers favor is on point. Every form should have a corresponding Ruby class whose responsibility is to process the inputs and get the form ready for storage.
|
136
|
+
|
137
|
+
However, this "Services" concept does not have a supporting framework. It is repetitive for every developer, for example, to write code to parse a decimal field that comes into our Ruby controller as a string (as in the example above).
|
138
|
+
|
139
|
+
Here is how it may be done right now:
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
class Forms::Trip
|
143
|
+
|
144
|
+
attr_accessor :trip
|
145
|
+
|
146
|
+
def initialize(args = {})
|
147
|
+
@trip = Trip.new
|
148
|
+
@trip.miles = parse_miles(args[:miles)
|
149
|
+
end
|
150
|
+
|
151
|
+
def parse_miles(miles_input)
|
152
|
+
regex = /(\d|[.])/ # only care about numbers and decimals
|
153
|
+
miles_input.scan(regex).join.try(:to_f)
|
154
|
+
end
|
155
|
+
|
156
|
+
def save
|
157
|
+
@trip.save
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
# trips_controller.rb
|
165
|
+
|
166
|
+
def create
|
167
|
+
form = Forms::Trip.new(trip_params)
|
168
|
+
@trip = form.trip
|
169
|
+
|
170
|
+
if form.save
|
171
|
+
redirect_to trips_path, notice: "New trip successfully created."
|
172
|
+
else
|
173
|
+
render "new"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
While this process is not so bad with only one field and one model, it gets more complex with many different fields, types of inputs, and especially with nested attributes.
|
179
|
+
|
180
|
+
A better solution is to have the form service classes inherit from a base "form parser" class that can handle the common parsing needs of the community. For example:
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
class Forms::Trip < FormParse::Base
|
184
|
+
|
185
|
+
input :miles, :float
|
186
|
+
|
187
|
+
def after_init(args)
|
188
|
+
@trip = Trip.new(to_hash)
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
# trips_controller.rb
|
196
|
+
# same as above
|
197
|
+
|
198
|
+
def create
|
199
|
+
form = Forms::Trip.new(trip_params)
|
200
|
+
@trip = form.trip
|
201
|
+
|
202
|
+
if form.save
|
203
|
+
redirect_to trips_path, notice: "New trip successfully created."
|
204
|
+
else
|
205
|
+
render "new"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
The FormParse::Base class that Forms::Trip inherits from by default performs the proper regex as seen in the original Forms::Trip service object above. We need only define the input key and the type of input and the base class takes care of the heavy lifting.
|
211
|
+
|
212
|
+
The "to_hash" method takes the inputs and converts the parsed values into a hash, which produces the following in this case:
|
213
|
+
|
214
|
+
```ruby
|
215
|
+
{ miles: 1000.0 }
|
216
|
+
```
|
217
|
+
|
218
|
+
A more complex form would end up with a service class like below:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
class Forms::Trip
|
222
|
+
|
223
|
+
attr_accessor :trip
|
224
|
+
|
225
|
+
def initialize(args = {})
|
226
|
+
@trip = Trip.new
|
227
|
+
@trip.miles = parse_miles(args[:miles)
|
228
|
+
@trip.origin_city = args[:origin_city]
|
229
|
+
@trip.origin_state = args[:origin_state]
|
230
|
+
@trip.departure_datetime = parse_datetime(args[:departure_datetime])
|
231
|
+
@trip.destination_city = args[:destination_city]
|
232
|
+
@trip.destination_state = args[:destination_state]
|
233
|
+
@trip.arrival_datetime = parse_datetime(args[:arrival_datetime])
|
234
|
+
end
|
235
|
+
|
236
|
+
def parse_miles(miles_input)
|
237
|
+
regex = /(\d|[.])/ # only care about numbers and decimals
|
238
|
+
miles_input.scan(regex).join.try(:to_f)
|
239
|
+
end
|
240
|
+
|
241
|
+
def parse_datetime(datetime_input)
|
242
|
+
Date.strptime(datetime, "%m/%d/%Y")
|
243
|
+
end
|
244
|
+
|
245
|
+
def save
|
246
|
+
@trip.save
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
```
|
251
|
+
|
252
|
+
With a framework, it would only involve the following:
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
class Forms::Trip < FormParse::Base
|
256
|
+
|
257
|
+
input :miles, :float
|
258
|
+
input :origin_city, :string
|
259
|
+
input :origin_state, :string
|
260
|
+
input :departure_datetime, :datetime
|
261
|
+
input :destination_city, :string
|
262
|
+
input :destination_state, :string
|
263
|
+
input :arrival_datetime, :datetime
|
264
|
+
|
265
|
+
def after_init(args)
|
266
|
+
@trip = Trip.new(to_hash)
|
267
|
+
end
|
268
|
+
|
269
|
+
# to_hash produces:
|
270
|
+
# {
|
271
|
+
# miles: 1000.0,
|
272
|
+
# origin_city: "Chicago",
|
273
|
+
# origin_state: "IL",
|
274
|
+
# departure_datetime: # DateTime object
|
275
|
+
# destination_city: "New York",
|
276
|
+
# destination_state: "NY",
|
277
|
+
# arrival_datetime: # DateTime object
|
278
|
+
# }
|
279
|
+
|
280
|
+
end
|
281
|
+
```
|
282
|
+
|
283
|
+
Taking it a step further, if our form inputs are not database backed, we can even validate within our new service object like so:
|
284
|
+
|
285
|
+
```ruby
|
286
|
+
class FormParse::Base
|
287
|
+
include ActiveModel::Model
|
288
|
+
|
289
|
+
# ... base parsing code
|
290
|
+
end
|
291
|
+
|
292
|
+
class Form::Trip < FormParse::Base
|
293
|
+
|
294
|
+
input :miles, :float
|
295
|
+
input :origin_city, :string
|
296
|
+
input :origin_city, :string
|
297
|
+
input :destination_city, :string
|
298
|
+
input :destination_state, :string
|
299
|
+
|
300
|
+
validates :miles, :origin_city, :origin_state, :destination_city, :destination_state, presence: true
|
301
|
+
|
302
|
+
def after_init(args)
|
303
|
+
@trip = Trip.new(to_hash)
|
304
|
+
end
|
305
|
+
|
306
|
+
def save
|
307
|
+
if valid? && trip.valid?
|
308
|
+
trip.save
|
309
|
+
else
|
310
|
+
return false
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
```
|
316
|
+
|
317
|
+
Of course, the save method could be abstracted to our base class too, assuming we define which object the Form::Trip class is saving like so:
|
318
|
+
|
319
|
+
```ruby
|
320
|
+
class Form::Trip
|
321
|
+
|
322
|
+
def object
|
323
|
+
trip
|
324
|
+
end
|
325
|
+
|
326
|
+
# ... above code here
|
327
|
+
end
|
328
|
+
|
329
|
+
class FormParse::Base
|
330
|
+
|
331
|
+
def save
|
332
|
+
if valid? && object.valid?
|
333
|
+
object.save
|
334
|
+
else
|
335
|
+
return false
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
```
|
341
|
+
|
342
|
+
But where we really can see benefits from a "framework" for parsing our Rails forms is when we start to use nested attributes. For example, if we abstract the origin and destination fields to a "Location" model, we could build out a separate service class to handle parsing our "Location" form (even if it is in the context of a parent object like Trip).
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
class Forms::Location < FormParse::Base
|
346
|
+
|
347
|
+
input :city, :string
|
348
|
+
input :state, :string
|
349
|
+
input :departure_datetime, :datetime
|
350
|
+
input :arrival_datetime, :datetime
|
351
|
+
|
352
|
+
validates :city, :state :presence => true
|
353
|
+
|
354
|
+
end
|
355
|
+
|
356
|
+
class Forms::Trip < FormParse::Base
|
357
|
+
|
358
|
+
input :miles, :float
|
359
|
+
input :origin_attributes, :belongs_to, class: 'location'
|
360
|
+
input :destination_attributes, :belongs_to, class: 'location'
|
361
|
+
|
362
|
+
validates :miles, presence: true
|
363
|
+
|
364
|
+
def after_init(args)
|
365
|
+
@trip = Trip.new(to_hash)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
```
|
369
|
+
|
370
|
+
The FormParse::Base class will recoginze the :belongs_to relationship and utilize the Location service class to parse the part of the params hash that correspond with our destination and origin models. This is great because if there is anywhere that the user can update just the origin or destination of the trip, we could just reuse that Location service object to parse the form.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'air_traffic_control/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "air_traffic_control"
|
8
|
+
spec.version = ATC::VERSION
|
9
|
+
spec.authors = ["Ryan Francis"]
|
10
|
+
spec.email = ["ryan.p.francis@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Rails form parser}
|
13
|
+
spec.description = %q{Framework for intuitively parsing Rails forms}
|
14
|
+
spec.homepage = "https://github.com/launchpadlab/air_traffic_control"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
+
# delete this section to allow pushing this gem to any host.
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['allowed_push_host'] = "https://rubygems.org"
|
21
|
+
else
|
22
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
|
+
spec.bindir = "exe"
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ["lib"]
|
29
|
+
|
30
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
31
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
32
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "air_traffic_control"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module ATC
|
2
|
+
class DirectoryParser
|
3
|
+
attr_reader :current_class_name, :relevant_classes
|
4
|
+
|
5
|
+
def initialize(args = {})
|
6
|
+
@current_class_name = args[:current_class_name]
|
7
|
+
@relevant_classes = set_relevant_classes
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_relevant_classes
|
11
|
+
relevant_classes = [current_class_name]
|
12
|
+
classes = current_class_name.split("::")
|
13
|
+
while classes.present?
|
14
|
+
classes.pop
|
15
|
+
possibility = classes.join("::")
|
16
|
+
relevant_classes << possibility unless possibility.exclude?("::")
|
17
|
+
end
|
18
|
+
return relevant_classes
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module ATC
|
2
|
+
class Form
|
3
|
+
attr_reader :params, :inputs
|
4
|
+
attr_accessor :inputs
|
5
|
+
|
6
|
+
def initialize(args)
|
7
|
+
@params = Rails.version[0].to_i > 4 ? args.to_h.with_indifferent_access : HashWithIndifferentAccess.new(args)
|
8
|
+
@inputs = set_inputs
|
9
|
+
after_init(@params)
|
10
|
+
end
|
11
|
+
|
12
|
+
def params
|
13
|
+
hash = {}
|
14
|
+
inputs.each do |input|
|
15
|
+
value = instance_variable_get("@#{input.variable_name}")
|
16
|
+
|
17
|
+
# only set values to nil when they are intended to be nil
|
18
|
+
next if value.nil? && (input.association? || params.keys.exclude?(input.name.to_s))
|
19
|
+
|
20
|
+
hash[input.variable_name] = value
|
21
|
+
end
|
22
|
+
hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.input(name, type = nil, options = {})
|
26
|
+
@@input_definitions ||= {}
|
27
|
+
array = @@input_definitions[self.name].present? ? @@input_definitions[self.name] : []
|
28
|
+
array << [name, type, options]
|
29
|
+
@@input_definitions[self.name] = array
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.input_definitions
|
33
|
+
@@input_definitions
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def set_inputs
|
39
|
+
setter = ATC::DirectoryParser.new(current_class_name: self.class.name)
|
40
|
+
all_inputs = []
|
41
|
+
ATC::Base.input_definitions.each do |class_name, form_inputs|
|
42
|
+
next unless setter.relevant_classes.include?(class_name)
|
43
|
+
form_inputs.each do |form_input|
|
44
|
+
all_inputs << set_input(form_input)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
all_inputs
|
48
|
+
end
|
49
|
+
|
50
|
+
def set_input(form_input)
|
51
|
+
name = form_input[0]
|
52
|
+
type = form_input[1]
|
53
|
+
options = form_input[2]
|
54
|
+
input_value = params[name]
|
55
|
+
input = ATC::Input.new(self, name, type, input_value, options)
|
56
|
+
self.class.__send__(:attr_accessor, input.variable_name)
|
57
|
+
instance_variable_set("@#{input.variable_name.to_s}", input.parsed_value)
|
58
|
+
return input
|
59
|
+
end
|
60
|
+
|
61
|
+
# METHODS FOR CHILDREN
|
62
|
+
def after_init(args)
|
63
|
+
# this method is optionally implemented by children to
|
64
|
+
# override default initialization behavior
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ATC
|
2
|
+
class Input
|
3
|
+
|
4
|
+
attr_accessor :current_instance, :name, :default, :input_value, :type, :variable_name, :options
|
5
|
+
|
6
|
+
def initialize(current_instance, name, type, input_value, options = {})
|
7
|
+
@current_instance = current_instance
|
8
|
+
@name = name
|
9
|
+
@default = perform_default(options[:default])
|
10
|
+
@input_value = input_value || @default
|
11
|
+
@type = type || input_value.class
|
12
|
+
@variable_name = options[:variable_name] || @name
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def value_parser
|
17
|
+
@value_parser ||= "ATC::ValueParser::#{type.to_s.classify}".constantize.new(options.merge({
|
18
|
+
input_value: input_value,
|
19
|
+
variable_name: variable_name
|
20
|
+
}))
|
21
|
+
end
|
22
|
+
|
23
|
+
def parsed_value
|
24
|
+
@parsed_value ||= value_parser.parse
|
25
|
+
end
|
26
|
+
|
27
|
+
def perform_default(default_value)
|
28
|
+
if default_value.is_a?(Symbol)
|
29
|
+
current_instance.send(default_value)
|
30
|
+
else
|
31
|
+
default_value
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def association?
|
36
|
+
[:has_many, :belongs_to, :has_one].include?(type)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ATC
|
2
|
+
class ValueParser
|
3
|
+
class Boolean < ValueParser
|
4
|
+
def parse
|
5
|
+
return nil if input_value.nil?
|
6
|
+
return nil if input_value == "on"
|
7
|
+
return true if input_value == "1"
|
8
|
+
return true if input_value == true
|
9
|
+
return true if input_value == "true"
|
10
|
+
return false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ATC
|
2
|
+
class ValueParser
|
3
|
+
class Date < ValueParser
|
4
|
+
|
5
|
+
attr_reader :parse_format
|
6
|
+
|
7
|
+
def after_init(args)
|
8
|
+
@parse_format = args[:parse_format] || "%m/%d/%Y"
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse
|
12
|
+
return input_value unless input_value.present?
|
13
|
+
return input_value if input_value.is_a?(Date)
|
14
|
+
::Date.strptime(input_value, parse_format)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ATC
|
2
|
+
class ValueParser
|
3
|
+
class DateTime < ValueParser
|
4
|
+
attr_reader :input_format
|
5
|
+
|
6
|
+
def after_init(args)
|
7
|
+
@input_format = args[:input_format] || "%m/%d/%Y"
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse
|
11
|
+
return input_value unless input_value.present?
|
12
|
+
return input_value if input_value.is_a?(DateTime)
|
13
|
+
::Date.strptime(input_value, input_format)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ATC
|
2
|
+
class ValueParser
|
3
|
+
class Float < ValueParser
|
4
|
+
REGEX = /(\d|[.])/
|
5
|
+
|
6
|
+
def parse
|
7
|
+
return input_value unless input_value.present?
|
8
|
+
return input_value if ["Float", "Fixnum"].include?(input_value.class.name)
|
9
|
+
input_value.scan(REGEX).join.try(:to_f)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ATC
|
2
|
+
class ValueParser
|
3
|
+
class HasMany < ValueParser
|
4
|
+
include ATC::ValueParser::Relationship
|
5
|
+
|
6
|
+
# should return a pattern like...
|
7
|
+
# {
|
8
|
+
# loads_attributes: {
|
9
|
+
# "0" => {
|
10
|
+
# pays_fuel_surcharge: true,
|
11
|
+
# pickup_datetime: #datetime_object
|
12
|
+
# }
|
13
|
+
# }
|
14
|
+
# }
|
15
|
+
def parse
|
16
|
+
return input_value unless input_value.present?
|
17
|
+
return parse_hash if input_value.is_a?(Hash)
|
18
|
+
return parse_array if input_value.is_a?(Array)
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_hash
|
22
|
+
hash = {}
|
23
|
+
input_value.each do |index, obj_hash|
|
24
|
+
object_hash = form.new(obj_hash).params
|
25
|
+
hash[index] = object_hash
|
26
|
+
end
|
27
|
+
hash
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_array
|
31
|
+
hash = {}
|
32
|
+
input_value.each_with_index do |obj_hash, index|
|
33
|
+
object_hash = form.new(obj_hash).params
|
34
|
+
hash[index] = object_hash
|
35
|
+
end
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ATC
|
2
|
+
class ValueParser
|
3
|
+
class HasOne < ValueParser
|
4
|
+
include ATC::ValueParser::Relationship
|
5
|
+
|
6
|
+
# should return a pattern like...
|
7
|
+
# {
|
8
|
+
# document_attributes: {
|
9
|
+
# name: "Name of doc",
|
10
|
+
# date: #datetime_object
|
11
|
+
# }
|
12
|
+
# }
|
13
|
+
def parse
|
14
|
+
return input_value unless input_value.present?
|
15
|
+
transformer.new(input_value).params
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ATC
|
2
|
+
class ValueParser
|
3
|
+
class Integer < ValueParser
|
4
|
+
REGEX = /(\d|[.])/
|
5
|
+
|
6
|
+
def parse
|
7
|
+
return nil unless input_value.present?
|
8
|
+
return input_value if input_value.is_a?(Fixnum)
|
9
|
+
return input_value.to_i if input_value.is_a?(Float)
|
10
|
+
input_value.scan(REGEX).join.try(:to_i)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'air_traffic_control/value_parser/relationship'
|
2
|
+
require 'air_traffic_control/value_parser/attachment'
|
3
|
+
require 'air_traffic_control/value_parser/belongs_to'
|
4
|
+
require 'air_traffic_control/value_parser/boolean'
|
5
|
+
require 'air_traffic_control/value_parser/date'
|
6
|
+
require 'air_traffic_control/value_parser/datetime'
|
7
|
+
require 'air_traffic_control/value_parser/float'
|
8
|
+
require 'air_traffic_control/value_parser/has_many'
|
9
|
+
require 'air_traffic_control/value_parser/has_one'
|
10
|
+
require 'air_traffic_control/value_parser/integer'
|
11
|
+
require 'air_traffic_control/value_parser/nil_class'
|
12
|
+
require 'air_traffic_control/value_parser/phone'
|
13
|
+
require 'air_traffic_control/value_parser/string'
|
14
|
+
require 'air_traffic_control/value_parser/zip'
|
15
|
+
|
16
|
+
module ATC
|
17
|
+
class ValueParser
|
18
|
+
|
19
|
+
attr_accessor :input_value
|
20
|
+
|
21
|
+
def initialize(args = {})
|
22
|
+
@input_value = args[:input_value]
|
23
|
+
after_init(args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse
|
27
|
+
# implemented by child classes
|
28
|
+
end
|
29
|
+
|
30
|
+
def after_init(args)
|
31
|
+
# optionally implemented by child classes
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: air_traffic_control
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Francis
|
@@ -44,7 +44,35 @@ email:
|
|
44
44
|
executables: []
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
|
-
files:
|
47
|
+
files:
|
48
|
+
- Gemfile
|
49
|
+
- Gemfile.lock
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- air_traffic_control.gemspec
|
54
|
+
- bin/console
|
55
|
+
- bin/setup
|
56
|
+
- lib/air_traffic_control.rb
|
57
|
+
- lib/air_traffic_control/directory_parser.rb
|
58
|
+
- lib/air_traffic_control/form.rb
|
59
|
+
- lib/air_traffic_control/input.rb
|
60
|
+
- lib/air_traffic_control/value_parser.rb
|
61
|
+
- lib/air_traffic_control/value_parser/attachment.rb
|
62
|
+
- lib/air_traffic_control/value_parser/belongs_to.rb
|
63
|
+
- lib/air_traffic_control/value_parser/boolean.rb
|
64
|
+
- lib/air_traffic_control/value_parser/date.rb
|
65
|
+
- lib/air_traffic_control/value_parser/datetime.rb
|
66
|
+
- lib/air_traffic_control/value_parser/float.rb
|
67
|
+
- lib/air_traffic_control/value_parser/has_many.rb
|
68
|
+
- lib/air_traffic_control/value_parser/has_one.rb
|
69
|
+
- lib/air_traffic_control/value_parser/integer.rb
|
70
|
+
- lib/air_traffic_control/value_parser/nil_class.rb
|
71
|
+
- lib/air_traffic_control/value_parser/phone.rb
|
72
|
+
- lib/air_traffic_control/value_parser/relationship.rb
|
73
|
+
- lib/air_traffic_control/value_parser/string.rb
|
74
|
+
- lib/air_traffic_control/value_parser/zip.rb
|
75
|
+
- lib/air_traffic_control/version.rb
|
48
76
|
homepage: https://github.com/launchpadlab/air_traffic_control
|
49
77
|
licenses:
|
50
78
|
- MIT
|