zip_search 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|