zip_search 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/javascripts/zip_search/init.js +16 -0
  6. data/app/assets/javascripts/zip_search/jquery.inputmask.bundle.js +2145 -0
  7. data/app/assets/javascripts/zip_search/locations.coffee +69 -0
  8. data/app/assets/stylesheets/zip_search/application.css +15 -0
  9. data/app/assets/stylesheets/zip_search/locations.css +4 -0
  10. data/app/assets/stylesheets/zip_search/typeahead.scss +72 -0
  11. data/app/controllers/zip_search/application_controller.rb +4 -0
  12. data/app/controllers/zip_search/locations_controller.rb +43 -0
  13. data/app/helpers/zip_search/application_helper.rb +4 -0
  14. data/app/helpers/zip_search/locations_helper.rb +4 -0
  15. data/app/models/zip_search/location.rb +9 -0
  16. data/app/views/layouts/zip_search/application.html.erb +14 -0
  17. data/app/views/zip_search/_simple_fields.html.erb +4 -0
  18. data/app/views/zip_search/locations/_search_field.html.erb +19 -0
  19. data/config/routes.rb +4 -0
  20. data/config/spring.rb +1 -0
  21. data/db/migrate/20150914014727_create_zip_search_locations.rb +17 -0
  22. data/db/migrate/20150920034901_add_zs_association_name_to_zip_search_locations.rb +7 -0
  23. data/lib/generators/zip_search/install/install_generator.rb +22 -0
  24. data/lib/tasks/zip_search_tasks.rake +4 -0
  25. data/lib/zip_search/acts_as_location.rb +61 -0
  26. data/lib/zip_search/capybara_helpers.rb +9 -0
  27. data/lib/zip_search/controller_helpers.rb +79 -0
  28. data/lib/zip_search/engine.rb +38 -0
  29. data/lib/zip_search/has_locations.rb +87 -0
  30. data/lib/zip_search/model_helpers.rb +29 -0
  31. data/lib/zip_search/railtie.rb +18 -0
  32. data/lib/zip_search/simple_form_helper.rb +50 -0
  33. data/lib/zip_search/strong_params_helper.rb +17 -0
  34. data/lib/zip_search/version.rb +3 -0
  35. data/lib/zip_search/view_helpers.rb +31 -0
  36. data/lib/zip_search.rb +62 -0
  37. data/spec/factories/travelers.rb +40 -0
  38. data/spec/features/travelers_spec.rb +68 -0
  39. data/spec/models/traveler_spec.rb +38 -0
  40. data/spec/models/zip_search/location_spec.rb +7 -0
  41. data/spec/spec_helper.rb +134 -0
  42. 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,4 @@
1
+ /*
2
+ Place all the styles related to the matching controller here.
3
+ They will automatically be included in application.css.
4
+ */
@@ -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,4 @@
1
+ module ZipSearch
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -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,4 @@
1
+ module ZipSearch
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module ZipSearch
2
+ module LocationsHelper
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module ZipSearch
2
+ class Location < ActiveRecord::Base
3
+
4
+ acts_as_location
5
+
6
+ belongs_to :locatable, polymorphic: true
7
+
8
+ end
9
+ 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,4 @@
1
+ <%= f.input :zip %>
2
+ <%= f.input :county %>
3
+ <%= f.input :city %>
4
+ <%= f.input :state, collection: ZipSearch::US_STATES %>
@@ -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
@@ -0,0 +1,4 @@
1
+ ZipSearch::Engine.routes.draw do
2
+ get '/location_search' => 'locations#location_search', as: :location_search
3
+ post '/zipcode_search' => 'locations#zipcode_search', as: :zipcode_search
4
+ end
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,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :zip_search do
3
+ # # Task goes here
4
+ # 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,9 @@
1
+ # require 'zipsearch'
2
+ MFaker = defined?(Faker) ? Faker : FFaker
3
+
4
+ def random_zip; rand(10000..99999).to_s end
5
+
6
+ module ZipSearch
7
+ module CapybaraHelpers
8
+ end
9
+ end
@@ -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