campfire_logic 1.1.7
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.
- data/Capfile +4 -0
- data/Gemfile +29 -0
- data/Gemfile.local +27 -0
- data/README.rdoc +17 -0
- data/Rakefile +43 -0
- data/VERSION +1 -0
- data/app/controllers/application_controller.rb +5 -0
- data/app/controllers/directory_controller.rb +65 -0
- data/app/controllers/locations_controller.rb +65 -0
- data/app/controllers/services_controller.rb +46 -0
- data/app/helpers/application_helper.rb +27 -0
- data/app/models/google_maps_geocoder.rb +108 -0
- data/app/models/locale.rb +204 -0
- data/app/models/location.rb +216 -0
- data/app/models/location_import.rb +48 -0
- data/app/models/service.rb +27 -0
- data/app/models/zip_code.rb +48 -0
- data/app/models/zip_code_import.rb +19 -0
- data/app/views/directory/_search_form.html.erb +4 -0
- data/app/views/directory/_show_children.html.erb +29 -0
- data/app/views/directory/_show_location.html.erb +14 -0
- data/app/views/directory/search.html.erb +31 -0
- data/app/views/directory/show.html.erb +32 -0
- data/app/views/layouts/application.html.erb +49 -0
- data/app/views/locations/_form.html.erb +69 -0
- data/app/views/locations/edit.html.erb +3 -0
- data/app/views/locations/export.erb +4 -0
- data/app/views/locations/import.html.erb +13 -0
- data/app/views/locations/index.html.erb +37 -0
- data/app/views/locations/new.html.erb +3 -0
- data/app/views/locations/show.html.erb +70 -0
- data/app/views/services/_form.html.erb +29 -0
- data/app/views/services/edit.html.erb +3 -0
- data/app/views/services/index.html.erb +25 -0
- data/app/views/services/new.html.erb +3 -0
- data/app/views/services/show.html.erb +15 -0
- data/app/views/shared/_location.html.erb +18 -0
- data/app/views/shared/_map.html.erb +2 -0
- data/app/views/shared/_nav_tabs.html.erb +5 -0
- data/autotest/discover.rb +1 -0
- data/campfire_logic.gemspec +208 -0
- data/config/application.rb +44 -0
- data/config/boot.rb +13 -0
- data/config/cucumber.yml +10 -0
- data/config/deploy.rb +40 -0
- data/config/environment.rb +6 -0
- data/config/environments/development.rb +28 -0
- data/config/environments/production.rb +49 -0
- data/config/environments/test.rb +35 -0
- data/config/initializers/campfire_logic.rb +3 -0
- data/config/initializers/metric_fu.rb +9 -0
- data/config/initializers/secret_token.rb +7 -0
- data/config/initializers/session_store.rb +8 -0
- data/config/locales/en.yml +5 -0
- data/config/mongoid.yml +25 -0
- data/config/routes.rb +96 -0
- data/config.ru +4 -0
- data/db/seeds.rb +5 -0
- data/db/zip_codes.txt +42742 -0
- data/doc/google_maps_response.rb +56 -0
- data/features/admin_manages_locations.feature +10 -0
- data/features/customer_browses_directory.feature +25 -0
- data/features/customer_searches_directory.feature +29 -0
- data/features/step_definitions/directory_steps.rb +22 -0
- data/features/step_definitions/location_steps.rb +10 -0
- data/features/step_definitions/web_steps.rb +211 -0
- data/features/support/env.rb +31 -0
- data/features/support/paths.rb +33 -0
- data/features/support/selectors.rb +39 -0
- data/init.rb +1 -0
- data/lib/campfire_logic/engine.rb +7 -0
- data/lib/campfire_logic/railtie.rb +10 -0
- data/lib/campfire_logic.rb +31 -0
- data/lib/tasks/campfire_logic.rake +7 -0
- data/lib/tasks/cucumber.rake +71 -0
- data/public/404.html +26 -0
- data/public/422.html +26 -0
- data/public/500.html +26 -0
- data/public/favicon.ico +0 -0
- data/public/images/icons/collapsed.gif +0 -0
- data/public/images/icons/delete.png +0 -0
- data/public/images/icons/drag.png +0 -0
- data/public/images/icons/edit.png +0 -0
- data/public/images/icons/expanded.gif +0 -0
- data/public/images/icons/help_icon.png +0 -0
- data/public/images/icons/link_icon.png +0 -0
- data/public/images/icons/move.png +0 -0
- data/public/images/icons/move_white.png +0 -0
- data/public/images/icons/note.png +0 -0
- data/public/images/icons/note_white.png +0 -0
- data/public/images/icons/notification_icon_sprite.png +0 -0
- data/public/images/icons/spinner.gif +0 -0
- data/public/images/icons/view.png +0 -0
- data/public/images/icons/warning.png +0 -0
- data/public/images/icons/warning_2.png +0 -0
- data/public/images/icons/warning_box.png +0 -0
- data/public/images/icons/warning_icon.png +0 -0
- data/public/images/icons/warning_white.png +0 -0
- data/public/images/layout/arrow_asc.png +0 -0
- data/public/images/layout/arrow_desc.png +0 -0
- data/public/images/layout/black_bar.png +0 -0
- data/public/images/layout/branding.png +0 -0
- data/public/images/layout/button_bg.png +0 -0
- data/public/images/layout/content_left_bg.png +0 -0
- data/public/images/layout/content_right_bg.png +0 -0
- data/public/images/layout/footer_bg.png +0 -0
- data/public/images/layout/h2_bg.png +0 -0
- data/public/images/layout/h2_bg_for_table.png +0 -0
- data/public/images/layout/header_bg_grey.png +0 -0
- data/public/images/layout/header_bg_purple.png +0 -0
- data/public/images/layout/legend_bg.png +0 -0
- data/public/images/layout/text_field_bg.jpg +0 -0
- data/public/images/layout/text_field_error_bg.png +0 -0
- data/public/images/layout/th_bg.png +0 -0
- data/public/images/layout/th_bg_selected.png +0 -0
- data/public/images/rails.png +0 -0
- data/public/index.html +9 -0
- data/public/javascripts/application.js +2 -0
- data/public/javascripts/controls.js +965 -0
- data/public/javascripts/dragdrop.js +974 -0
- data/public/javascripts/effects.js +1123 -0
- data/public/javascripts/prototype.js +6001 -0
- data/public/javascripts/rails.js +175 -0
- data/public/robots.txt +6 -0
- data/public/sample-locations.xls +1 -0
- data/public/stylesheets/.gitkeep +0 -0
- data/public/stylesheets/application.css +682 -0
- data/public/stylesheets/core.css +1147 -0
- data/public/stylesheets/core_ie.css +52 -0
- data/public/stylesheets/csshover3.htc +14 -0
- data/script/cucumber +10 -0
- data/script/rails +6 -0
- data/spec/controllers/directory_controller_spec.rb +11 -0
- data/spec/controllers/services_controller_spec.rb +62 -0
- data/spec/models/google_maps_geocoder_spec.rb +62 -0
- data/spec/models/locale_spec.rb +64 -0
- data/spec/models/location_import_spec.rb +41 -0
- data/spec/models/location_spec.rb +195 -0
- data/spec/rcov.opts +2 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/test-locations.xls +1 -0
- metadata +361 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Stateflow.persistence = :mongoid
|
|
2
|
+
|
|
3
|
+
class Location
|
|
4
|
+
include CampfireLogic::Base
|
|
5
|
+
include Mongoid::Document
|
|
6
|
+
include Mongoid::Timestamps
|
|
7
|
+
include Stateflow
|
|
8
|
+
|
|
9
|
+
# Mongo Config ===================================================================================
|
|
10
|
+
field :name
|
|
11
|
+
field :address1
|
|
12
|
+
field :address2
|
|
13
|
+
field :city
|
|
14
|
+
field :state
|
|
15
|
+
field :state_long_name
|
|
16
|
+
field :zip
|
|
17
|
+
field :country
|
|
18
|
+
field :country_long_name
|
|
19
|
+
field :email
|
|
20
|
+
field :fax
|
|
21
|
+
field :phone
|
|
22
|
+
field :toll_free
|
|
23
|
+
field :hours
|
|
24
|
+
field :active, :type => Boolean
|
|
25
|
+
field :location_number
|
|
26
|
+
field :meta_description
|
|
27
|
+
field :accuracy, :type => Integer
|
|
28
|
+
field :address_status
|
|
29
|
+
field :lat_lng, :type => Array
|
|
30
|
+
field :validated_address1
|
|
31
|
+
field :validated_city
|
|
32
|
+
field :validated_zip
|
|
33
|
+
index [[ :lat_lng, Mongo::GEO2D ]], :min => -200, :max => 200
|
|
34
|
+
references_one :locale, :dependent => :destroy
|
|
35
|
+
references_and_referenced_in_many :services, :inverse_of => :locations
|
|
36
|
+
|
|
37
|
+
# Callbacks ======================================================================================
|
|
38
|
+
|
|
39
|
+
# Macros =========================================================================================
|
|
40
|
+
attr_reader :geocoder
|
|
41
|
+
trimmed_fields :address1, :address2, :city, :email, :fax, :location_number, :name, :phone, :state, :zip
|
|
42
|
+
validates_presence_of :address1, :city, :state
|
|
43
|
+
|
|
44
|
+
# State machine ==================================================================================
|
|
45
|
+
stateflow do
|
|
46
|
+
state_column :address_status
|
|
47
|
+
initial :new
|
|
48
|
+
|
|
49
|
+
state :new, :partial_match, :validated
|
|
50
|
+
|
|
51
|
+
# Approves this location's partially-matched address, marking it as "validated".
|
|
52
|
+
event :approve_address do
|
|
53
|
+
transitions :from => :partial_match, :to => :validated
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Clears this location's address, marking it as "new".
|
|
57
|
+
event :clear_address do
|
|
58
|
+
transitions :from => any, :to => :new
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Validates this location's address by geocoding. Partial matches are flagged; exact matches are flagged as "validated".
|
|
62
|
+
event :validate_address do
|
|
63
|
+
transitions :from => any, :to => [:new, :partial_match, :validated], :decide => :geocode
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Instance methods: Overrides ====================================================================
|
|
68
|
+
def address1=(value)
|
|
69
|
+
clear_geocoding if self.address1 != value
|
|
70
|
+
self[:address1] = value
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def city=(value)
|
|
74
|
+
clear_geocoding if self.city != value
|
|
75
|
+
self[:city] = value
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def state=(value)
|
|
79
|
+
clear_geocoding if self.state != value
|
|
80
|
+
self[:state] = value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Instance methods: Addressing ===================================================================
|
|
84
|
+
|
|
85
|
+
# Returns this location's address for geocoding. It omits the second address field to simplify post-processing.
|
|
86
|
+
def geocoding_address
|
|
87
|
+
[address1, city, state].select{|s| ! s.blank?} * ' '
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns this location's street address, combining the two address fields with the specified delimiter. Latter defaults to a space.
|
|
91
|
+
def street_address( delimiter = ' ' )
|
|
92
|
+
[address1, address2].select{|s| ! s.blank?} * delimiter
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Instance methods: Geocoding ====================================================================
|
|
96
|
+
|
|
97
|
+
# Geocodes this location.
|
|
98
|
+
def geocode
|
|
99
|
+
return :new if self.geocoding_address.blank?
|
|
100
|
+
|
|
101
|
+
unless @geocoder
|
|
102
|
+
@geocoder = GoogleMapsGeocoder.new(self.geocoding_address)
|
|
103
|
+
self.country, self.country_long_name, self[:state], self.state_long_name = @geocoder.country_short_name, @geocoder.country_long_name, @geocoder.state_short_name, @geocoder.state_long_name
|
|
104
|
+
self.lat_lng = [@geocoder.lat, @geocoder.lng]
|
|
105
|
+
audit_address
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
@geocoder.partial_match? ? :partial_match : :validated
|
|
109
|
+
rescue SocketError
|
|
110
|
+
Rails.logger.error "Can't geocode without a network connection!"
|
|
111
|
+
:new
|
|
112
|
+
rescue RuntimeError
|
|
113
|
+
Rails.logger.error "Can't geocode!"
|
|
114
|
+
:new
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns true if this location has a latitude and longitude.
|
|
118
|
+
def geocoded?
|
|
119
|
+
self.lat_lng.is_a?(Array)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Returns true if the specified address field matches the geocoder's results.
|
|
123
|
+
def matches_geocoder?(attribute)
|
|
124
|
+
@geocoder.blank? || self.send(attribute) == @geocoder.send(GOOGLE_MAPS_GEOCODER_FIELDS_BY_LOCATION_FIELD[attribute])
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Returns the n nearest locations.
|
|
128
|
+
def nearby_locations(n = 5)
|
|
129
|
+
Location.near(:lat_lng => self.lat_lng).limit(n + 1).select{ |l| l != self }
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Instance methods: Locales ======================================================================
|
|
133
|
+
|
|
134
|
+
def localize
|
|
135
|
+
self.save
|
|
136
|
+
self.locale ? update_locales : create_locales
|
|
137
|
+
self.save
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Returns this location's city locale.
|
|
141
|
+
def city_locale
|
|
142
|
+
self.locale.parent
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Returns this location's state locale.
|
|
146
|
+
def state_locale
|
|
147
|
+
self.city_locale.parent
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
GOOGLE_MAPS_GEOCODER_FIELDS_BY_LOCATION_FIELD = {
|
|
153
|
+
:address1 => :formatted_street_address,
|
|
154
|
+
:city => :city,
|
|
155
|
+
:zip => :postal_code
|
|
156
|
+
}.freeze
|
|
157
|
+
|
|
158
|
+
# Compares this location's address with the geocoder's results.
|
|
159
|
+
def audit_address
|
|
160
|
+
if @geocoder.partial_match?
|
|
161
|
+
GOOGLE_MAPS_GEOCODER_FIELDS_BY_LOCATION_FIELD.keys.each do |field|
|
|
162
|
+
# set "validated_" field to geocoder's result
|
|
163
|
+
self.send("validated_#{field}=", @geocoder.send(GOOGLE_MAPS_GEOCODER_FIELDS_BY_LOCATION_FIELD[field])) unless self.matches_geocoder?(field)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Clears all addressing data set by the geocoder.
|
|
169
|
+
def clear_geocoding
|
|
170
|
+
@geocoder = nil
|
|
171
|
+
self.country, self.country_long_name, self.lat_lng, self.state_long_name, self.validated_address1, self.validated_city, self.validated_zip = nil
|
|
172
|
+
clear_address
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Creates a locale tree for this location.
|
|
176
|
+
def create_locales
|
|
177
|
+
# Find or create a state locale for this location
|
|
178
|
+
_state = Locale.find_or_create_state_for(self)
|
|
179
|
+
|
|
180
|
+
# Find or create a city locale for this location
|
|
181
|
+
_city = _state.children.to_a.detect{ |c| c.name =~ /#{self.city}/i } || (sleep 0.25; _state.children.create(:name => self.city))
|
|
182
|
+
|
|
183
|
+
# Find or create a locale for this location
|
|
184
|
+
self.locale = _city.children.to_a.detect{ |c| c.name =~ /#{self.name}/i } || (sleep 0.25; _city.children.create(:name => self.name, :skip_geocoding => true))
|
|
185
|
+
self.locale.save
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Updates the locale tree for this location.
|
|
189
|
+
def update_locales
|
|
190
|
+
# update location
|
|
191
|
+
self.locale.name = self.name
|
|
192
|
+
self.locale.save
|
|
193
|
+
|
|
194
|
+
# does its city locale have other locations?
|
|
195
|
+
if self.city_locale.children.count > 1
|
|
196
|
+
# create a new city
|
|
197
|
+
self.locale.parent = self.state_locale.children.create(:name => self.city)
|
|
198
|
+
else
|
|
199
|
+
# update the city
|
|
200
|
+
self.city_locale.name = self.city
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# does its state locale have other cities?
|
|
204
|
+
if self.state_locale.children.count > 1
|
|
205
|
+
# find or create a state
|
|
206
|
+
self.city_locale.parent = Locale.find_or_create_state_for(self)
|
|
207
|
+
else
|
|
208
|
+
# update the state
|
|
209
|
+
self.state_locale.abbreviation = self.state
|
|
210
|
+
self.state_locale.name = self.state_long_name
|
|
211
|
+
self.state_locale.save
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
self.city_locale.save
|
|
215
|
+
end
|
|
216
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class LocationImport < ScaffoldLogic::DataImport
|
|
2
|
+
def self.save(upload)
|
|
3
|
+
# create file path
|
|
4
|
+
_path = File.join('tmp', upload['datafile'].original_filename)
|
|
5
|
+
|
|
6
|
+
# write file
|
|
7
|
+
File.open(_path, 'wb') { |f| f.write(upload['datafile'].read) }
|
|
8
|
+
|
|
9
|
+
self.run(_path)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.test(path='public/sample-locations.xls')
|
|
13
|
+
self.run(path)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def process_row
|
|
17
|
+
@required_fields ||= [:'location #']
|
|
18
|
+
|
|
19
|
+
if valid_row? && _location = Location.find_or_initialize_by(:location_number => row_map[@required_fields.first])
|
|
20
|
+
COLUMNS_BY_MODEL_FIELDS.keys.each do |_field|
|
|
21
|
+
_content = eval(COLUMNS_BY_MODEL_FIELDS[_field])
|
|
22
|
+
_location.send("#{_field}=", _content) unless _content.blank?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
_location.validate_address!
|
|
26
|
+
_location.localize
|
|
27
|
+
sleep 0.33
|
|
28
|
+
else
|
|
29
|
+
@errors << "Row #{@row_index}: Missing at least one required field: #{@required_fields * ', '}!"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
|
|
35
|
+
COLUMNS_BY_MODEL_FIELDS = {
|
|
36
|
+
'location_number' => 'row_map[:"location #"]',
|
|
37
|
+
'name' => 'row_map[:"name"]',
|
|
38
|
+
'address1' => 'row_map[:"address 1"]',
|
|
39
|
+
'address2' => 'row_map[:"address 2"]',
|
|
40
|
+
'city' => 'row_map[:"city"]',
|
|
41
|
+
'state' => 'row_map[:"state"]',
|
|
42
|
+
'zip' => 'row_map[:"postal code"]',
|
|
43
|
+
'country' => 'row_map[:"country code"]',
|
|
44
|
+
'phone' => 'row_map[:"phone"]',
|
|
45
|
+
'email' => 'row_map[:"email"]',
|
|
46
|
+
'meta_description' => 'row_map[:"description"]'
|
|
47
|
+
}
|
|
48
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Service
|
|
2
|
+
include Mongoid::Document
|
|
3
|
+
include Mongoid::Timestamps
|
|
4
|
+
|
|
5
|
+
# Attributes =====================================================================================
|
|
6
|
+
field :name
|
|
7
|
+
field :description
|
|
8
|
+
|
|
9
|
+
# Indices ========================================================================================
|
|
10
|
+
|
|
11
|
+
# Constants ======================================================================================
|
|
12
|
+
|
|
13
|
+
# Scopes =========================================================================================
|
|
14
|
+
|
|
15
|
+
# Relationships ==================================================================================
|
|
16
|
+
references_and_referenced_in_many :locations, :inverse_of => :services
|
|
17
|
+
|
|
18
|
+
# Behavior =======================================================================================
|
|
19
|
+
|
|
20
|
+
# Callbacks ======================================================================================
|
|
21
|
+
|
|
22
|
+
# Validations ====================================================================================
|
|
23
|
+
|
|
24
|
+
# Class methods ==================================================================================
|
|
25
|
+
|
|
26
|
+
# Instance methods ===============================================================================
|
|
27
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class ZipCode
|
|
2
|
+
include Mongoid::Document
|
|
3
|
+
include Mongoid::Timestamps
|
|
4
|
+
|
|
5
|
+
# Attributes =====================================================================================
|
|
6
|
+
field :zip
|
|
7
|
+
field :city
|
|
8
|
+
field :state
|
|
9
|
+
field :county
|
|
10
|
+
field :lat_lng, :type => Array
|
|
11
|
+
index [[ :lat_lng, Mongo::GEO2D ]], :min => -200, :max => 200
|
|
12
|
+
|
|
13
|
+
# Indices ========================================================================================
|
|
14
|
+
|
|
15
|
+
# Constants ======================================================================================
|
|
16
|
+
|
|
17
|
+
# Scopes =========================================================================================
|
|
18
|
+
|
|
19
|
+
# Relationships ==================================================================================
|
|
20
|
+
|
|
21
|
+
# Behavior =======================================================================================
|
|
22
|
+
|
|
23
|
+
# Callbacks ======================================================================================
|
|
24
|
+
before_save :geocode
|
|
25
|
+
|
|
26
|
+
# Validations ====================================================================================
|
|
27
|
+
|
|
28
|
+
# Class methods ==================================================================================
|
|
29
|
+
|
|
30
|
+
# Instance methods ===============================================================================
|
|
31
|
+
|
|
32
|
+
# Geocodes this zip code.
|
|
33
|
+
def geocode
|
|
34
|
+
return if geocoded?
|
|
35
|
+
|
|
36
|
+
unless @geocoder
|
|
37
|
+
@geocoder = GoogleMapsGeocoder.new(self.zip)
|
|
38
|
+
self.city, self.state, self.county, self.lat_lng = @geocoder.city, @geocoder.state_short_name, @geocoder.county, [@geocoder.lat, @geocoder.lng], @geocoder.state_long_name
|
|
39
|
+
end
|
|
40
|
+
rescue SocketError
|
|
41
|
+
Rails.logger.error "Can't geocode without a network connection!"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns true if this location has a latitude and longitude.
|
|
45
|
+
def geocoded?
|
|
46
|
+
self.lat_lng.is_a?(Array)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
class ZipCodeImport < ScaffoldLogic::DataImport
|
|
2
|
+
def self.save(upload)
|
|
3
|
+
# create file path
|
|
4
|
+
_path = File.join('tmp', upload['datafile'].original_filename)
|
|
5
|
+
|
|
6
|
+
# write file
|
|
7
|
+
File.open(_path, 'wb') { |f| f.write(upload['datafile'].read) }
|
|
8
|
+
|
|
9
|
+
self.run(_path)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.test(path='db/zip_codes.txt')
|
|
13
|
+
self.run(path)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def process_row
|
|
17
|
+
zip = ZipCode.create :zip => row_map[:zip], :city => row_map[:city], :state => row_map[:state], :county => row_map[:zip_class], :lat_lng => [row_map[:latitude].to_f, row_map[:longitude].to_f]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<%-
|
|
2
|
+
subtitle = " - #{@locale.location ? @locale.location.name : @locale.name}" unless @locale.planet?
|
|
3
|
+
set_title "Location Directory#{subtitle}"
|
|
4
|
+
-%>
|
|
5
|
+
|
|
6
|
+
<br style="clear: both;" />
|
|
7
|
+
<br style="clear: both;" />
|
|
8
|
+
|
|
9
|
+
<%- unless @locale.children.blank? -%>
|
|
10
|
+
<%- if @locale.root? -%>
|
|
11
|
+
<h2>Select a State/Province</h2>
|
|
12
|
+
<%- elsif @locale.country? -%>
|
|
13
|
+
<h2>Select a State</h2>
|
|
14
|
+
<%- elsif @locale.state? -%>
|
|
15
|
+
<h2>Select a City</h2>
|
|
16
|
+
<%- else -%>
|
|
17
|
+
<h2>Select a Location</h2>
|
|
18
|
+
<%- end -%>
|
|
19
|
+
|
|
20
|
+
<div>
|
|
21
|
+
<%- @locale.sorted_children.in_groups(2, false).each do |group| -%>
|
|
22
|
+
<ul style="float: left; margin-right: 1em; width: 40%;">
|
|
23
|
+
<%- group.each do |_child| -%>
|
|
24
|
+
<li><%= link_to _child.name, _child.to_url -%></li>
|
|
25
|
+
<%- end -%>
|
|
26
|
+
</ul>
|
|
27
|
+
<%- end -%>
|
|
28
|
+
</div><br style="clear: both;" />
|
|
29
|
+
<%- end -%>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%- set_title "Location Directory - #{ @location.name }" -%>
|
|
2
|
+
|
|
3
|
+
<%= render 'shared/location' -%><br style="clear: both;" />
|
|
4
|
+
<br style="clear: both;" />
|
|
5
|
+
|
|
6
|
+
<h2>Nearby Locations</h2>
|
|
7
|
+
|
|
8
|
+
<%- @location.nearby_locations.in_groups(2, false).each do |group| -%>
|
|
9
|
+
<ul style="float: left; margin-right: 1em; width: 40%;">
|
|
10
|
+
<% group.each do |@location| %>
|
|
11
|
+
<li><%= render 'shared/location' -%></li>
|
|
12
|
+
<% end %>
|
|
13
|
+
</ul>
|
|
14
|
+
<%- end -%>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<%- set_title 'Location Directory - Search' -%>
|
|
2
|
+
|
|
3
|
+
<div id="map_canvas" style="border: solid black 1px; width: 100%; height: 300px"></div><br />
|
|
4
|
+
|
|
5
|
+
<%= render 'search_form' -%><br style="clear: both;" />
|
|
6
|
+
<br style="clear: both;" />
|
|
7
|
+
|
|
8
|
+
<div id="directions_panel"></div>
|
|
9
|
+
|
|
10
|
+
<%- if @locations.empty? -%>
|
|
11
|
+
<p>No matching locations found. Please try again.</p>
|
|
12
|
+
</em></p>
|
|
13
|
+
<%- else -%>
|
|
14
|
+
<h2><%= controller.searching_by_zip? ? 'Locations nearest' : "#{@locations.size} locations match" -%> "<%= params[:criteria] -%>"</h2>
|
|
15
|
+
|
|
16
|
+
<%- for @location in @locations -%>
|
|
17
|
+
<%= render 'shared/location' -%>
|
|
18
|
+
<%- end -%>
|
|
19
|
+
<%- end -%>
|
|
20
|
+
|
|
21
|
+
<br style="clear: both;" />
|
|
22
|
+
|
|
23
|
+
<%= render :partial => 'shared/map', :locals => {:locations => @locations, :place => Locale.us.first} -%>
|
|
24
|
+
|
|
25
|
+
<script type="text/javascript">
|
|
26
|
+
initializeMap();
|
|
27
|
+
|
|
28
|
+
$('search-form').addEventListener('submit', function (e) {
|
|
29
|
+
window.location = '<%= directory_search_root_url -%>/' + $('search-input').value;
|
|
30
|
+
}, false);
|
|
31
|
+
</script>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<div id="map_canvas" style="border: solid black 1px; width: 100%; height: 300px"></div><br />
|
|
2
|
+
|
|
3
|
+
<%= render 'search_form' -%>
|
|
4
|
+
|
|
5
|
+
<div id="directions_panel" style="margin-bottom: 1em;"></div>
|
|
6
|
+
|
|
7
|
+
<%- if Locale.count <= 1 -%>
|
|
8
|
+
<%- set_title 'Location Directory' -%>
|
|
9
|
+
|
|
10
|
+
<p>No locations found. Why don't you <%= link_to 'import', import_locations_path -%> some?</p>
|
|
11
|
+
<%- else -%>
|
|
12
|
+
<%- if @locale.location? -%>
|
|
13
|
+
<%= render :partial => 'show_location' -%>
|
|
14
|
+
<%- else -%>
|
|
15
|
+
<%= render :partial => 'show_children' -%>
|
|
16
|
+
<%- end -%>
|
|
17
|
+
<%- end -%>
|
|
18
|
+
|
|
19
|
+
<br style="clear: both;" />
|
|
20
|
+
|
|
21
|
+
<%= render :partial => 'shared/map', :locals => {:locations => @locale.locations, :place => @locale.root? ? Locale.us.first : @locale} -%>
|
|
22
|
+
|
|
23
|
+
<script type="text/javascript">
|
|
24
|
+
initializeMap();
|
|
25
|
+
|
|
26
|
+
// change search-form's action so its query string can be converted to a URL
|
|
27
|
+
$('search-form').action = 'javascript:return(false);';
|
|
28
|
+
|
|
29
|
+
$('search-form').addEventListener('submit', function (e) {
|
|
30
|
+
window.location = '<%= directory_search_root_url -%>/' + $('search-input').value;
|
|
31
|
+
}, false);
|
|
32
|
+
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
3
|
+
<head>
|
|
4
|
+
<title><%= yield(:title) -%></title>
|
|
5
|
+
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
|
6
|
+
<meta name="description" content="" />
|
|
7
|
+
<meta name="keywords" content="" />
|
|
8
|
+
<link rel="canonical" href="<%= request.url -%>" />
|
|
9
|
+
<link rel="shortcut icon" href="/favicon.ico" />
|
|
10
|
+
<%= stylesheet_link_tag 'core', 'application', :cache => true -%>
|
|
11
|
+
<!--[if IE]>
|
|
12
|
+
<%= stylesheet_link_tag 'core_ie', :media => :all -%>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<%= javascript_include_tag :defaults, :cache => true -%>
|
|
15
|
+
<%= csrf_meta_tag -%>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div id="container">
|
|
19
|
+
<div id="header">
|
|
20
|
+
<div id="branding"></div>
|
|
21
|
+
<div id="navigation"><%= render :partial => 'shared/nav_tabs' -%></div>
|
|
22
|
+
</div>
|
|
23
|
+
<div id="page_header">
|
|
24
|
+
<h1><%= yield(:page_title) -%></h1>
|
|
25
|
+
<div id="breadcrumbs"><%= breadcrumbs -%></div>
|
|
26
|
+
</div>
|
|
27
|
+
<div id="content_area">
|
|
28
|
+
<div id="washout" style="display:none;"></div>
|
|
29
|
+
|
|
30
|
+
<%- flash.each do |name, msg| -%>
|
|
31
|
+
<%- if name == :notice -%>
|
|
32
|
+
<%= content_tag :div, msg, :id => "flash_#{name}", :class => "flash_#{name}", :style => "display:none;" -%>
|
|
33
|
+
<script type="text/javascript">
|
|
34
|
+
$('washout').toggle();
|
|
35
|
+
$('flash_notice').toggle();
|
|
36
|
+
<%= visual_effect(:fade, 'flash_notice', :duration => 1.0, :delay => 1.0).html_safe -%>
|
|
37
|
+
<%= visual_effect(:fade, 'washout', :duration => 1.0, :delay => 1.0).html_safe -%>
|
|
38
|
+
</script>
|
|
39
|
+
<%- else -%>
|
|
40
|
+
<%= content_tag :div, msg, :id => "flash_#{name}", :class => "flash_#{name}" -%>
|
|
41
|
+
<%- end -%>
|
|
42
|
+
<%- end -%>
|
|
43
|
+
<%= yield -%>
|
|
44
|
+
<br style="clear: both;" />
|
|
45
|
+
</div>
|
|
46
|
+
<div id="footer"><p>© <%= Time.zone.now.year -%> SEO Logic. All rights reserved.</p></div>
|
|
47
|
+
</div>
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<%= form_for(@location) do |f| -%>
|
|
2
|
+
<%- if @location.errors.any? -%>
|
|
3
|
+
<div id="error_explanation">
|
|
4
|
+
<h2>Please correct the following errors:</h2>
|
|
5
|
+
<%- @location.errors.to_a.in_groups(2, false).each do |group| -%>
|
|
6
|
+
<ul>
|
|
7
|
+
<%- group.each do |msg| -%>
|
|
8
|
+
<li><%= msg -%></li>
|
|
9
|
+
<%- end -%>
|
|
10
|
+
</ul>
|
|
11
|
+
<%- end -%>
|
|
12
|
+
<br style="clear: both;" />
|
|
13
|
+
</div>
|
|
14
|
+
<br style="clear: both;" />
|
|
15
|
+
<%- end -%>
|
|
16
|
+
|
|
17
|
+
<fieldset class="form_container">
|
|
18
|
+
<%= legend_tag 'About This Location' -%>
|
|
19
|
+
<div class="form_column">
|
|
20
|
+
<%= f.text_field :name -%>
|
|
21
|
+
<%= f.check_box :active, :inline_label => 'Yes' -%>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="form_column">
|
|
25
|
+
<div id="map_canvas" style="border: solid black 1px; width: 100%; height: 200px"></div>
|
|
26
|
+
</div>
|
|
27
|
+
</fieldset>
|
|
28
|
+
|
|
29
|
+
<fieldset class="form_container">
|
|
30
|
+
<%= legend_tag 'Address' -%>
|
|
31
|
+
<div class="form_column">
|
|
32
|
+
<%= f.text_field :address1 -%>
|
|
33
|
+
<%= f.text_field :address2 -%>
|
|
34
|
+
<%= f.text_field :city -%>
|
|
35
|
+
<%= f.text_field :state -%>
|
|
36
|
+
<%= f.text_field :zip -%>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="form_column">
|
|
39
|
+
<%= f.text_field :phone -%>
|
|
40
|
+
<%= f.text_field :fax -%>
|
|
41
|
+
<%= f.text_field :email -%>
|
|
42
|
+
</div>
|
|
43
|
+
</fieldset>
|
|
44
|
+
|
|
45
|
+
<fieldset class="form_container">
|
|
46
|
+
<%= legend_tag 'Services Offered' -%>
|
|
47
|
+
<%- i = 0 -%>
|
|
48
|
+
<%- Service.all.to_a.in_groups(2, false) do |column| -%>
|
|
49
|
+
<div class="form_column">
|
|
50
|
+
<%- column.each do |service| -%>
|
|
51
|
+
<fieldset>
|
|
52
|
+
<%= check_box_tag 'location[service_ids][]', service.id, @location.services.include?(service), :id => "service_#{i}" -%>
|
|
53
|
+
<%= service.name -%>
|
|
54
|
+
</fieldset>
|
|
55
|
+
<%- i += 1 -%>
|
|
56
|
+
<%- end -%>
|
|
57
|
+
</div>
|
|
58
|
+
<%- end -%>
|
|
59
|
+
</fieldset>
|
|
60
|
+
|
|
61
|
+
<%= f.submit 'Save', :class => 'button' -%>
|
|
62
|
+
<%= link_to 'Cancel', @location, :class => 'button' -%>
|
|
63
|
+
<%- end -%>
|
|
64
|
+
|
|
65
|
+
<%= render :partial => 'shared/map', :locals => {:place => @location.locale} -%>
|
|
66
|
+
|
|
67
|
+
<script type="text/javascript">
|
|
68
|
+
initializeMap();
|
|
69
|
+
</script>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
Location # Name Address 1 Address 2 City State Postal Code Country Code Phone Email Description
|
|
2
|
+
<%- @locations.each do |l| -%>
|
|
3
|
+
<%= l.location_number -%> <%= l.name -%> <%= l.address1 -%> <%= l.address2 -%> <%= l.city -%> <%= l.state -%> <%= l.zip -%> <%= l.country -%> <%= l.phone -%> <%= l.email -%> <%= l.meta_description %>
|
|
4
|
+
<%- end -%>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<%- set_title 'Import Locations' -%>
|
|
2
|
+
|
|
3
|
+
<%= form_tag upload_locations_path, :method => :post, :multipart => true do -%>
|
|
4
|
+
<fieldset class="form_container">
|
|
5
|
+
<p>To import locations, upload the import file below. Make sure the file follows this <%= link_to 'format', '/sample-locations.xls' -%>: it must be tab-delimited and the column names must match exactly. Otherwise, your data won't be processed correctly.</p>
|
|
6
|
+
|
|
7
|
+
<fieldset>
|
|
8
|
+
<%= file_field 'upload', 'datafile' -%>
|
|
9
|
+
</fieldset>
|
|
10
|
+
</fieldset>
|
|
11
|
+
|
|
12
|
+
<%= submit_tag 'Upload', :class => 'button' -%>
|
|
13
|
+
<%- end -%>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<%- set_title 'Locations' -%>
|
|
2
|
+
|
|
3
|
+
<%- unless @errors.blank? -%>
|
|
4
|
+
<%= @errors * '<br />' -%>
|
|
5
|
+
<%- end -%>
|
|
6
|
+
|
|
7
|
+
<%- if @locations.empty? -%>
|
|
8
|
+
<p><em>No locations found. Why don't you <%= link_to 'import', import_locations_path -%> some?
|
|
9
|
+
</em></p>
|
|
10
|
+
<%- else -%>
|
|
11
|
+
<table class="standard">
|
|
12
|
+
<tr>
|
|
13
|
+
<th><%= sort_link('locations', 'location_number', params) -%></th>
|
|
14
|
+
<th><%= sort_link('locations', 'name', params) -%></th>
|
|
15
|
+
<th>Address</th>
|
|
16
|
+
<th>URL</th>
|
|
17
|
+
<th> </th>
|
|
18
|
+
</tr>
|
|
19
|
+
|
|
20
|
+
<%- for location in @locations -%>
|
|
21
|
+
<tr class="<%= cycle('odd', 'even') -%>">
|
|
22
|
+
<td><%= link_to location.location_number, location -%></td>
|
|
23
|
+
<td><%= link_to location.name, location -%></td>
|
|
24
|
+
<td><%= location.geocoding_address -%></td>
|
|
25
|
+
<td>
|
|
26
|
+
<%- if location.locale -%>
|
|
27
|
+
<%= link_to location.locale.to_url, location.locale.to_url -%>
|
|
28
|
+
<%- end -%>
|
|
29
|
+
</td>
|
|
30
|
+
<td class="crud_links"><%= crud_links(location, [:show, :edit, :destroy]) -%></td>
|
|
31
|
+
</tr>
|
|
32
|
+
<%- end -%>
|
|
33
|
+
</table>
|
|
34
|
+
<%- end -%>
|
|
35
|
+
|
|
36
|
+
<%= link_to 'Import', import_locations_path, :class => 'button' -%>
|
|
37
|
+
<%= link_to 'Export', export_locations_path, :class => 'button' -%>
|