public
Fork of DrMark/ultrasphinx
Description: a maintained fork of Evan Weaver's Ultrasphinx code -- see the escape_sql branch
Homepage: http://blog.evanweaver.com/files/doc/fauna/ultrasphinx/files/README.html
Clone URL: git://github.com/github/ultrasphinx.git
Clean up and apply geodistance patch (Jeremy Seitz, Mark Lane).


git-svn-id: svn://rubyforge.org/var/svn/fauna/ultrasphinx/trunk@1759 
c1ad287d-65d5-445d-b84c-e64f8492f1e6
evanweaver (author)
Sun Mar 23 23:21:30 -0700 2008
commit  1a5e3ef307a2e3b29be3120d7900e1364d3d70cb
tree    7d6f963dd49aa0a292761bca00af0f6a778fbdc7
parent  20d51b2508b5a6b173ccc9ff41423ae18d4cf268
...
1
 
 
2
3
4
...
1
2
3
4
5
6
0
@@ -1,4 +1,6 @@
0
 
0
+v1.9.2. Add geodistance support (Jeremy Seitz, Mark Lane).
0
+
0
 v1.9.1. Add ultrasphinx:index:merge task for index merging, and a note in DEPLOYMENT_NOTES about how to use it.
0
 
0
 v1.9. Delta indexing. ERb now supported in .base files. Allow setting the searched indexes at runtime.
...
37
38
39
 
40
41
42
...
71
72
73
74
75
76
77
...
82
83
84
85
 
 
86
87
88
...
115
116
117
 
 
118
119
120
...
125
126
127
 
128
129
130
...
153
154
155
 
156
157
158
...
166
167
168
 
 
169
170
171
...
179
180
181
 
182
183
184
...
37
38
39
40
41
42
43
...
72
73
74
 
75
76
77
...
82
83
84
 
85
86
87
88
89
...
116
117
118
119
120
121
122
123
...
128
129
130
131
132
133
134
...
157
158
159
160
161
162
163
...
171
172
173
174
175
176
177
178
...
186
187
188
189
190
191
192
0
@@ -37,6 +37,7 @@ test/integration/app/app/helpers/application_helper.rb
0
 test/integration/app/app/helpers/sellers_helper.rb
0
 test/integration/app/app/helpers/states_helper.rb
0
 test/integration/app/app/helpers/users_helper.rb
0
+test/integration/app/app/models/category.rb
0
 test/integration/app/app/models/geo/address.rb
0
 test/integration/app/app/models/geo/country.rb
0
 test/integration/app/app/models/geo/state.rb
0
@@ -71,7 +72,6 @@ test/integration/app/config/environments/test.rb
0
 test/integration/app/config/locomotive.yml
0
 test/integration/app/config/routes.rb
0
 test/integration/app/config/ultrasphinx/default.base
0
-test/integration/app/config/ultrasphinx/development.conf
0
 test/integration/app/config/ultrasphinx/development.conf.canonical
0
 test/integration/app/db/migrate/001_create_users.rb
0
 test/integration/app/db/migrate/002_create_sellers.rb
0
@@ -82,7 +82,8 @@ test/integration/app/db/migrate/006_add_deleted_to_user.rb
0
 test/integration/app/db/migrate/007_add_lat_and_long_to_address.rb
0
 test/integration/app/db/migrate/008_add_mission_statement_to_seller.rb
0
 test/integration/app/db/migrate/009_create_countries.rb
0
-test/integration/app/db/schema.rb
0
+test/integration/app/db/migrate/010_create_categories.rb
0
+test/integration/app/db/migrate/011_categories_sellers.rb
0
 test/integration/app/doc/README_FOR_APP
0
 test/integration/app/public/404.html
0
 test/integration/app/public/500.html
0
@@ -115,6 +116,8 @@ test/integration/app/script/process/spawner
0
 test/integration/app/script/runner
0
 test/integration/app/script/server
0
 test/integration/app/test/fixtures/addresses.yml
0
+test/integration/app/test/fixtures/categories.yml
0
+test/integration/app/test/fixtures/categories_sellers.yml
0
 test/integration/app/test/fixtures/countries.yml
0
 test/integration/app/test/fixtures/sellers.yml
