zip_search 0.1.3
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +37 -0
- data/app/assets/javascripts/zip_search/init.js +16 -0
- data/app/assets/javascripts/zip_search/jquery.inputmask.bundle.js +2145 -0
- data/app/assets/javascripts/zip_search/locations.coffee +69 -0
- data/app/assets/stylesheets/zip_search/application.css +15 -0
- data/app/assets/stylesheets/zip_search/locations.css +4 -0
- data/app/assets/stylesheets/zip_search/typeahead.scss +72 -0
- data/app/controllers/zip_search/application_controller.rb +4 -0
- data/app/controllers/zip_search/locations_controller.rb +43 -0
- data/app/helpers/zip_search/application_helper.rb +4 -0
- data/app/helpers/zip_search/locations_helper.rb +4 -0
- data/app/models/zip_search/location.rb +9 -0
- data/app/views/layouts/zip_search/application.html.erb +14 -0
- data/app/views/zip_search/_simple_fields.html.erb +4 -0
- data/app/views/zip_search/locations/_search_field.html.erb +19 -0
- data/config/routes.rb +4 -0
- data/config/spring.rb +1 -0
- data/db/migrate/20150914014727_create_zip_search_locations.rb +17 -0
- data/db/migrate/20150920034901_add_zs_association_name_to_zip_search_locations.rb +7 -0
- data/lib/generators/zip_search/install/install_generator.rb +22 -0
- data/lib/tasks/zip_search_tasks.rake +4 -0
- data/lib/zip_search/acts_as_location.rb +61 -0
- data/lib/zip_search/capybara_helpers.rb +9 -0
- data/lib/zip_search/controller_helpers.rb +79 -0
- data/lib/zip_search/engine.rb +38 -0
- data/lib/zip_search/has_locations.rb +87 -0
- data/lib/zip_search/model_helpers.rb +29 -0
- data/lib/zip_search/railtie.rb +18 -0
- data/lib/zip_search/simple_form_helper.rb +50 -0
- data/lib/zip_search/strong_params_helper.rb +17 -0
- data/lib/zip_search/version.rb +3 -0
- data/lib/zip_search/view_helpers.rb +31 -0
- data/lib/zip_search.rb +62 -0
- data/spec/factories/travelers.rb +40 -0
- data/spec/features/travelers_spec.rb +68 -0
- data/spec/models/traveler_spec.rb +38 -0
- data/spec/models/zip_search/location_spec.rb +7 -0
- data/spec/spec_helper.rb +134 -0
- metadata +383 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
$ ->
|
2
|
+
return unless (form=$('.location-search-form')).is('*')
|
3
|
+
$(document).ready ->
|
4
|
+
states = new Bloodhound
|
5
|
+
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name')
|
6
|
+
queryTokenizer: Bloodhound.tokenizers.whitespace
|
7
|
+
prefetch: '/zip_search/location_search.json?by=state&a=1'
|
8
|
+
cities = new Bloodhound
|
9
|
+
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name')
|
10
|
+
queryTokenizer: Bloodhound.tokenizers.whitespace
|
11
|
+
remote:
|
12
|
+
url: '/zip_search/location_search.json?by=city&q=%QUERY'
|
13
|
+
wildcard: '%QUERY'
|
14
|
+
counties = new Bloodhound
|
15
|
+
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name')
|
16
|
+
queryTokenizer: Bloodhound.tokenizers.whitespace
|
17
|
+
remote:
|
18
|
+
url: '/zip_search/location_search.json?by=county&q=%QUERY'
|
19
|
+
wildcard: '%QUERY'
|
20
|
+
zips = new Bloodhound
|
21
|
+
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name')
|
22
|
+
queryTokenizer: Bloodhound.tokenizers.whitespace
|
23
|
+
remote:
|
24
|
+
url: '/zip_search/location_search.json?by=zip&q=%QUERY'
|
25
|
+
wildcard: '%QUERY'
|
26
|
+
data_defaults =
|
27
|
+
display: 'name'
|
28
|
+
templates:
|
29
|
+
empty: '<span></span>'
|
30
|
+
suggestion: (datum)->
|
31
|
+
output = "<p style='line-height:16px;padding:5px 20px'>#{datum.name}"
|
32
|
+
output += "<br /><small>#{datum.desc}</small>" if datum.desc?
|
33
|
+
output += '</p>'
|
34
|
+
datasets = [
|
35
|
+
$.extend({}, data_defaults, name: 'zips', source: zips),
|
36
|
+
$.extend({}, data_defaults, name: 'counties', source: counties),
|
37
|
+
$.extend({}, data_defaults, name: 'cities', source: cities),
|
38
|
+
$.extend({}, data_defaults, name: 'states', source: states) ]
|
39
|
+
$('.zipsearch-typeahead').typeahead null, datasets
|
40
|
+
$('.zipsearch-typeahead').on 'typeahead:selected', (e, datum, name)=>
|
41
|
+
e.preventDefault()
|
42
|
+
console.log datum
|
43
|
+
ulid_attrs = type: 'hidden', name: 'ulid'
|
44
|
+
by_attrs = type: 'hidden', name: 'by'
|
45
|
+
form.prepend $('<input />').attr(ulid_attrs).val(datum.id)
|
46
|
+
form.prepend $('<input />').attr(by_attrs).val(datum.param)
|
47
|
+
form.submit()
|
48
|
+
|
49
|
+
$ ->
|
50
|
+
return unless $('.zs-location-fields').is('*')
|
51
|
+
$(document).ready ->
|
52
|
+
$('.zs-location-fields').each ->
|
53
|
+
zip_field = $(@).find('input[name*=zip]')
|
54
|
+
county_field = $(@).find('input[name*=county]')
|
55
|
+
city_field = $(@).find('input[name*=city]')
|
56
|
+
state_field = $(@).find('select[name*=state]')
|
57
|
+
zip_field.inputmask
|
58
|
+
mask: '99999[-9999]'
|
59
|
+
greedy: false
|
60
|
+
oncomplete: ->
|
61
|
+
v = zip_field.val()
|
62
|
+
$.post '/zip_search/zipcode_search', {q: v}, (data)->
|
63
|
+
console.log data
|
64
|
+
if data.status == 'ok'
|
65
|
+
county_field.val(data.county) if data.county?
|
66
|
+
city_field.val(data.city) if data.city?
|
67
|
+
state_field.find('option').filter(-> $(@).text()==data.state )
|
68
|
+
.prop('selected', true) if data.state?
|
69
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,72 @@
|
|
1
|
+
// Typeahead
|
2
|
+
.twitter-typeahead {
|
3
|
+
width:100%;
|
4
|
+
color: black;
|
5
|
+
}
|
6
|
+
.twitter-typeahead .tt-hint {
|
7
|
+
display: block;
|
8
|
+
height: 34px;
|
9
|
+
line-height: 1.428571429;
|
10
|
+
border: 1px solid transparent;
|
11
|
+
border-radius:4px;
|
12
|
+
.simple_form.checkin & {
|
13
|
+
padding: 6px 12px;
|
14
|
+
font-size: 13px;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
.twitter-typeahead .hint-small {
|
18
|
+
height: 30px;
|
19
|
+
padding: 5px 10px;
|
20
|
+
font-size: 12px;
|
21
|
+
border-radius: 3px;
|
22
|
+
line-height: 1.5;
|
23
|
+
}
|
24
|
+
.twitter-typeahead .hint-large {
|
25
|
+
height: 45px;
|
26
|
+
padding: 10px 16px;
|
27
|
+
font-size: 18px;
|
28
|
+
border-radius: 6px;
|
29
|
+
line-height: 1.33;
|
30
|
+
}
|
31
|
+
.tt-menu {
|
32
|
+
z-index:1500;
|
33
|
+
padding: 8px 0px;
|
34
|
+
background-color: #ffffff;
|
35
|
+
border: 1px solid rgba(0,0,0,0.2);
|
36
|
+
border-radius: 0px 0px 8px 8px;
|
37
|
+
box-shadow: 0px 5px 10px rgba(0,0,0,0.2);
|
38
|
+
width:100%;
|
39
|
+
}
|
40
|
+
.tt-suggestion {
|
41
|
+
padding: 3px 20px;
|
42
|
+
line-height: 24px;
|
43
|
+
font-size:14px;
|
44
|
+
&.tt-cursor {
|
45
|
+
color: #ffffff;
|
46
|
+
background-color: #0097cf;
|
47
|
+
small { color: white; }
|
48
|
+
}
|
49
|
+
p {margin:0px;}
|
50
|
+
}
|
51
|
+
.typeahead-header {
|
52
|
+
margin:5px 10px;
|
53
|
+
padding:5px 10px;
|
54
|
+
font-weight: 100;
|
55
|
+
border-top: 1px solid #cccccc;
|
56
|
+
border-bottom: 1px solid #cccccc;
|
57
|
+
}
|
58
|
+
|
59
|
+
.location-search-form {
|
60
|
+
margin: 10px 6px 0 0;
|
61
|
+
width: 33%;
|
62
|
+
// @media(max-width: $screen-md-max) { width: 50%; }
|
63
|
+
// @media(max-width: $screen-sm-max) { width: 100%; }
|
64
|
+
|
65
|
+
.location-search-clear-icon {
|
66
|
+
position: relative;
|
67
|
+
top: -29px;
|
68
|
+
right: 5px;
|
69
|
+
float: right;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require_dependency 'zip_search/application_controller'
|
2
|
+
|
3
|
+
module ZipSearch
|
4
|
+
class LocationsController < ApplicationController
|
5
|
+
|
6
|
+
def location_search
|
7
|
+
q = params[:q]; by = params[:by]
|
8
|
+
results = if by.present?
|
9
|
+
case by
|
10
|
+
when 'state'
|
11
|
+
US_STATES.map{|s| { name: s[0], id: s[1], param: by } }
|
12
|
+
when 'city'
|
13
|
+
search = Location.search_by_city q
|
14
|
+
search.to_a.uniq{|r| [ r[:city], r[:state] ] }.map{|r|
|
15
|
+
{ id: r[:id], name: r[:city], param: by,
|
16
|
+
desc: "City in #{r[:state]}" } }
|
17
|
+
when 'county'
|
18
|
+
search = Location.search_by_county q
|
19
|
+
search.to_a.uniq{|r| [ r[:county], r[:state] ] }.map{|r|
|
20
|
+
{ id: r[:id], name: r[:county], param: by,
|
21
|
+
desc: "County in #{r[:city]}, #{r[:state]}" } }
|
22
|
+
when 'zip'
|
23
|
+
search = Location.search_by_zip q
|
24
|
+
search.to_a.uniq(&:zip).map{|r|
|
25
|
+
{ id: r[:id], name: r[:zip], param: by,
|
26
|
+
desc: "#{r[:county]}, #{r[:city]}, #{r[:state]}" } }
|
27
|
+
end
|
28
|
+
else [] end
|
29
|
+
render json: results
|
30
|
+
end
|
31
|
+
|
32
|
+
def zipcode_search
|
33
|
+
results = do_zipcode_search params[:q]
|
34
|
+
if results.any?
|
35
|
+
results[:status] ||= 'ok'
|
36
|
+
render json: results
|
37
|
+
else
|
38
|
+
render json: { status: 'not ok', error: 'No results' }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>ZipSearch</title>
|
5
|
+
<%= stylesheet_link_tag "zip_search/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "zip_search/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<% if defined? SimpleForm %>
|
2
|
+
<%= simple_form_for :location_search, settings[:form] do |f| %>
|
3
|
+
<%= f.input_field :q, settings[:field] %>
|
4
|
+
<% if params.key?(:location_search) %>
|
5
|
+
<%= link_to params.except(:location_search, :ulid, :by), class: 'location-search-clear-icon' do %>
|
6
|
+
<span class="glyphicon glyphicon-remove"></span>
|
7
|
+
<% end %>
|
8
|
+
<% end %>
|
9
|
+
<% end %>
|
10
|
+
<% else %>
|
11
|
+
<%= form_for :location_search, settings[:form] do |f| %>
|
12
|
+
<%= f.text_field :q, settings[:field] %>
|
13
|
+
<% if params.key?(:location_search) %>
|
14
|
+
<%= link_to params.except(:location_search, :ulid, :by), class: 'location-search-clear-icon' do %>
|
15
|
+
<span class="glyphicon glyphicon-remove"></span>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
data/config/routes.rb
ADDED
data/config/spring.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Spring.application_root = './test/dummy'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class CreateZipSearchLocations < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :zip_search_locations do |t|
|
4
|
+
t.integer :locatable_id
|
5
|
+
t.string :locatable_type
|
6
|
+
t.string :title
|
7
|
+
t.string :zip
|
8
|
+
t.string :county
|
9
|
+
t.string :city
|
10
|
+
t.string :state
|
11
|
+
t.float :latitude
|
12
|
+
t.float :longitude
|
13
|
+
|
14
|
+
t.timestamps null: false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
class AddZsAssociationNameToZipSearchLocations < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
add_column :zip_search_locations, :zs_association_name, :string
|
4
|
+
add_index :zip_search_locations, :zs_association_name
|
5
|
+
add_index :zip_search_locations, [ :locatable_type, :locatable_id ]
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'zip_search'
|
2
|
+
|
3
|
+
module ZipSearch
|
4
|
+
module Generators
|
5
|
+
class InstallGenerator < ::Rails::Generators::Base
|
6
|
+
|
7
|
+
def inject_engine_routing
|
8
|
+
inject_into_file 'config/routes.rb', after: 'pplication.routes.draw do' do
|
9
|
+
"\n mount ZipSearch::Engine => '/zipsearch'\n"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def inject_js_initializer
|
14
|
+
inject_into_file 'app/assets/javascripts/application.js', before: '//= require_tree .' do
|
15
|
+
"//= require zip_search/init\n"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'zip_search'
|
2
|
+
require 'pg_search'
|
3
|
+
|
4
|
+
module ZipSearch
|
5
|
+
module ActsAsLocation
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include PgSearch
|
8
|
+
|
9
|
+
included do
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def acts_as_location(title=:location, options={})
|
14
|
+
pg_search_scope :search_by_location,
|
15
|
+
against: [:zip, :county, :city, :state],
|
16
|
+
using: {tsearch: {prefix: true}}
|
17
|
+
pg_search_scope :search_by_state, against: :state,
|
18
|
+
using: {tsearch: {prefix: true}}
|
19
|
+
pg_search_scope :search_by_city, against: :city,
|
20
|
+
using: {tsearch: {prefix: true}}
|
21
|
+
pg_search_scope :search_by_county, :against => :county,
|
22
|
+
using: {tsearch: {prefix: true}}
|
23
|
+
pg_search_scope :search_by_zip, against: :zip
|
24
|
+
|
25
|
+
geocoded_by :to_sentence
|
26
|
+
after_validation :geocode
|
27
|
+
|
28
|
+
validates_presence_of :zs_association_name
|
29
|
+
|
30
|
+
include ActsAsLocation::LocalInstanceMethods
|
31
|
+
end
|
32
|
+
|
33
|
+
def location_fields; %i( zip county city state ) end
|
34
|
+
|
35
|
+
def params(extra_params=[])
|
36
|
+
extra_params + [ :zs_association_name, :title, :latitude, :longitude,
|
37
|
+
:zip, :county, :city, :state ]
|
38
|
+
end
|
39
|
+
|
40
|
+
def nested_params(extra_params=[])
|
41
|
+
[:id, :_destroy] + params(extra_params)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module LocalInstanceMethods
|
46
|
+
|
47
|
+
def location_fields; self.class.location_fields end
|
48
|
+
def location; Hash[ location_fields.map{|lf| [ lf, send(lf) ] } ] end
|
49
|
+
def location_blank?; location.values.reject(&:blank?).empty? end
|
50
|
+
|
51
|
+
def to_sentence
|
52
|
+
return 'Location unavailable' if location_blank?
|
53
|
+
location_fields.map{|lf| send(lf) }.reject(&:blank?).join(', ')
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'zip_search'
|
2
|
+
require 'zip_search/strong_params_helper'
|
3
|
+
|
4
|
+
module ZipSearch
|
5
|
+
module ControllerHelpers
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include StrongParamsHelper
|
10
|
+
end
|
11
|
+
|
12
|
+
def do_zipcode_search(zip)
|
13
|
+
Geocoder.configure(lookup: :nominatim)
|
14
|
+
n_results = Geocoder.search zip, bounds: US_BOUNDS
|
15
|
+
Geocoder.configure(lookup: :google)
|
16
|
+
g_results = Geocoder.search zip, bounds: US_BOUNDS
|
17
|
+
response = {}
|
18
|
+
if n_results.any? || g_results.any?
|
19
|
+
if g_results.any?
|
20
|
+
g_best_match = g_results.first
|
21
|
+
response[:city] = g_best_match.city
|
22
|
+
response[:state] = g_best_match.state
|
23
|
+
end
|
24
|
+
if n_results.any?
|
25
|
+
n_best_match = if g_results.any?
|
26
|
+
matching_g_results = n_results.select{|r|
|
27
|
+
(r.city.present? && r.city == g_best_match.city) ||
|
28
|
+
(r.state.present? && r.state == g_best_match.state) }
|
29
|
+
if matching_g_results.any? then
|
30
|
+
matching_g_results.first
|
31
|
+
else n_results.first end
|
32
|
+
else n_results.first end
|
33
|
+
response[:county] = n_best_match.county
|
34
|
+
response[:city] ||= n_best_match.city
|
35
|
+
response[:state] ||= n_best_match.state
|
36
|
+
end
|
37
|
+
end
|
38
|
+
response
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def apply_location_filtering(target)
|
43
|
+
return target unless params.key? :zs_location
|
44
|
+
zl = Location.find_by(id: params[:zlid].to_i)
|
45
|
+
q = params[:location_search][:q]
|
46
|
+
zl_association = if has_zipsearch_locations? target
|
47
|
+
:zipsearch_locations
|
48
|
+
elsif has_zipsearch_location? target
|
49
|
+
:zipsearch_location
|
50
|
+
end
|
51
|
+
return target if zl_association.nil?
|
52
|
+
wq = case params[:by]
|
53
|
+
when 'zip' then ['zipsearch_locations.zip = ?', q]
|
54
|
+
when 'county'
|
55
|
+
['zipsearch_locations.county = ? AND zipsearch_locations.state = ?',
|
56
|
+
zl.county, zl.state]
|
57
|
+
when 'city'
|
58
|
+
['zipsearch_locations.city = ? AND zipsearch_locations.state = ?',
|
59
|
+
zl.city, zl.state]
|
60
|
+
when 'state' then ['zipsearch_locations.state = ?', q]
|
61
|
+
end
|
62
|
+
return target unless wq.present?
|
63
|
+
target.joins(zl_association).where(*wq)
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
def has_zipsearch_locations?(klass, name=:zipsearch_locations)
|
68
|
+
klass.reflect_on_all_associations(:has_many)
|
69
|
+
.any?{|a| a.name == name }
|
70
|
+
end
|
71
|
+
def has_zipsearch_location?(klass, name=:zipsearch_location)
|
72
|
+
klass.reflect_on_all_associations(:has_one)
|
73
|
+
.any?{|a| a.name == name } ||
|
74
|
+
klass.reflect_on_all_associations(:belongs_to)
|
75
|
+
.any?{|a| a.name == name }
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'zip_search'
|
2
|
+
|
3
|
+
module ZipSearch
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
isolate_namespace ZipSearch
|
6
|
+
|
7
|
+
require 'zip_search/controller_helpers'
|
8
|
+
require 'zip_search/model_helpers'
|
9
|
+
require 'zip_search/view_helpers'
|
10
|
+
require 'zip_search/simple_form_helper'
|
11
|
+
|
12
|
+
initializer 'zip_search.controller_helpers' do
|
13
|
+
ActionController::Base.send :include, ControllerHelpers
|
14
|
+
end
|
15
|
+
initializer 'zip_search.model_helpers' do
|
16
|
+
ActiveRecord::Base.send :include, ModelHelpers
|
17
|
+
end
|
18
|
+
initializer 'zip_search.view_helpers' do
|
19
|
+
ActionView::Base.send :include, ViewHelpers
|
20
|
+
if defined? SimpleForm
|
21
|
+
ActionView::Base.send :include, SimpleFormHelper
|
22
|
+
SimpleFormHelper::METHODS_TO_EXPORT.each do |m|
|
23
|
+
SimpleForm::FormBuilder.send :define_method, m do |*args, &block|
|
24
|
+
template.send m, self, *args, &block
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
initializer :append_migrations do |app|
|
30
|
+
unless app.root.to_s == root.to_s
|
31
|
+
config.paths['db/migrate'].expanded.each do |expanded_path|
|
32
|
+
app.config.paths['db/migrate'] << expanded_path
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'zip_search'
|
2
|
+
|
3
|
+
module ZipSearch
|
4
|
+
module HasLocations
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def has_location(title=:location, options={})
|
9
|
+
has_one title, -> { where zs_association_name: title.to_s },
|
10
|
+
as: :locatable, class_name: 'ZipSearch::Location'
|
11
|
+
initialize_location_ownership! title
|
12
|
+
end
|
13
|
+
|
14
|
+
def has_locations(title=:locations, options={})
|
15
|
+
has_many title, -> { where zs_association_name: title.to_s },
|
16
|
+
as: :locatable, class_name: 'ZipSearch::Location'
|
17
|
+
initialize_locations_ownership! title
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_location_history(title=:location_history)
|
21
|
+
has_locations title
|
22
|
+
has_location :current_location
|
23
|
+
before_validation if: -> { current_location && current_location.changed? } do
|
24
|
+
historical_attrs = {}
|
25
|
+
current_location.changes.each{|k, (oldval, _)|
|
26
|
+
historical_attrs[k] = oldval unless oldval.blank? }
|
27
|
+
if historical_attrs.any?
|
28
|
+
self.location_history << Location.new( Hash[
|
29
|
+
current_location.changes.map{|k, (v, _)| [ k, v ] } ].merge(
|
30
|
+
zs_association_name: 'location_history') )
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def zipsearch_associations
|
36
|
+
reflect_on_all_associations.select{|a| a.foreign_key == 'locatable_id' }
|
37
|
+
end
|
38
|
+
def zipsearch_association_names
|
39
|
+
zipsearch_associations.map(&:name) end
|
40
|
+
def zipsearch_association(title=nil)
|
41
|
+
zipsearch_associations.find{|za| title.nil? || za.name == title } end
|
42
|
+
|
43
|
+
def has_zipsearch_association?(title=nil)
|
44
|
+
return zipsearch_associations.any? if title.nil?
|
45
|
+
zipsearch_association_names.include?(title)
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize_location_ownership!(title=:location)
|
49
|
+
include HasLocations::LocalInstanceMethods
|
50
|
+
accepts_nested_attributes_for title, allow_destroy: true,
|
51
|
+
reject_if: :"should_reject_#{title}?"
|
52
|
+
before_validation :prune_empty_zs_locations
|
53
|
+
after_initialize if: :"should_build_#{title}?" do
|
54
|
+
send :"build_#{title}", zs_association_name: title end
|
55
|
+
end
|
56
|
+
def initialize_locations_ownership!(title=:locations)
|
57
|
+
include HasLocations::LocalInstanceMethods
|
58
|
+
accepts_nested_attributes_for title, allow_destroy: true,
|
59
|
+
reject_if: :"should_reject_#{title}?"
|
60
|
+
before_validation :prune_empty_zs_locations
|
61
|
+
unless title == :location_history
|
62
|
+
after_initialize if: :"should_build_#{title}?" do
|
63
|
+
send(title).build zs_association_name: title end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module LocalInstanceMethods
|
69
|
+
|
70
|
+
ZipSearch::ASSOCIATION_MODES.each do |am|
|
71
|
+
define_method :"should_build_#{am}?" do
|
72
|
+
respond_to?(am) && send(am).nil? end
|
73
|
+
define_method :"should_reject_#{am}?" do |attributed|
|
74
|
+
self.class.zipsearch_association(am).klass
|
75
|
+
.location_fields.select{|lf| !attributed[lf].blank? }.empty? end
|
76
|
+
end
|
77
|
+
|
78
|
+
def prune_empty_zs_locations
|
79
|
+
self.class.zipsearch_associations.each{|zsa|
|
80
|
+
Array(self.send(zsa.name)).each {|zsl|
|
81
|
+
zsl.delete if zsl.new_record? && zsl.location_blank? } }
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'zip_search'
|
2
|
+
require 'zip_search/acts_as_location'
|
3
|
+
require 'zip_search/has_locations'
|
4
|
+
|
5
|
+
module ZipSearch
|
6
|
+
module ModelHelpers
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
include ActsAsLocation
|
10
|
+
include HasLocations
|
11
|
+
|
12
|
+
# DENVER = [39.7643389, -104.8551114]
|
13
|
+
# def self.random_address
|
14
|
+
# loop do
|
15
|
+
# random_point = Geocoder::Calculations.random_point_near(DENVER, 30)
|
16
|
+
# query = Geocoder::Query.new(random_point)
|
17
|
+
# results = query.execute
|
18
|
+
# results.select! do |r|
|
19
|
+
# addr = r.data['formatted_address']
|
20
|
+
# addr.present? && !(addr =~ /Unnamed Road/) && r.data['address_components'].select{|c| c['types'].include?('street_number') }.any?
|
21
|
+
# end
|
22
|
+
# next unless results.any?
|
23
|
+
# break results[0]# .data['formatted_address']
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'zip_search'
|
2
|
+
|
3
|
+
module ZipSearch
|
4
|
+
require 'rails'
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer 'zip_search.insert_into_active_record' do |app|
|
7
|
+
ActiveSupport.on_load :active_record do
|
8
|
+
ZipSearch::Railtie.insert
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.insert
|
13
|
+
if defined?(ActiveRecord)
|
14
|
+
# ActiveRecord::Base.send(:include, ZipSearch::Glue)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|