eaternet 0.3.3 → 0.3.4
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 +4 -4
- data/Guardfile +3 -26
- data/eaternet.gemspec +1 -1
- data/lib/eaternet/agencies/nyc.rb +12 -15
- data/lib/eaternet/lives_1_0.rb +8 -319
- data/lib/eaternet/lives_1_0/adapter.rb +38 -0
- data/lib/eaternet/lives_1_0/business.rb +95 -0
- data/lib/eaternet/lives_1_0/feed_info.rb +33 -0
- data/lib/eaternet/lives_1_0/inspection.rb +69 -0
- data/lib/eaternet/lives_1_0/legend.rb +15 -0
- data/lib/eaternet/lives_1_0/legend_group.rb +43 -0
- data/lib/eaternet/lives_1_0/violation.rb +20 -0
- data/lib/eaternet/loggable.rb +2 -3
- data/lib/eaternet/prototype.rb +0 -2
- data/lib/eaternet/util.rb +11 -2
- data/lib/eaternet/validated_object.rb +75 -0
- data/lib/eaternet/version.rb +1 -1
- data/test/{eaternet_test.rb → eaternet/eaternet_test.rb} +0 -0
- data/test/{lives_1_0 → eaternet/lives_1_0}/business_test.rb +0 -0
- data/test/{lives_1_0 → eaternet/lives_1_0}/feed_info_test.rb +0 -0
- data/test/{lives_1_0 → eaternet/lives_1_0}/inspection_test.rb +0 -0
- data/test/{lives_1_0 → eaternet/lives_1_0}/legend_test.rb +0 -0
- data/test/{loggable_test.rb → eaternet/loggable_test.rb} +0 -0
- data/test/{nyc_adapter_test.rb → eaternet/nyc_adapter_test.rb} +3 -2
- data/test/{snhd_adapter_test.rb → eaternet/snhd_adapter_test.rb} +0 -0
- metadata +40 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 202273f23326e002f8e4a03d631b324d71025969
|
4
|
+
data.tar.gz: d817710bb5054fe8af26a6550e0e41454e4b0aa7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: feac8796ac69bc065bc16385d1a46469aa99d158bd3adbaae8303627af3653278618ac368ae403cc903355c997fa1d7e6721d93f4e594aebfe1efd2e63e40987
|
7
|
+
data.tar.gz: 6792b804fa1c37a490fd5ac8b93e88cf6357efec2b481d631d5a6a24440a015ee8269b5dcb95ebc38dfa6ebf675264d12d40a29850890aba8fdb9aed32bf7e64
|
data/Guardfile
CHANGED
@@ -6,31 +6,8 @@
|
|
6
6
|
|
7
7
|
## Uncomment to clear the screen before every task
|
8
8
|
# clearing :on
|
9
|
-
|
10
|
-
## Guard internally checks for changes in the Guardfile and exits.
|
11
|
-
## If you want Guard to automatically start up again, run guard in a
|
12
|
-
## shell loop, e.g.:
|
13
|
-
##
|
14
|
-
## $ while bundle exec guard; do echo "Restarting Guard..."; done
|
15
|
-
##
|
16
|
-
## Note: if you are using the `directories` clause above and you are not
|
17
|
-
## watching the project directory ('.'), then you will want to move
|
18
|
-
## the Guardfile to a watched dir and symlink it back, e.g.
|
19
|
-
#
|
20
|
-
# $ mkdir config
|
21
|
-
# $ mv Guardfile config/
|
22
|
-
# $ ln -s config/Guardfile .
|
23
|
-
#
|
24
|
-
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
25
|
-
|
26
9
|
guard :minitest do
|
27
|
-
|
28
|
-
watch(%r{^
|
29
|
-
watch(%r{^
|
30
|
-
watch(%r{^test/test_helper\.rb$}) { 'test' }
|
31
|
-
|
32
|
-
# with Minitest::Spec
|
33
|
-
# watch(%r{^spec/(.*)_spec\.rb$})
|
34
|
-
# watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
35
|
-
# watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
|
10
|
+
watch(%r{^test/(.*)_test\.rb$})
|
11
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
|
12
|
+
watch(%r{^test/test_helper\.rb$}) { 'test' }
|
36
13
|
end
|
data/eaternet.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency 'guard'
|
24
24
|
spec.add_development_dependency 'guard-minitest'
|
25
25
|
spec.add_development_dependency 'minitest'
|
26
|
-
|
26
|
+
spec.add_development_dependency 'minitest-utils'
|
27
27
|
spec.add_development_dependency 'pry'
|
28
28
|
spec.add_development_dependency 'rake', '~> 10'
|
29
29
|
spec.add_development_dependency 'vcr'
|
@@ -15,17 +15,11 @@ module Eaternet
|
|
15
15
|
module Agencies
|
16
16
|
# A LIVES 1.0 data source for New York City.
|
17
17
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# ```ruby
|
21
|
-
# require 'eaternet'
|
22
|
-
#
|
18
|
+
# @example Print all the restaurant names in New York City.
|
23
19
|
# nyc = Eaternet::Nyc.new
|
24
20
|
# nyc.businesses.each { |biz| puts biz.name }
|
25
|
-
# ```
|
26
21
|
#
|
27
22
|
# @see https://data.cityofnewyork.us/Health/DOHMH-New-York-City-Restaurant-Inspection-Results/xx67-kt59
|
28
|
-
# @todo This is a somewhat long namespace. Can this be improved?
|
29
23
|
class Nyc < Eaternet::Lives_1_0::Adapter
|
30
24
|
include Eaternet::Lives_1_0
|
31
25
|
include Eaternet::Loggable
|
@@ -34,17 +28,20 @@ module Eaternet
|
|
34
28
|
DATASET = 'xx67-kt59'
|
35
29
|
CSV_URL = "https://#{DOMAIN}/api/views/#{DATASET}/rows.csv?accessType=DOWNLOAD"
|
36
30
|
|
37
|
-
# Create an NYC
|
31
|
+
# Create an adapter for the NYC data.
|
38
32
|
#
|
39
|
-
# @
|
33
|
+
# @example
|
34
|
+
# nyc = Eaternet::Nyc.new
|
35
|
+
#
|
36
|
+
# @param [String] csv_path only needed for unit testing
|
40
37
|
def initialize(csv_path: nil)
|
41
38
|
@table_file = csv_path
|
42
39
|
end
|
43
40
|
|
44
41
|
def businesses
|
45
42
|
map_csv { |row| try_to_create_business(row) }
|
46
|
-
|
47
|
-
|
43
|
+
.compact
|
44
|
+
.unique
|
48
45
|
end
|
49
46
|
|
50
47
|
def inspections
|
@@ -93,7 +90,7 @@ module Eaternet
|
|
93
90
|
l.description = 'C'
|
94
91
|
end
|
95
92
|
]
|
96
|
-
end
|
93
|
+
end.legends
|
97
94
|
end
|
98
95
|
|
99
96
|
|
@@ -180,7 +177,7 @@ module Eaternet
|
|
180
177
|
end
|
181
178
|
|
182
179
|
def map_csv(&block)
|
183
|
-
CSV.new(open(table_file, encoding: 'utf-8'), headers: true)
|
180
|
+
CSV.new(File.open(table_file, encoding: 'utf-8'), headers: true)
|
184
181
|
.lazy
|
185
182
|
.map { |row| block.call(row) }
|
186
183
|
end
|
@@ -192,12 +189,12 @@ module Eaternet
|
|
192
189
|
def table_file
|
193
190
|
if @table_file.nil?
|
194
191
|
@table_file = Tempfile.new('all.csv.')
|
195
|
-
Nyc.
|
192
|
+
Nyc.download_to(@table_file)
|
196
193
|
end
|
197
194
|
@table_file
|
198
195
|
end
|
199
196
|
|
200
|
-
def self.
|
197
|
+
def self.download_to(a_file)
|
201
198
|
download_via_url(a_file)
|
202
199
|
end
|
203
200
|
|
data/lib/eaternet/lives_1_0.rb
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'eaternet/lives_1_0/adapter'
|
2
|
+
require 'eaternet/lives_1_0/business'
|
3
|
+
require 'eaternet/lives_1_0/feed_info'
|
4
|
+
require 'eaternet/lives_1_0/inspection'
|
5
|
+
require 'eaternet/lives_1_0/legend'
|
6
|
+
require 'eaternet/lives_1_0/legend_group'
|
7
|
+
require 'eaternet/lives_1_0/violation'
|
3
8
|
|
9
|
+
module Eaternet
|
4
10
|
# Framework for creating LIVES 1.0 apps.
|
5
11
|
#
|
6
12
|
# The goal is to make it as easy as possible to create
|
@@ -12,322 +18,5 @@ module Eaternet
|
|
12
18
|
# @see http://www.yelp.com/healthscores Local Inspector Value-Entry
|
13
19
|
# Specification (LIVES)
|
14
20
|
module Lives_1_0
|
15
|
-
require 'active_model'
|
16
|
-
|
17
|
-
# @abstract Subclass and override {#businesses}, {#inspections},
|
18
|
-
# and optionally {#violations} and {#feed_info} to implement
|
19
|
-
# a custom Lives 1.0 data source adapter.
|
20
|
-
class Adapter
|
21
|
-
# Required.
|
22
|
-
# @return [Enumerable<Business>]
|
23
|
-
def businesses
|
24
|
-
fail 'Override this to return an Enumerable of Business'
|
25
|
-
end
|
26
|
-
|
27
|
-
# Required.
|
28
|
-
# @return [Enumerable<Inspection>]
|
29
|
-
def inspections
|
30
|
-
fail 'Override this to return an Enumerable of Inspection'
|
31
|
-
end
|
32
|
-
|
33
|
-
# Optional.
|
34
|
-
# @return [Enumerable<Violation>]
|
35
|
-
def violations
|
36
|
-
fail 'Optionally override this to return an Enumerable of Violation'
|
37
|
-
end
|
38
|
-
|
39
|
-
# Optional.
|
40
|
-
# @return [FeedInfo]
|
41
|
-
def feed_info
|
42
|
-
fail 'Optionally override this to return a FeedInfo'
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
|
-
# Uses {ActiveModel::Validations} to create self-validating
|
48
|
-
# Plain Old Ruby objects.
|
49
|
-
#
|
50
|
-
# @abstract Subclass and add `attr_accessor` and validations
|
51
|
-
# to create custom validating objects.
|
52
|
-
#
|
53
|
-
# @see http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/
|
54
|
-
# @see http://www.rubyinside.com/rails-3-0s-activemodel-how-to-give-ruby-classes-some-activerecord-magic-2937.html
|
55
|
-
class ValidatedObject
|
56
|
-
include ActiveModel::Validations
|
57
|
-
|
58
|
-
# @raise [ArgumentError] if the object is not valid at the
|
59
|
-
# end of initialization.
|
60
|
-
#
|
61
|
-
# @yieldparam [ValidatedObject] new_object the yielded new object
|
62
|
-
# for configuration.
|
63
|
-
#
|
64
|
-
def initialize(&block)
|
65
|
-
block.call(self)
|
66
|
-
check_validations!
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
private
|
71
|
-
|
72
|
-
def check_validations!
|
73
|
-
fail ArgumentError, errors.messages.inspect if invalid?
|
74
|
-
end
|
75
|
-
|
76
|
-
class TypeValidator < ActiveModel::EachValidator
|
77
|
-
def validate_each(record, attribute, value)
|
78
|
-
record.errors.add attribute, (options[:message] || "is not of class #{options[:with]}") unless
|
79
|
-
value.class == options[:with]
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
# A food service establishment, e.g. a restaurant.
|
86
|
-
#
|
87
|
-
# @!attribute [rw] business_id
|
88
|
-
# Unique identifier for the business. For many cities,
|
89
|
-
# this may be the license number. Required.
|
90
|
-
# @return [String]
|
91
|
-
#
|
92
|
-
# @!attribute [rw] name
|
93
|
-
# Common name of the business. Required.
|
94
|
-
# @return [String]
|
95
|
-
#
|
96
|
-
# @!attribute [rw] address
|
97
|
-
# Street address of the business. For example: 706 Mission St.
|
98
|
-
# Required.
|
99
|
-
# @return [String]
|
100
|
-
#
|
101
|
-
# @!attribute [rw] city
|
102
|
-
# City of the business. This field must be included if the
|
103
|
-
# file contains businesses from multiple cities.
|
104
|
-
# @return [String]
|
105
|
-
#
|
106
|
-
# @!attribute [rw] state
|
107
|
-
# State or province for the business. In the U.S. this should
|
108
|
-
# be the two-letter code for the state. Optional.
|
109
|
-
# @return [String]
|
110
|
-
#
|
111
|
-
# @!attribute [rw] postal_code
|
112
|
-
# Zip code or other postal code. Optional.
|
113
|
-
# @return [String]
|
114
|
-
#
|
115
|
-
# @!attribute [rw] latitude
|
116
|
-
# Latitude of the business. This field must be a valid WGS 84
|
117
|
-
# latitude. For example: 37.7859547. Optional.
|
118
|
-
# @return [Float]
|
119
|
-
#
|
120
|
-
# @!attribute [rw] longitude
|
121
|
-
# Longitude of the business. This field must be a valid WGS 84
|
122
|
-
# longitude. For example: -122.4024658. Optional.
|
123
|
-
# @return [Float]
|
124
|
-
#
|
125
|
-
# @!attribute [rw] phone_number
|
126
|
-
# Phone number for a business including country specific dialing
|
127
|
-
# information. For example: +14159083801
|
128
|
-
# @return [String]
|
129
|
-
#
|
130
|
-
# @see http://www.yelp.com/healthscores#businesses LIVES/Business
|
131
|
-
class Business < ValidatedObject
|
132
|
-
attr_accessor :business_id, :name, :address, :city, :state,
|
133
|
-
:postal_code, :latitude, :longitude, :phone_number
|
134
|
-
|
135
|
-
validates :business_id, :name, :address,
|
136
|
-
type: String,
|
137
|
-
presence: true
|
138
|
-
validates :city, :state, :postal_code, :phone_number,
|
139
|
-
type: String,
|
140
|
-
allow_nil: true
|
141
|
-
validates :latitude,
|
142
|
-
numericality:
|
143
|
-
{
|
144
|
-
greater_than_or_equal_to: -90,
|
145
|
-
less_than_or_equal_to: 90
|
146
|
-
},
|
147
|
-
allow_nil: true
|
148
|
-
validates :longitude,
|
149
|
-
numericality:
|
150
|
-
{
|
151
|
-
greater_than_or_equal_to: -180,
|
152
|
-
less_than_or_equal_to: 180
|
153
|
-
},
|
154
|
-
allow_nil: true
|
155
|
-
|
156
|
-
def ==(other)
|
157
|
-
self.business_id == other.business_id
|
158
|
-
end
|
159
|
-
|
160
|
-
def eql?(other)
|
161
|
-
self == other
|
162
|
-
end
|
163
|
-
|
164
|
-
def hash
|
165
|
-
self.business_id.hash
|
166
|
-
end
|
167
|
-
|
168
|
-
# @return [String]
|
169
|
-
def to_s
|
170
|
-
"Business #{self.business_id}"
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
|
175
|
-
# Information about an inspectors’ visit to a businesses.
|
176
|
-
#
|
177
|
-
# @!attribute [rw] business_id
|
178
|
-
# Unique identifier of the business for which this inspection
|
179
|
-
# was done. Required.
|
180
|
-
# @return [String]
|
181
|
-
#
|
182
|
-
# @!attribute [rw] score
|
183
|
-
# Inspection score on a 0-100 scale. 100 is the highest score.
|
184
|
-
# This column must always be present in inspections.csv. However, it
|
185
|
-
# can be safely left blank for inspection rows that don’t have an
|
186
|
-
# associated score. (For example, some municipalities don’t associate
|
187
|
-
# a follow-up inspection with a score.)
|
188
|
-
# @return [Integer] if it's a scored inspection
|
189
|
-
# @return [String] if it's an un-scored inspection, then the return value
|
190
|
-
# will be an empty string.
|
191
|
-
#
|
192
|
-
# @!attribute [rw] date
|
193
|
-
# Date of the inspection.
|
194
|
-
# @return [Date]
|
195
|
-
#
|
196
|
-
# @!attribute [rw] description
|
197
|
-
# Single line description containing details on the outcome of an
|
198
|
-
# inspection. Use of this field is only encouraged if no violations
|
199
|
-
# are provided.
|
200
|
-
# @return [String]
|
201
|
-
#
|
202
|
-
# @!attribute [rw] type
|
203
|
-
# String representing the type of inspection. Must be (initial,
|
204
|
-
# routine, followup, complaint).
|
205
|
-
# @return [String]
|
206
|
-
#
|
207
|
-
# @see http://www.yelp.com/healthscores#inspections LIVES / Inspections
|
208
|
-
class Inspection < ValidatedObject
|
209
|
-
attr_accessor :business_id, :score, :date, :description, :type
|
210
|
-
|
211
|
-
ZERO_TO_ONE_HUNDRED_AND_BLANK = (0..100).to_a + ['']
|
212
|
-
|
213
|
-
validates :business_id,
|
214
|
-
type: String,
|
215
|
-
presence: true
|
216
|
-
validates :score,
|
217
|
-
inclusion: { in: ZERO_TO_ONE_HUNDRED_AND_BLANK },
|
218
|
-
allow_nil: true
|
219
|
-
validates :date,
|
220
|
-
type: Date,
|
221
|
-
presence: true
|
222
|
-
validates :type,
|
223
|
-
inclusion: { in: %w(initial routine followup complaint) },
|
224
|
-
allow_nil: true
|
225
|
-
|
226
|
-
def score
|
227
|
-
# noinspection RubyResolve
|
228
|
-
@score.nil? ? '' : @score
|
229
|
-
end
|
230
|
-
|
231
|
-
# @return [String]
|
232
|
-
def to_s
|
233
|
-
"Inspection #{self.business_id}/#{self.date}/#{self.score}"
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
|
238
|
-
# @see http://www.yelp.com/healthscores#violations LIVES / Violations
|
239
|
-
class Violation < ValidatedObject
|
240
|
-
attr_accessor :business_id, :date, :code, :description
|
241
|
-
|
242
|
-
validates :business_id,
|
243
|
-
type: String,
|
244
|
-
presence: true
|
245
|
-
validates :date,
|
246
|
-
type: Date,
|
247
|
-
presence: true
|
248
|
-
validates :code, :description,
|
249
|
-
type: String,
|
250
|
-
allow_nil: true
|
251
|
-
end
|
252
|
-
|
253
|
-
|
254
|
-
# @see http://www.yelp.com/healthscores#feed_info LIVES / Feed Information
|
255
|
-
class FeedInfo < ValidatedObject
|
256
|
-
attr_accessor :feed_date, :feed_version, :municipality_name,
|
257
|
-
:municipality_url, :contact_email
|
258
|
-
|
259
|
-
# See http://railscasts.com/episodes/211-validations-in-rails-3
|
260
|
-
EMAIL_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
|
261
|
-
URL_REGEX = %r{\Ahttps?:/}
|
262
|
-
|
263
|
-
validates :feed_date,
|
264
|
-
type: Date,
|
265
|
-
presence: true
|
266
|
-
validates :feed_version,
|
267
|
-
type: String,
|
268
|
-
presence: true
|
269
|
-
validates :municipality_name,
|
270
|
-
type: String,
|
271
|
-
presence: true
|
272
|
-
validates :municipality_url,
|
273
|
-
type: String,
|
274
|
-
format: { with: URL_REGEX },
|
275
|
-
allow_nil: true
|
276
|
-
validates :contact_email,
|
277
|
-
type: String,
|
278
|
-
format: { with: EMAIL_REGEX },
|
279
|
-
allow_nil: true
|
280
|
-
end
|
281
|
-
|
282
|
-
|
283
|
-
# @see http://www.yelp.com/healthscores#legend LIVES / Legend
|
284
|
-
class Legend < ValidatedObject
|
285
|
-
attr_accessor :minimum_score, :maximum_score, :description
|
286
|
-
|
287
|
-
validates :minimum_score, :maximum_score,
|
288
|
-
inclusion: { in: (0..100) }
|
289
|
-
validates :description,
|
290
|
-
presence: true,
|
291
|
-
type: String
|
292
|
-
end
|
293
|
-
|
294
|
-
|
295
|
-
# A container for all the Legends in the data set. Performs
|
296
|
-
# validation on the whole set of Legends ensure they cover
|
297
|
-
# the entire range of scores and do not overlap.
|
298
|
-
class LegendGroup < ValidatedObject
|
299
|
-
attr_accessor :legends
|
300
|
-
|
301
|
-
# Check that all the items in this LegendGroup:
|
302
|
-
#
|
303
|
-
# 1. Are of the class, Legend
|
304
|
-
# 2. Cover the range of scores from 0-100 without overlap
|
305
|
-
class ComprehensiveValidator < ActiveModel::EachValidator
|
306
|
-
def validate_each(record, attribute, legends)
|
307
|
-
scores = (0..100).to_a
|
308
|
-
legends.each do |legend|
|
309
|
-
unless legend.class == Legend
|
310
|
-
record.errors.add attribute, 'must be a Legend'
|
311
|
-
return
|
312
|
-
end
|
313
|
-
range = (legend.minimum_score..legend.maximum_score)
|
314
|
-
range.each do |score|
|
315
|
-
if scores.delete(score).nil?
|
316
|
-
unless score == legend.minimum_score || score == legend.maximum_score
|
317
|
-
record.errors.add attribute, 'may not overlap'
|
318
|
-
return
|
319
|
-
end
|
320
|
-
end
|
321
|
-
end
|
322
|
-
end
|
323
|
-
unless scores.empty?
|
324
|
-
record.errors.add attribute, 'do not cover entire span from 0–100'
|
325
|
-
end
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
validates :legends, comprehensive: true
|
330
|
-
end
|
331
|
-
|
332
21
|
end
|
333
22
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Eaternet
|
2
|
+
module Lives_1_0
|
3
|
+
# @abstract Subclass and override {#businesses}, {#inspections},
|
4
|
+
# and optionally {#violations}, {#feed_info}, and {#legends} to
|
5
|
+
# implement a custom Lives 1.0 data source adapter.
|
6
|
+
class Adapter
|
7
|
+
# Required.
|
8
|
+
# @return [Enumerable<Business>]
|
9
|
+
def businesses
|
10
|
+
fail 'Override this to return an Enumerable of Business'
|
11
|
+
end
|
12
|
+
|
13
|
+
# Required.
|
14
|
+
# @return [Enumerable<Inspection>]
|
15
|
+
def inspections
|
16
|
+
fail 'Override this to return an Enumerable of Inspection'
|
17
|
+
end
|
18
|
+
|
19
|
+
# Optional.
|
20
|
+
# @return [Enumerable<Violation>]
|
21
|
+
def violations
|
22
|
+
fail 'Optionally override this to return an Enumerable of Violation'
|
23
|
+
end
|
24
|
+
|
25
|
+
# Optional.
|
26
|
+
# @return [FeedInfo]
|
27
|
+
def feed_info
|
28
|
+
fail 'Optionally override this to return a FeedInfo'
|
29
|
+
end
|
30
|
+
|
31
|
+
# Optional.
|
32
|
+
# @return [Enumerable<Legend>]
|
33
|
+
def legends
|
34
|
+
fails 'Optionally override this to return an Enumerable of Legend'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'eaternet/lives_1_0/adapter'
|
2
|
+
require 'eaternet/validated_object'
|
3
|
+
|
4
|
+
module Eaternet
|
5
|
+
module Lives_1_0
|
6
|
+
# A food service establishment, e.g. a restaurant.
|
7
|
+
#
|
8
|
+
# @!attribute [rw] business_id
|
9
|
+
# Unique identifier for the business. For many cities,
|
10
|
+
# this may be the license number. Required.
|
11
|
+
# @return [String]
|
12
|
+
#
|
13
|
+
# @!attribute [rw] name
|
14
|
+
# Common name of the business. Required.
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
# @!attribute [rw] address
|
18
|
+
# Street address of the business. For example: 706 Mission St.
|
19
|
+
# Required.
|
20
|
+
# @return [String]
|
21
|
+
#
|
22
|
+
# @!attribute [rw] city
|
23
|
+
# City of the business. This field must be included if the
|
24
|
+
# file contains businesses from multiple cities.
|
25
|
+
# @return [String]
|
26
|
+
#
|
27
|
+
# @!attribute [rw] state
|
28
|
+
# State or province for the business. In the U.S. this should
|
29
|
+
# be the two-letter code for the state. Optional.
|
30
|
+
# @return [String]
|
31
|
+
#
|
32
|
+
# @!attribute [rw] postal_code
|
33
|
+
# Zip code or other postal code. Optional.
|
34
|
+
# @return [String]
|
35
|
+
#
|
36
|
+
# @!attribute [rw] latitude
|
37
|
+
# Latitude of the business. This field must be a valid WGS 84
|
38
|
+
# latitude. For example: 37.7859547. Optional.
|
39
|
+
# @return [Float]
|
40
|
+
#
|
41
|
+
# @!attribute [rw] longitude
|
42
|
+
# Longitude of the business. This field must be a valid WGS 84
|
43
|
+
# longitude. For example: -122.4024658. Optional.
|
44
|
+
# @return [Float]
|
45
|
+
#
|
46
|
+
# @!attribute [rw] phone_number
|
47
|
+
# Phone number for a business including country specific dialing
|
48
|
+
# information. For example: +14159083801
|
49
|
+
# @return [String]
|
50
|
+
#
|
51
|
+
# @see http://www.yelp.com/healthscores#businesses LIVES/Business
|
52
|
+
class Business < ValidatedObject
|
53
|
+
attr_accessor :business_id, :name, :address, :city, :state,
|
54
|
+
:postal_code, :latitude, :longitude, :phone_number
|
55
|
+
|
56
|
+
validates :business_id, :name, :address,
|
57
|
+
type: String,
|
58
|
+
presence: true
|
59
|
+
validates :city, :state, :postal_code, :phone_number,
|
60
|
+
type: String,
|
61
|
+
allow_nil: true
|
62
|
+
validates :latitude,
|
63
|
+
numericality:
|
64
|
+
{
|
65
|
+
greater_than_or_equal_to: -90,
|
66
|
+
less_than_or_equal_to: 90
|
67
|
+
},
|
68
|
+
allow_nil: true
|
69
|
+
validates :longitude,
|
70
|
+
numericality:
|
71
|
+
{
|
72
|
+
greater_than_or_equal_to: -180,
|
73
|
+
less_than_or_equal_to: 180
|
74
|
+
},
|
75
|
+
allow_nil: true
|
76
|
+
|
77
|
+
def ==(other)
|
78
|
+
business_id == other.business_id
|
79
|
+
end
|
80
|
+
|
81
|
+
def eql?(other)
|
82
|
+
self == other
|
83
|
+
end
|
84
|
+
|
85
|
+
def hash
|
86
|
+
business_id.hash
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [String]
|
90
|
+
def to_s
|
91
|
+
"Business #{business_id}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'eaternet/validated_object'
|
2
|
+
|
3
|
+
module Eaternet
|
4
|
+
module Lives_1_0
|
5
|
+
# @see http://www.yelp.com/healthscores#feed_info LIVES / Feed Information
|
6
|
+
class FeedInfo < ValidatedObject
|
7
|
+
attr_accessor :feed_date, :feed_version, :municipality_name,
|
8
|
+
:municipality_url, :contact_email
|
9
|
+
|
10
|
+
# See http://railscasts.com/episodes/211-validations-in-rails-3
|
11
|
+
EMAIL_REGEX = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
|
12
|
+
URL_REGEX = %r{\Ahttps?:/}
|
13
|
+
|
14
|
+
validates :feed_date,
|
15
|
+
type: Date,
|
16
|
+
presence: true
|
17
|
+
validates :feed_version,
|
18
|
+
type: String,
|
19
|
+
presence: true
|
20
|
+
validates :municipality_name,
|
21
|
+
type: String,
|
22
|
+
presence: true
|
23
|
+
validates :municipality_url,
|
24
|
+
type: String,
|
25
|
+
format: { with: URL_REGEX },
|
26
|
+
allow_nil: true
|
27
|
+
validates :contact_email,
|
28
|
+
type: String,
|
29
|
+
format: { with: EMAIL_REGEX },
|
30
|
+
allow_nil: true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'eaternet/validated_object'
|
3
|
+
|
4
|
+
module Eaternet
|
5
|
+
module Lives_1_0
|
6
|
+
# Information about an inspectors’ visit to a businesses.
|
7
|
+
# @see http://www.yelp.com/healthscores#inspections LIVES / Inspections
|
8
|
+
class Inspection < ValidatedObject
|
9
|
+
ZERO_TO_ONE_HUNDRED_AND_BLANK = (0..100).to_a + ['']
|
10
|
+
|
11
|
+
# @!attribute [rw] business_id
|
12
|
+
# Unique identifier of the business for which this inspection
|
13
|
+
# was done. Required.
|
14
|
+
# @return [String]
|
15
|
+
attr_accessor :business_id
|
16
|
+
validates :business_id,
|
17
|
+
type: String,
|
18
|
+
presence: true
|
19
|
+
|
20
|
+
# @!attribute [rw] score
|
21
|
+
# Inspection score on a 0-100 scale. 100 is the highest score.
|
22
|
+
# This column must always be present in inspections.csv. However, it
|
23
|
+
# can be safely left blank for inspection rows that don’t have an
|
24
|
+
# associated score. (For example, some municipalities don’t associate
|
25
|
+
# a follow-up inspection with a score.)
|
26
|
+
# @return [Integer] if it's a scored inspection
|
27
|
+
# @return [String] if it's an un-scored inspection, then the return value
|
28
|
+
# will be an empty string.
|
29
|
+
attr_accessor :score
|
30
|
+
validates :score,
|
31
|
+
inclusion: { in: ZERO_TO_ONE_HUNDRED_AND_BLANK },
|
32
|
+
allow_nil: true
|
33
|
+
def score
|
34
|
+
# noinspection RubyResolve
|
35
|
+
@score.nil? ? '' : @score
|
36
|
+
end
|
37
|
+
|
38
|
+
# @!attribute [rw] date
|
39
|
+
# Date of the inspection.
|
40
|
+
# @return [Date]
|
41
|
+
attr_accessor :date
|
42
|
+
validates :date,
|
43
|
+
type: Date,
|
44
|
+
presence: true
|
45
|
+
|
46
|
+
# @!attribute [rw] description
|
47
|
+
# Single line description containing details on the outcome of an
|
48
|
+
# inspection. Use of this field is only encouraged if no violations
|
49
|
+
# are provided.
|
50
|
+
# @return [String]
|
51
|
+
attr_accessor :description
|
52
|
+
|
53
|
+
validates :type,
|
54
|
+
inclusion: { in: %w(initial routine followup complaint) },
|
55
|
+
allow_nil: true
|
56
|
+
|
57
|
+
# @!attribute [rw] type
|
58
|
+
# String representing the type of inspection. Must be (initial,
|
59
|
+
# routine, followup, complaint).
|
60
|
+
# @return [String]
|
61
|
+
attr_accessor :type
|
62
|
+
|
63
|
+
# @return [String]
|
64
|
+
def to_s
|
65
|
+
"Inspection #{business_id}/#{date}/#{score}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'eaternet/validated_object'
|
2
|
+
|
3
|
+
module Eaternet
|
4
|
+
module Lives_1_0
|
5
|
+
class Legend < ValidatedObject
|
6
|
+
attr_accessor :minimum_score, :maximum_score, :description
|
7
|
+
|
8
|
+
validates :minimum_score, :maximum_score,
|
9
|
+
inclusion: { in: (0..100) }
|
10
|
+
validates :description,
|
11
|
+
presence: true,
|
12
|
+
type: String
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'eaternet/validated_object'
|
3
|
+
|
4
|
+
module Eaternet
|
5
|
+
module Lives_1_0
|
6
|
+
# A container for all the Legends in the data set. Performs
|
7
|
+
# validation on the whole set of Legends to ensure they cover
|
8
|
+
# the entire range of scores and do not overlap, as spec'd.
|
9
|
+
class LegendGroup < ValidatedObject
|
10
|
+
attr_accessor :legends
|
11
|
+
|
12
|
+
# Check that all the items in this LegendGroup:
|
13
|
+
#
|
14
|
+
# 1. Are of the class, Legend
|
15
|
+
# 2. Cover the range of scores from 0-100 without overlap
|
16
|
+
class ComprehensiveValidator < ActiveModel::EachValidator
|
17
|
+
def validate_each(record, attribute, legends)
|
18
|
+
scores = (0..100).to_a
|
19
|
+
legends.each do |legend|
|
20
|
+
unless legend.class == Legend
|
21
|
+
record.errors.add attribute, 'must be a Legend'
|
22
|
+
return
|
23
|
+
end
|
24
|
+
range = (legend.minimum_score..legend.maximum_score)
|
25
|
+
range.each do |score|
|
26
|
+
if scores.delete(score).nil?
|
27
|
+
unless score == legend.minimum_score || score == legend.maximum_score
|
28
|
+
record.errors.add attribute, 'may not overlap'
|
29
|
+
return
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
unless scores.empty?
|
35
|
+
record.errors.add attribute, 'do not cover entire span from 0–100'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
validates :legends, comprehensive: true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'eaternet/validated_object'
|
2
|
+
|
3
|
+
module Eaternet
|
4
|
+
module Lives_1_0
|
5
|
+
# @see http://www.yelp.com/healthscores#violations LIVES / Violations
|
6
|
+
class Violation < ValidatedObject
|
7
|
+
attr_accessor :business_id, :date, :code, :description
|
8
|
+
|
9
|
+
validates :business_id,
|
10
|
+
type: String,
|
11
|
+
presence: true
|
12
|
+
validates :date,
|
13
|
+
type: Date,
|
14
|
+
presence: true
|
15
|
+
validates :code, :description,
|
16
|
+
type: String,
|
17
|
+
allow_nil: true
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/eaternet/loggable.rb
CHANGED
@@ -1,19 +1,18 @@
|
|
1
1
|
module Eaternet
|
2
|
+
# A mixin to add logging functionality to a class.
|
2
3
|
module Loggable
|
3
|
-
|
4
4
|
# @return [Logger] the configured logger singleton
|
5
5
|
def logger
|
6
6
|
@logger ||= create_logger
|
7
7
|
end
|
8
8
|
|
9
9
|
private
|
10
|
-
|
10
|
+
|
11
11
|
def create_logger
|
12
12
|
logger = Logger.new(ENV['EATERNET_LOG_FILE'] || $stderr)
|
13
13
|
logger.datetime_format = '%Y-%m-%d %H:%M:%S'
|
14
14
|
logger.progname = 'Eaternet'
|
15
15
|
logger
|
16
16
|
end
|
17
|
-
|
18
17
|
end
|
19
18
|
end
|
data/lib/eaternet/prototype.rb
CHANGED
data/lib/eaternet/util.rb
CHANGED
@@ -3,8 +3,7 @@ require 'zip'
|
|
3
3
|
|
4
4
|
module Eaternet
|
5
5
|
module Util
|
6
|
-
#
|
7
|
-
# extract it into a temp directory.
|
6
|
+
# Download a zip file and extract it into a temp directory.
|
8
7
|
#
|
9
8
|
# @return [String] the directory path
|
10
9
|
def self.download_and_extract_zipfile(url)
|
@@ -23,10 +22,20 @@ module Eaternet
|
|
23
22
|
dir
|
24
23
|
end
|
25
24
|
|
25
|
+
# Download a file from the network.
|
26
|
+
#
|
27
|
+
# @param [String] source the URL to retrieve
|
28
|
+
# @param [String] dest pathname in which to save the file
|
26
29
|
def self.download(source:, dest:)
|
27
30
|
open(dest, 'wb') { |file| file << open(source).read }
|
28
31
|
end
|
29
32
|
|
33
|
+
# Extract a Zip archive.
|
34
|
+
#
|
35
|
+
# @param [String] path the Zip file's location
|
36
|
+
# @param [String] dest_dir directory in which to perform the
|
37
|
+
# extraction
|
38
|
+
# @return nil
|
30
39
|
def self.extract_zipfile(path:, dest_dir:)
|
31
40
|
open(path) do |zip_file|
|
32
41
|
Zip::File.open(zip_file, 'rb') do |zip_data|
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
|
3
|
+
module Eaternet
|
4
|
+
# @abstract Subclass and add `attr_accessor` and validations
|
5
|
+
# to create custom validating objects.
|
6
|
+
#
|
7
|
+
# Uses [ActiveModel::Validations](http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validates)
|
8
|
+
# to create self-validating Plain Old Ruby objects. This is especially useful for
|
9
|
+
# data validation when importing from CSV.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# class Dog < Eaternet::ValidatedObject
|
13
|
+
# attr_accessor :name, :birthday
|
14
|
+
#
|
15
|
+
# validates :name, presence: true
|
16
|
+
# validates :birthday, type: Date, allow_nil: true
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # The dog1 instance is validated at the end of instantiation. Here, it succeeds
|
20
|
+
# # without exception:
|
21
|
+
# dog1 = Dog.new do |d|
|
22
|
+
# d.name = 'Spot'
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# puts dog1.valid? # => true
|
26
|
+
#
|
27
|
+
# dog1.birthday = Date.new(2015, 1, 23)
|
28
|
+
# puts dog1.valid? # => true
|
29
|
+
#
|
30
|
+
# dog1.birthday = '2015-01-23'
|
31
|
+
# puts dog1.valid? # => false
|
32
|
+
# dog1.check_validations! # => ArgumentError: birthday is not of class Date
|
33
|
+
#
|
34
|
+
# @see Eaternet::ValidatedObject::TypeValidator
|
35
|
+
# @see http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/ ActiveModel: Make Any Ruby Object Feel Like ActiveRecord, Yehuda Katz
|
36
|
+
# @see http://www.rubyinside.com/rails-3-0s-activemodel-how-to-give-ruby-classes-some-activerecord-magic-2937.html Rails 3.0′s ActiveModel: How To Give Ruby Classes Some ActiveRecord Magic, Peter Cooper
|
37
|
+
class ValidatedObject
|
38
|
+
include ActiveModel::Validations
|
39
|
+
|
40
|
+
# Instantiate and validate a new object.
|
41
|
+
#
|
42
|
+
# @yieldparam [ValidatedObject] new_object the yielded new object
|
43
|
+
# for configuration.
|
44
|
+
#
|
45
|
+
# @raise [ArgumentError] if the object is not valid at the
|
46
|
+
# end of initialization.
|
47
|
+
def initialize(&block)
|
48
|
+
block.call(self)
|
49
|
+
check_validations!
|
50
|
+
end
|
51
|
+
|
52
|
+
# Run any validations and raise an error if invalid.
|
53
|
+
# @raise [ArgumentError] if any validations fail.
|
54
|
+
def check_validations!
|
55
|
+
fail ArgumentError, errors.messages.inspect if invalid?
|
56
|
+
end
|
57
|
+
|
58
|
+
# Ensure an object is a certain class. This is an example of a custom
|
59
|
+
# validator. It's here as a nested class for easy access by subclasses.
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# class Dog < ValidatedObject
|
63
|
+
# attr_accessor :weight
|
64
|
+
# validates :weight, type: Float
|
65
|
+
# end
|
66
|
+
class TypeValidator < ActiveModel::EachValidator
|
67
|
+
def validate_each(record, attribute, value)
|
68
|
+
return if value.class == options[:with]
|
69
|
+
|
70
|
+
message = options[:message] || "is not of class #{options[:with]}"
|
71
|
+
record.errors.add attribute, message
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/eaternet/version.rb
CHANGED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -76,7 +76,8 @@ class NycAdapterTest < Minitest::Test
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def test_finds_correct_violations
|
79
|
-
expected_violations = %w(06C 10F 04L 04N 04C 04L 06A 06C 08A
|
79
|
+
expected_violations = %w(06C 10F 04L 04N 04C 04L 06A 06C 08A
|
80
|
+
10F 02G 10F 02G 04L 06C 08A 10F)
|
80
81
|
actual_violations = @@nyc.violations.to_a.map(&:code)
|
81
82
|
assert_equal expected_violations, actual_violations
|
82
83
|
end
|
@@ -90,6 +91,6 @@ class NycAdapterTest < Minitest::Test
|
|
90
91
|
end
|
91
92
|
|
92
93
|
def test_has_legends
|
93
|
-
|
94
|
+
assert enumerable_of? Eaternet::Lives_1_0::Legend, @@nyc.legends
|
94
95
|
end
|
95
96
|
end
|
File without changes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eaternet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robb Shecter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-05-
|
11
|
+
date: 2015-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest-utils
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: pry
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,20 +186,28 @@ files:
|
|
172
186
|
- lib/eaternet/agencies/snhd.rb
|
173
187
|
- lib/eaternet/agencies/snhd_config.rb
|
174
188
|
- lib/eaternet/lives_1_0.rb
|
189
|
+
- lib/eaternet/lives_1_0/adapter.rb
|
190
|
+
- lib/eaternet/lives_1_0/business.rb
|
191
|
+
- lib/eaternet/lives_1_0/feed_info.rb
|
192
|
+
- lib/eaternet/lives_1_0/inspection.rb
|
193
|
+
- lib/eaternet/lives_1_0/legend.rb
|
194
|
+
- lib/eaternet/lives_1_0/legend_group.rb
|
195
|
+
- lib/eaternet/lives_1_0/violation.rb
|
175
196
|
- lib/eaternet/loggable.rb
|
176
197
|
- lib/eaternet/prototype.rb
|
177
198
|
- lib/eaternet/util.rb
|
199
|
+
- lib/eaternet/validated_object.rb
|
178
200
|
- lib/eaternet/version.rb
|
179
201
|
- lib/ext/lazy.rb
|
180
|
-
- test/eaternet_test.rb
|
202
|
+
- test/eaternet/eaternet_test.rb
|
203
|
+
- test/eaternet/lives_1_0/business_test.rb
|
204
|
+
- test/eaternet/lives_1_0/feed_info_test.rb
|
205
|
+
- test/eaternet/lives_1_0/inspection_test.rb
|
206
|
+
- test/eaternet/lives_1_0/legend_test.rb
|
207
|
+
- test/eaternet/loggable_test.rb
|
208
|
+
- test/eaternet/nyc_adapter_test.rb
|
209
|
+
- test/eaternet/snhd_adapter_test.rb
|
181
210
|
- test/fixtures/morris-park-bake-shop.csv
|
182
|
-
- test/lives_1_0/business_test.rb
|
183
|
-
- test/lives_1_0/feed_info_test.rb
|
184
|
-
- test/lives_1_0/inspection_test.rb
|
185
|
-
- test/lives_1_0/legend_test.rb
|
186
|
-
- test/loggable_test.rb
|
187
|
-
- test/nyc_adapter_test.rb
|
188
|
-
- test/snhd_adapter_test.rb
|
189
211
|
- test/test_helper.rb
|
190
212
|
homepage: https://github.com/eaternet/adapters-ruby
|
191
213
|
licenses:
|
@@ -212,14 +234,14 @@ signing_key:
|
|
212
234
|
specification_version: 4
|
213
235
|
summary: Regional adapters for restaurant health scores
|
214
236
|
test_files:
|
215
|
-
- test/eaternet_test.rb
|
237
|
+
- test/eaternet/eaternet_test.rb
|
238
|
+
- test/eaternet/lives_1_0/business_test.rb
|
239
|
+
- test/eaternet/lives_1_0/feed_info_test.rb
|
240
|
+
- test/eaternet/lives_1_0/inspection_test.rb
|
241
|
+
- test/eaternet/lives_1_0/legend_test.rb
|
242
|
+
- test/eaternet/loggable_test.rb
|
243
|
+
- test/eaternet/nyc_adapter_test.rb
|
244
|
+
- test/eaternet/snhd_adapter_test.rb
|
216
245
|
- test/fixtures/morris-park-bake-shop.csv
|
217
|
-
- test/lives_1_0/business_test.rb
|
218
|
-
- test/lives_1_0/feed_info_test.rb
|
219
|
-
- test/lives_1_0/inspection_test.rb
|
220
|
-
- test/lives_1_0/legend_test.rb
|
221
|
-
- test/loggable_test.rb
|
222
|
-
- test/nyc_adapter_test.rb
|
223
|
-
- test/snhd_adapter_test.rb
|
224
246
|
- test/test_helper.rb
|
225
247
|
has_rdoc:
|