0
 test/integration/app/test/fixtures/states.yml
0
@@ -125,6 +128,7 @@ test/integration/app/test/functional/states_controller_test.rb
0
 test/integration/app/test/functional/users_controller_test.rb
0
 test/integration/app/test/test_helper.rb
0
 test/integration/app/test/unit/address_test.rb
0
+test/integration/app/test/unit/category_test.rb
0
 test/integration/app/test/unit/country_test.rb
0
 test/integration/app/test/unit/seller_test.rb
0
 test/integration/app/test/unit/state_test.rb
0
@@ -153,6 +157,7 @@ vendor/riddle/README
0
 vendor/riddle/spec/fixtures/data/anchor.bin
0
 vendor/riddle/spec/fixtures/data/any.bin
0
 vendor/riddle/spec/fixtures/data/boolean.bin
0
+vendor/riddle/spec/fixtures/data/comment.bin
0
 vendor/riddle/spec/fixtures/data/distinct.bin
0
 vendor/riddle/spec/fixtures/data/field_weights.bin
0
 vendor/riddle/spec/fixtures/data/filter.bin
0
@@ -166,6 +171,8 @@ vendor/riddle/spec/fixtures/data/filter_range_exclude.bin
0
 vendor/riddle/spec/fixtures/data/group.bin
0
 vendor/riddle/spec/fixtures/data/index.bin
0
 vendor/riddle/spec/fixtures/data/index_weights.bin
0
+vendor/riddle/spec/fixtures/data/keywords_with_hits.bin
0
+vendor/riddle/spec/fixtures/data/keywords_without_hits.bin
0
 vendor/riddle/spec/fixtures/data/phrase.bin
0
 vendor/riddle/spec/fixtures/data/rank_mode.bin
0
 vendor/riddle/spec/fixtures/data/simple.bin
0
@@ -179,6 +186,7 @@ vendor/riddle/spec/fixtures/sql/conf.example.yml
0
 vendor/riddle/spec/fixtures/sql/data.sql
0
 vendor/riddle/spec/fixtures/sql/structure.sql
0
 vendor/riddle/spec/functional/excerpt_spec.rb
0
+vendor/riddle/spec/functional/keywords_spec.rb
0
 vendor/riddle/spec/functional/search_spec.rb
0
 vendor/riddle/spec/functional/update_spec.rb
0
 vendor/riddle/spec/spec_helper.rb
0
...
32
33
34
 
35
36
37
...
32
33
34
35
36
37
38
0
@@ -32,6 +32,7 @@ Features include:
0
 * spellcheck
0
 * faceting on text, date, and numeric fields
0
 * field weighting, merging, and aliases
0
+* sorting and filtering by geodistance
0
 * <tt>belongs_to</tt> and <tt>has_many</tt> includes
0
 * drop-in compatibility with will_paginate[http://err.lighthouseapp.com/projects/466/home]
0
 * drop-in compatibility with Interlock[http://blog.evanweaver.com/files/doc/fauna/interlock/]
...
34
35
36
37
 
38
39
40
...
52
53
54
55
 
56
57
58
...
34
35
36
 
37
38
39
40
...
52
53
54
 
55
56
57
58
0
@@ -34,7 +34,7 @@ searchd
0
 {
0
   # What interface the search daemon should listen on and where to store its logs
0
   address = 0.0.0.0
0
- port = 3312
0
+ port = 3313
0
   seamless_rotate = 1
0
   log = <%= path %>log/searchd.log
0
   query_log = <%= path %>log/query.log
0
@@ -52,7 +52,7 @@ client
0
   
0
   # How your application connects to the search daemon (not necessarily the same as above)
0
   server_host = localhost
0
- server_port = 3312
0
+ server_port = 3313
0
 }
0
 
0
 # Individual SQL source options
...
42
43
44
 
45
46
 
 
47
48
49
50
 
51
52
53
54
55
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
58
59
...
108
109
110
111
 
 
 
 
 
 
112
113
114
...
262
263
264
 
 
265
266
267
268
 
 
 
 
 
 
269
270
271
272
273
...
42
43
44
45
46
47
48
49
50
51
52
 
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
...
139
140
141
 
142
143
144
145
146
147
148
149
150
...
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
 
314
315
316
0
@@ -42,18 +42,49 @@ The hash lets you customize internal aspects of the search.
0
 <tt>:weights</tt>:: A hash. Text-field names and associated query weighting. The default weight for every field is 1.0. Example: <tt>:weights => {'title' => 2.0}</tt>
0
 <tt>:filters</tt>:: A hash. Names of numeric or date fields and associated values. You can use a single value, an array of values, or a range. (See the bottom of the ActiveRecord::Base page for an example.)
0
 <tt>:facets</tt>:: An array of fields for grouping/faceting. You can access the returned facet values and their result counts with the <tt>facets</tt> method.
0
+<tt>:location</tt>:: A hash. Specify the names of your latititude and longitude attributes as declared in your is_indexed calls. To sort the results by distance, set <tt>:sort_mode => 'extended'</tt> and <tt>:sort_by => 'distance asc'.</tt>
0
 <tt>:indexes</tt>:: An array of indexes to search. Currently only <tt>Ultrasphinx::MAIN_INDEX</tt> and <tt>Ultrasphinx::DELTA_INDEX</tt> are available. Defaults to both; changing this is rarely needed.
0
 
0
+== Query Defaults
0
+
0
 Note that you can set up your own query defaults in <tt>environment.rb</tt>:
0
   
0
   self.class.query_defaults = HashWithIndifferentAccess.new({
0
- :per_page => 10,
0
+ :per_page => 10,c
0
     :sort_mode => 'relevance',
0
     :weights => {'title' => 2.0}
0
   })
0
 
0
 = Advanced features
0
 
0
+== Geographic distance
0
+
0
+If you pass a <tt>:location</tt> Hash, distance from the location in meters will be available in your result records via the <tt>distance</tt> accessor:
0
+
0
+ @search = Ultrasphinx::Search.new(:class_names => 'Point',
0
+ :query => 'pizza',
0
+ :sort_mode => 'extended',
0
+ :sort_by => 'distance',
0
+ :location => {
0
+ :lat => 40.3,
0
+ :long => -73.6
0
+ })
0
+
0
+ @search.run.first.distance #=> 1402.4
0
+
0
+Note that Sphinx expects lat/long to be indexed as radians. If you have degrees in your database, do the conversion in the <tt>is_indexed</tt> as so:
0
+
0
+ is_indexed 'fields' => [
0
+ 'name',
0
+ 'description',
0
+ {:field => 'lat', :function_sql => "RADIANS(?)"},
0
+ {:field => 'lng', :function_sql => "RADIANS(?)"}
0
+ ]
0
+
0
+Then, set <tt>Ultrasphinx::Search.client_options[:location][:units] = 'degrees'</tt>.
0
+
0
+The <tt>:double</tt> column type is recommended for storing location data.
0
+
0
 == Interlock integration
0
   
0
 Ultrasphinx uses the <tt>find_all_by_id</tt> method to instantiate records. If you set <tt>with_finders: true</tt> in {Interlock's}[http://blog.evanweaver.com/files/doc/fauna/interlock] <tt>config/memcached.yml</tt>, Interlock overrides <tt>find_all_by_id</tt> with a caching version.
0
@@ -108,7 +139,12 @@ Note that your database is never changed by anything Ultrasphinx does.
0
       :weights => {},
0
       :class_names => [],
0
       :filters => {},
0
- :facets => []
0
+ :facets => [],
0
+ :location => HashWithIndifferentAccess.new({
0
+ :lat_attribute_name => 'lat',
0
+ :long_attribute_name => 'lng',
0
+ :units => 'radians'
0
+ })
0
     })
0
     
0
     cattr_accessor :excerpting_options
0
@@ -262,12 +298,19 @@ Note that your database is never changed by anything Ultrasphinx does.
0
       opts = Hash[HashWithIndifferentAccess.new(opts._deep_dup._coerce_basic_types)]
0
       unless self.class.query_defaults.instance_of? Hash
0
         self.class.query_defaults = Hash[self.class.query_defaults]
0
+ self.class.query_defaults['location'] = Hash[self.class.query_defaults['location']]
0
+
0
         self.class.client_options = Hash[self.class.client_options]
0
         self.class.excerpting_options = Hash[self.class.excerpting_options]
0
         self.class.excerpting_options['content_methods'].map! {|ary| ary.map {|m| m.to_s}}
0
       end
0
+
0
+ # We need an annoying deep merge on the :location parameter
0
+ opts['location'].reverse_merge!(self.class.query_defaults['location']) if opts['location']
0
+
0
+ # Merge the rest of the defaults
0
+ @options = self.class.query_defaults.merge(opts)
0
       
0
- @options = self.class.query_defaults.merge(opts)
0
       @options['query'] = @options['query'].to_s
0
       @options['class_names'] = Array(@options['class_names'])
0
       @options['facets'] = Array(@options['facets'])
...
2
3
4
 
 
 
5
6
7
...
11
12
13
 
 
14
15
16
...
20
21
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
24
 
 
 
 
 
 
 
25
26
27
...
65
66
67
68
 
 
69
70
71
72
73
 
 
 
 
 
 
 
74
 
 
75
76
77
...
292
293
294
 
 
 
 
 
 
 
 
 
 
295
296
297
...
335
336
337
 
 
 
 
338
339
340
...
2
3
4
5
6
7
8
9
10
...
14
15
16
17
18
19
20
21
...
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
...
91
92
93
 
94
95
96
 
 
 
 
97
98
99
100
101
102
103
104
105
106
107
108
109
...
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
...
377
378
379
380
381
382
383
384
385
386
0
@@ -2,6 +2,9 @@
0
 module Ultrasphinx
0
   class Search
0
     module Internals
0
+
0
+ INFINITY = 1/0.0
0
+
0
       include Associations
0
 
0
       # These methods are kept stateless to ease debugging
0
@@ -11,6 +14,8 @@ module Ultrasphinx
0
       def build_request_with_options opts
0
       
0
         request = Riddle::Client.new
0
+
0
+ # Basic options
0
         request.instance_eval do
0
           @server = Ultrasphinx::CLIENT_SETTINGS['server_host']
0
           @port = Ultrasphinx::CLIENT_SETTINGS['server_port']
0
@@ -20,8 +25,29 @@ module Ultrasphinx
0
           @max_matches = [@offset + @limit + Ultrasphinx::Search.client_options['max_matches_offset'], MAX_MATCHES].min
0
         end
0
           
0
+ # Geosearch location
0
+ loc = opts['location']
0
+ loc.stringify_keys!
0
+ lat, long = loc['lat'], loc['long']
0
+ if lat and long
0
+ # Convert degrees to radians, if requested
0
+ if loc['units'] == 'degrees'
0
+ lat = degrees_to_radians(lat)
0
+ long = degrees_to_radians(long)
0
+ end
0
+ # Set the location/anchor point
0
+ request.set_anchor(loc['lat_attribute_name'], lat, loc['long_attribute_name'], long)
0
+ end
0
+
0
         # Sorting
0
         sort_by = opts['sort_by']
0
+ if options['location']
0
+ case sort_by
0
+ when "distance asc", "distance" then sort_by = "@geodist asc"
0
+ when "distance desc" then sort_by = "@geodist desc"
0
+ end
0
+ end
0
+
0
         # Use the additional sortable column if it is a text type
0
         sort_by += "_sortable" if Fields.instance.types[sort_by] == "text"
0
         
0
@@ -65,13 +91,19 @@ module Ultrasphinx
0
         end
0
 
0
         # Extract raw filters
0
- # XXX This is poorly done. We should coerce based on the Field types, not the value class
0
+ # XXX This is poorly done. We should coerce based on the Field types, not the value class.
0
+ # That would also allow us to move numeric filters from the query string into the hash.
0
         Array(opts['filters']).each do |field, value|
0
- field = field.to_s
0
- type = Fields.instance.types[field]
0
- unless type
0
- raise UsageError, "field #{field.inspect} is invalid"
0
+
0
+ field = field.to_s
0
+ type = Fields.instance.types[field]
0
+
0
+ # Special derived attribute
0
+ if field == 'distance' and options['location']
0
+ field, type = '@geodist', 'float'
0
           end
0
+
0
+ raise UsageError, "field #{field.inspect} is invalid" unless type
0
           
0
           begin
0
             case value
0
@@ -292,6 +324,16 @@ module Ultrasphinx
0
             end
0
           end
0
         end
0
+
0
+ # Add an accessor for distance, if requested
0
+ if self.options['location']['lat'] and self.options['location']['long']
0
+ results.each_with_index do |result, index|
0
+ if result
0
+ distance = (response[:matches][index][:attributes]['@geodist'] or INFINITY)
0
+ result.instance_variable_get('@attributes')['distance'] = distance
0
+ end
0
+ end
0
+ end
0
         
0
         results.compact!
0
         
0
@@ -335,6 +377,10 @@ module Ultrasphinx
0
         # Also removes apostrophes in the middle of words so that they don't get split in two.
0
         s.gsub(/(^|\s)(AND|OR|NOT|\@\w+)(\s|$)/i, "").gsub(/(\w)\'(\w)/, '\1\2')
0
       end
0
+
0
+ def degrees_to_radians(value)
0
+ Math::PI * value / 180.0
0
+ end
0
     
0
     end
0
   end
...
2
3
4
5
 
6
7
8
...
13
14
15
16
 
17
18
19
...
24
25
26
27
 
28
29
30
...
34
35
36
37
 
38
39
40
41
42
43
 
44
45
46
47
48
 
49
50
51
...
57
58
59
60
 
61
62
63
64
65
 
66
67
68
...
74
75
76
77
 
78
79
80
...
2
3
4
 
5
6
7
8
...
13
14
15
 
16
17
18
19
...
24
25
26
 
27
28
29
30
...
34
35
36
 
37
38
39
40
41
42
 
43
44
45
46
47
 
48
49
50
51
...
57
58
59
 
60
61
62
63
64
 
65
66
67
68
...
74
75
76
 
77
78
79
80
0
@@ -2,7 +2,7 @@ class AddressesController < ApplicationController
0
   # GET /addresses
0
   # GET /addresses.xml
0
   def index
0
- @addresses = Address.find(:all)
0
+ @addresses = Geo::Address.find(:all)
0
 
0
     respond_to do |format|
0
       format.html # index.html.erb
0
@@ -13,7 +13,7 @@ class AddressesController < ApplicationController
0
   # GET /addresses/1
0
   # GET /addresses/1.xml
0
   def show
0
- @address = Address.find(params[:id])
0
+ @address = Geo::Address.find(params[:id])
0
 
0
     respond_to do |format|
0
       format.html # show.html.erb
0
@@ -24,7 +24,7 @@ class AddressesController < ApplicationController
0
   # GET /addresses/new
0
   # GET /addresses/new.xml
0
   def new
0
- @address = Address.new
0
+ @address = Geo::Address.new
0
 
0
     respond_to do |format|
0
       format.html # new.html.erb
0
@@ -34,18 +34,18 @@ class AddressesController < ApplicationController
0
 
0
   # GET /addresses/1/edit
0
   def edit
0
- @address = Address.find(params[:id])
0
+ @address = Geo::Address.find(params[:id])
0
   end
0
 
0
   # POST /addresses
0
   # POST /addresses.xml
0
   def create
0
- @address = Address.new(params[:address])
0
+ @address = Geo::Address.new(params[:address])
0
 
0
     respond_to do |format|
0
       if @address.save
0
         flash[:notice] = 'Address was successfully created.'
0
- format.html { redirect_to(@address) }
0
+ format.html { redirect_to(address_path(@address)) }
0
         format.xml { render :xml => @address, :status => :created, :location => @address }
0
       else
0
         format.html { render :action => "new" }
0
@@ -57,12 +57,12 @@ class AddressesController < ApplicationController
0
   # PUT /addresses/1
0
   # PUT /addresses/1.xml
0
   def update
0
- @address = Address.find(params[:id])
0
+ @address = Geo::Address.find(params[:id])
0
 
0
     respond_to do |format|
0
       if @address.update_attributes(params[:address])
0
         flash[:notice] = 'Address was successfully updated.'
0
- format.html { redirect_to(@address) }
0
+ format.html { redirect_to(address_path(@address)) }
0
         format.xml { head :ok }
0
       else
0
         format.html { render :action => "edit" }
0
@@ -74,7 +74,7 @@ class AddressesController < ApplicationController
0
   # DELETE /addresses/1
0
   # DELETE /addresses/1.xml
0
   def destroy
0
- @address = Address.find(params[:id])
0
+ @address = Geo::Address.find(params[:id])
0
     @address.destroy
0
 
0
     respond_to do |format|
...
2
3
4
5
 
6
7
8
...
13
14
15
16
 
17
18
19
...
24
25
26
27
 
28
29
30
...
34
35
36
37
 
38
39
40
41
42
43
 
44
45
46
47
48
 
49
50
51
...
57
58
59
60
 
61
62
63
64
65
 
66
67
68
...
74
75
76
77
 
78
79
80
...
2
3
4
 
5
6
7
8
...
13
14
15
 
16
17
18
19
...
24
25
26
 
27
28
29
30
...
34
35
36
 
37
38
39
40
41
42
 
43
44
45
46
47
 
48
49
50
51
...
57
58
59
 
60
61
62
63
64
 
65
66
67
68
...
74
75
76
 
77
78
79
80
0
@@ -2,7 +2,7 @@ class StatesController < ApplicationController
0
   # GET /states
0
   # GET /states.xml
0
   def index
0
- @states = State.find(:all)
0
+ @states = Geo::State.find(:all)
0
 
0
     respond_to do |format|
0
       format.html # index.html.erb
0
@@ -13,7 +13,7 @@ class StatesController < ApplicationController
0
   # GET /states/1
0
   # GET /states/1.xml
0
   def show
0
- @state = State.find(params[:id])
0
+ @state = Geo::State.find(params[:id])
0
 
0
     respond_to do |format|
0
       format.html # show.html.erb
0
@@ -24,7 +24,7 @@ class StatesController < ApplicationController
0
   # GET /states/new
0
   # GET /states/new.xml
0
   def new
0
- @state = State.new
0
+ @state = Geo::State.new
0
 
0
     respond_to do |format|
0
       format.html # new.html.erb
0
@@ -34,18 +34,18 @@ class StatesController < ApplicationController
0
 
0
   # GET /states/1/edit
0
   def edit
0
- @state = State.find(params[:id])
0
+ @state = Geo::State.find(params[:id])
0
   end
0
 
0
   # POST /states
0
   # POST /states.xml
0
   def create
0
- @state = State.new(params[:state])
0
+ @state = Geo::State.new(params[:state])
0
 
0
     respond_to do |format|
0
       if @state.save
0
         flash[:notice] = 'State was successfully created.'
0
- format.html { redirect_to(@state) }
0
+ format.html { redirect_to(state_path(@state)) }
0
         format.xml { render :xml => @state, :status => :created, :location => @state }
0
       else
0
         format.html { render :action => "new" }
0
@@ -57,12 +57,12 @@ class StatesController < ApplicationController
0
   # PUT /states/1
0
   # PUT /states/1.xml
0
   def update
0
- @state = State.find(params[:id])
0
+ @state = Geo::State.find(params[:id])
0
 
0
     respond_to do |format|
0
       if @state.update_attributes(params[:state])
0
         flash[:notice] = 'State was successfully updated.'
0
- format.html { redirect_to(@state) }
0
+ format.html { redirect_to(state_path(@state)) }
0
         format.xml { head :ok }
0
       else
0
         format.html { render :action => "edit" }
0
@@ -74,7 +74,7 @@ class StatesController < ApplicationController
0
   # DELETE /states/1
0
   # DELETE /states/1.xml
0
   def destroy
0
- @state = State.find(params[:id])
0
+ @state = Geo::State.find(params[:id])
0
     @state.destroy
0
 
0
     respond_to do |format|
...
2
3
4
5
 
6
7
8
...
2
3
4
 
5
6
7
8
0
@@ -2,7 +2,7 @@ class Geo::Address < ActiveRecord::Base
0
   belongs_to :user
0
   belongs_to :state, :class_name => 'Geo::State'
0
   
0
- is_indexed 'fields' => ['name'],
0
+ is_indexed 'fields' => ['name', {:field => 'lat', :function_sql => "RADIANS(?)"}, {:field => 'lng', :function_sql => "RADIANS(?)"}],
0
     'concatenate' => [{'fields' => ['line_1', 'line_2', 'city', 'province_region', 'zip_postal_code'], 'as' => 'content'}],
0
     'include' => [{'association_name' => 'state', 'field' => 'name', 'as' => 'state'}],
0
     'delta' => true
...
2
3
4
5
 
6
7
8
9
10
11
 
12
...
2
3
4
 
5
6
7
8
9
10
 
11
12
0
@@ -2,11 +2,11 @@
0
 
0
 <%= error_messages_for :address %>
0
 
0
-<% form_for(@address) do |f| %>
0
+<% form_for @address, :url => address_path(@address) do |f| %>
0
   <p>
0
     <%= f.submit "Update" %>
0
   </p>
0
 <% end %>
0
 
0
-<%= link_to 'Show', @address %> |
0
+<%= link_to 'Show', address_path(@address) %> |
0
 <%= link_to 'Back', addresses_path %>
...
6
7
8
9
 
10
11
 
12
13
14
...
6
7
8
 
9
10
 
11
12
13
14
0
@@ -6,9 +6,9 @@
0
 
0
 <% for address in @addresses %>
0
   <tr>
0
- <td><%= link_to 'Show', address %></td>
0
+ <td><%= link_to 'Show', address_path(address) %></td>
0
     <td><%= link_to 'Edit', edit_address_path(address) %></td>
0
- <td><%= link_to 'Destroy', address, :confirm => 'Are you sure?', :method => :delete %></td>
0
+ <td><%= link_to 'Destroy', address_path(address), :confirm => 'Are you sure?', :method => :delete %></td>
0
   </tr>
0
 <% end %>
0
 </table>
...
2
3
4
5
 
6
7
8
...
2
3
4
 
5
6
7
8
0
@@ -2,7 +2,7 @@
0
 
0
 <%= error_messages_for :address %>
0
 
0
-<% form_for(@address) do |f| %>
0
+<% form_for @address, :url => address_path(@address) do |f| %>
0
   <p>
0
     <%= f.submit "Create" %>
0
   </p>
...
2
3
4
5
 
6
7
8
9
10
11
 
12
...
2
3
4
 
5
6
7
8
9
10
 
11
12
0
@@ -2,11 +2,11 @@
0
 
0
 <%= error_messages_for :state %>
0
 
0
-<% form_for(@state) do |f| %>
0
+<% form_for @state, :url => state_path(@state) do |f| %>
0
   <p>
0
     <%= f.submit "Update" %>
0
   </p>
0
 <% end %>
0
 
0
-<%= link_to 'Show', @state %> |
0
+<%= link_to 'Show', state_path(@state) %> |
0
 <%= link_to 'Back', states_path %>
...
7
8
9
10
 
11
12
 
13
14
15
...
7
8
9
 
10
11
 
12
13
14
15
0
@@ -7,9 +7,9 @@
0
 <% for state in @states %>
0
   <tr>
0
     <td><%= state.name %></td>
0
- <td><%= link_to 'Show', state %></td>
0
+ <td><%= link_to 'Show', state_path(state) %></td>
0
     <td><%= link_to 'Edit', edit_state_path(state) %></td>
0
- <td><%= link_to 'Destroy', state, :confirm => 'Are you sure?', :method => :delete %></td>
0
+ <td><%= link_to 'Destroy', state_path(state), :confirm => 'Are you sure?', :method => :delete %></td>
0
   </tr>
0
 <% end %>
0
 </table>
...
2
3
4
5
 
6
7
8
...
2
3
4
 
5
6
7
8
0
@@ -2,7 +2,7 @@
0
 
0
 <%= error_messages_for :state %>
0
 
0
-<% form_for(@state) do |f| %>
0
+<% form_for @state, :url => state_path(@state) do |f| %>
0
   <p>
0
     <%= f.submit "Create" %>
0
   </p>
...
7
8
9
10
11
12
 
 
 
13
14
15
...
7
8
9
 
 
 
10
11
12
13
14
15
0
@@ -7,9 +7,9 @@
0
 <% for user in @users %>
0
   <tr>
0
   <td><%= h(user.login) %></td>
0
- <td><%= h(user.address.line_1) %></td>
0
- <td><%= h(user.address.city) %></td>
0
- <td><%= h(user.address.state.name) %></td>
0
+ <td><%= h(user.address.line_1) if user