From aceed9ac83c6cbd0e6ed1a558280bbae4aeac86c Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 10:16:02 +0900 Subject: [PATCH 01/12] Add a scope `with_location` to Event. --- app/models/event.rb | 4 ++++ .../agent_views/user_location_agent/_show.html.erb | 2 +- spec/models/event_spec.rb | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/models/event.rb b/app/models/event.rb index f534ef05..df1bdcfa 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -28,6 +28,10 @@ class Event < ActiveRecord::Base where("expires_at IS NOT NULL AND expires_at < ?", Time.now) } + scope :with_location, -> { + where.not(lat: nil).where.not(lng: nil) + } + # Emit this event again, as a new Event. def reemit! agent.create_event :payload => payload, :lat => lat, :lng => lng diff --git a/app/views/agents/agent_views/user_location_agent/_show.html.erb b/app/views/agents/agent_views/user_location_agent/_show.html.erb index f1756ad8..503f76e5 100644 --- a/app/views/agents/agent_views/user_location_agent/_show.html.erb +++ b/app/views/agents/agent_views/user_location_agent/_show.html.erb @@ -2,7 +2,7 @@

Recent Event Map

-<% events = @agent.events.where("lat IS NOT null AND lng IS NOT null").order("id desc").limit(500) %> +<% events = @agent.events.with_location.order("id desc").limit(500) %> <% if events.length > 0 %>
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index c2e40c03..a5f3ddfb 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -1,6 +1,20 @@ require 'spec_helper' describe Event do + describe ".with_location" do + it "selects events with location" do + event = events(:bob_website_agent_event) + event.lat = 2 + event.lng = 3 + event.save! + Event.with_location.pluck(:id).should == [event.id] + + event.lat = nil + event.save! + Event.with_location.should be_empty + end + end + describe "#reemit" do it "creates a new event identical to itself" do events(:bob_website_agent_event).lat = 2 From d4bd3c7c8b821deb9846dd0d8a519df4bd92945a Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 10:36:27 +0900 Subject: [PATCH 02/12] Add Event#location. --- app/models/event.rb | 32 +++++++++++++++++++++++++ spec/models/event_spec.rb | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/app/models/event.rb b/app/models/event.rb index df1bdcfa..257ec207 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -32,6 +32,38 @@ class Event < ActiveRecord::Base where.not(lat: nil).where.not(lng: nil) } + def location + @location ||= { + # lat and lng are BigDecimal, so convert them to Float + lat: (lat.to_f if lat), + lng: (lng.to_f if lng), + radius: + begin + h = payload[:horizontal_accuracy].presence + v = payload[:vertical_accuracy].presence + if h && v + (h.to_f + v.to_f) / 2 + else + (h || v || payload[:accuracy]).to_f + end + end, + course: + begin + if (v = payload[:course].presence) && + (v = v.to_f) >= 0 + v + end + end, + speed: + begin + if (v = payload[:speed].presence) && + (v = v.to_f) >= 0 + v + end + end, + } + end + # Emit this event again, as a new Event. def reemit! agent.create_event :payload => payload, :lat => lat, :lng => lng diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index a5f3ddfb..3a13b623 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -15,6 +15,56 @@ describe Event do end end + describe "#location" do + it "returns a default hash when an event does not have a location" do + event = events(:bob_website_agent_event) + event.location.should == { + lat: nil, + lng: nil, + radius: 0.0, + speed: nil, + course: nil, + } + end + + it "returns a hash containing location information" do + event = events(:bob_website_agent_event) + event.lat = 2 + event.lng = 3 + event.payload = { + radius: 300, + speed: 0.5, + course: 90.0, + } + event.save! + event.location.should == { + lat: 2.0, + lng: 3.0, + radius: 0.0, + speed: 0.5, + course: 90.0, + } + end + + it "ignores invalid speed and course" do + event = events(:bob_website_agent_event) + event.lat = 2 + event.lng = 3 + event.payload = { + speed: -1, + course: -1, + } + event.save! + event.location.should == { + lat: 2.0, + lng: 3.0, + radius: 0.0, + speed: nil, + course: nil, + } + end + end + describe "#reemit" do it "creates a new event identical to itself" do events(:bob_website_agent_event).lat = 2 From 951648c94ea109402996eaa5246e4c5a3ac3844c Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 10:37:36 +0900 Subject: [PATCH 03/12] Refactor the map_marker partial. --- app/assets/javascripts/map_marker.js.coffee | 41 ++++++++++ .../user_location_agent/_show.html.erb | 7 +- app/views/events/show.html.erb | 4 +- app/views/shared/_map_marker.html.erb | 74 ++++--------------- config/environments/production.rb | 2 +- 5 files changed, 60 insertions(+), 68 deletions(-) create mode 100644 app/assets/javascripts/map_marker.js.coffee diff --git a/app/assets/javascripts/map_marker.js.coffee b/app/assets/javascripts/map_marker.js.coffee new file mode 100644 index 00000000..3b16daf7 --- /dev/null +++ b/app/assets/javascripts/map_marker.js.coffee @@ -0,0 +1,41 @@ +window.map_marker = (map, options = {}) -> + pos = new google.maps.LatLng(options.lat, options.lng) + + if options.radius > 0 + new google.maps.Circle + map: map + strokeColor: '#FF0000' + strokeOpacity: 0.8 + strokeWeight: 2 + fillColor: '#FF0000' + fillOpacity: 0.35 + center: pos + radius: options.radius + else + new google.maps.Marker + map: map + position: pos + title: 'Recorded Location' + + if options.course + p1 = new LatLon(pos.lat(), pos.lng()) + speed = if options.speed? then options.speed else 1 + p2 = p1.destinationPoint(options.course, Math.max(0.2, speed) * 0.1) + + lineCoordinates = [ + pos + new google.maps.LatLng(p2.lat(), p2.lon()) + ] + + lineSymbol = + path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW + + new google.maps.Polyline + map: map + path: lineCoordinates + icons: [ + { + icon: lineSymbol + offset: '100%' + } + ] diff --git a/app/views/agents/agent_views/user_location_agent/_show.html.erb b/app/views/agents/agent_views/user_location_agent/_show.html.erb index 503f76e5..abe950fa 100644 --- a/app/views/agents/agent_views/user_location_agent/_show.html.erb +++ b/app/views/agents/agent_views/user_location_agent/_show.html.erb @@ -14,11 +14,10 @@ }; var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions); + <% events.each do |event| %> + <%= render "shared/map_marker", map: 'map', location: event.location %> + <% end %> - - <% events.each do |event| %> - <%= render "shared/map_marker", event: event %> - <% end %> <% else %>

No events found. diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 8bfa52ce..279ac91c 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -36,9 +36,9 @@ }; var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions); - - <%= render "shared/map_marker", event: @event %> + <%= render "shared/map_marker", map: 'map', location: @event.location %> + <% end %>
diff --git a/app/views/shared/_map_marker.html.erb b/app/views/shared/_map_marker.html.erb index e288a591..7d02b3be 100644 --- a/app/views/shared/_map_marker.html.erb +++ b/app/views/shared/_map_marker.html.erb @@ -1,61 +1,13 @@ - \ No newline at end of file +<%- unless @map_marker_included_p -%> + <%- content_for :head do -%> + <%= javascript_include_tag "map_marker" %> + <%- end -%> + <%- @map_marker_included_p = true -%> +<%- end -%> + map_marker(<%= map %>, { + lat: <%= location[:lat].to_json %>, + lng: <%= location[:lng].to_json %>, + radius: <%= location[:radius].to_json %>, + course: <%= location[:course].to_json %>, + speed: <%= location[:speed].to_json %> + }); diff --git a/config/environments/production.rb b/config/environments/production.rb index a43a0775..51d0dc99 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -61,7 +61,7 @@ Huginn::Application.configure do end # Precompile additional assets (application.js.coffee.erb, application.css, and all non-JS/CSS are already added) - config.assets.precompile += %w( diagram.js graphing.js user_credentials.js ) + config.assets.precompile += %w( diagram.js graphing.js map_marker.js user_credentials.js ) # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. From 223f3a61e93b16d710ce77fe83169bc44b51cb81 Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 11:00:36 +0900 Subject: [PATCH 04/12] Compact map_marker.js and use Utils.jsonify. --- app/views/shared/_map_marker.html.erb | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/app/views/shared/_map_marker.html.erb b/app/views/shared/_map_marker.html.erb index 7d02b3be..7db4b459 100644 --- a/app/views/shared/_map_marker.html.erb +++ b/app/views/shared/_map_marker.html.erb @@ -1,13 +1,7 @@ <%- unless @map_marker_included_p -%> - <%- content_for :head do -%> - <%= javascript_include_tag "map_marker" %> - <%- end -%> - <%- @map_marker_included_p = true -%> +<%- content_for :head do -%> +<%= javascript_include_tag "map_marker" %> <%- end -%> - map_marker(<%= map %>, { - lat: <%= location[:lat].to_json %>, - lng: <%= location[:lng].to_json %>, - radius: <%= location[:radius].to_json %>, - course: <%= location[:course].to_json %>, - speed: <%= location[:speed].to_json %> - }); +<%- @map_marker_included_p = true -%> +<%- end -%> +map_marker(<%= map %>, <%= Utils.jsonify(location) %>) From d22cad0327803a61851b595cab9e84675abf3914 Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 15:20:24 +0900 Subject: [PATCH 05/12] Remove the map_marker partial, which is no longer useful. --- .../agents/agent_views/user_location_agent/_show.html.erb | 7 +++++-- app/views/events/show.html.erb | 7 +++++-- app/views/shared/_map_marker.html.erb | 7 ------- 3 files changed, 10 insertions(+), 11 deletions(-) delete mode 100644 app/views/shared/_map_marker.html.erb diff --git a/app/views/agents/agent_views/user_location_agent/_show.html.erb b/app/views/agents/agent_views/user_location_agent/_show.html.erb index abe950fa..37555cd9 100644 --- a/app/views/agents/agent_views/user_location_agent/_show.html.erb +++ b/app/views/agents/agent_views/user_location_agent/_show.html.erb @@ -1,4 +1,7 @@ - +<% content_for :head do -%> +<%= javascript_include_tag "https://maps.googleapis.com/maps/api/js?sensor=false" %> +<%= javascript_include_tag "map_marker" %> +<% end -%>

Recent Event Map

@@ -15,7 +18,7 @@ var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions); <% events.each do |event| %> - <%= render "shared/map_marker", map: 'map', location: event.location %> + map_marker(map, <%= Utils.jsonify(event.location) %>); <% end %> <% else %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 279ac91c..636e8254 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -16,7 +16,10 @@

<% if @event.lat && @event.lng %> - + <% content_for :head do -%> +<%= javascript_include_tag "https://maps.googleapis.com/maps/api/js?sensor=false" %> +<%= javascript_include_tag "map_marker" %> + <% end -%>

Lat: @@ -37,7 +40,7 @@ var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions); - <%= render "shared/map_marker", map: 'map', location: @event.location %> + map_marker(map, <%= Utils.jsonify(@event.location) %>); <% end %> diff --git a/app/views/shared/_map_marker.html.erb b/app/views/shared/_map_marker.html.erb deleted file mode 100644 index 7db4b459..00000000 --- a/app/views/shared/_map_marker.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -<%- unless @map_marker_included_p -%> -<%- content_for :head do -%> -<%= javascript_include_tag "map_marker" %> -<%- end -%> -<%- @map_marker_included_p = true -%> -<%- end -%> -map_marker(<%= map %>, <%= Utils.jsonify(location) %>) From 1784eeb38a5409dfd051afd743bdbc540eda893c Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 15:40:57 +0900 Subject: [PATCH 06/12] TIL: `x ? y` in CoffeeScript is a shorthand for `if x? then x else y`. --- app/assets/javascripts/map_marker.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/map_marker.js.coffee b/app/assets/javascripts/map_marker.js.coffee index 3b16daf7..146a805c 100644 --- a/app/assets/javascripts/map_marker.js.coffee +++ b/app/assets/javascripts/map_marker.js.coffee @@ -19,7 +19,7 @@ window.map_marker = (map, options = {}) -> if options.course p1 = new LatLon(pos.lat(), pos.lng()) - speed = if options.speed? then options.speed else 1 + speed = options.speed ? 1 p2 = p1.destinationPoint(options.course, Math.max(0.2, speed) * 0.1) lineCoordinates = [ From b25142e7931c102b74f0aa5ae8e8974f1c91af8f Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 19:06:55 +0900 Subject: [PATCH 07/12] Introduce a Location class. --- app/models/event.rb | 27 ++++--------- lib/location.rb | 82 +++++++++++++++++++++++++++++++++++++++ spec/lib/location_spec.rb | 49 +++++++++++++++++++++++ spec/models/event_spec.rb | 28 ++----------- 4 files changed, 143 insertions(+), 43 deletions(-) create mode 100644 lib/location.rb create mode 100644 spec/lib/location_spec.rb diff --git a/app/models/event.rb b/app/models/event.rb index 257ec207..178252f3 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,3 +1,5 @@ +require 'location' + # Events are how Huginn Agents communicate and log information about the world. Events can be emitted and received by # Agents. They contain a serialized `payload` of arbitrary JSON data, as well as optional `lat`, `lng`, and `expires_at` # fields. @@ -33,10 +35,10 @@ class Event < ActiveRecord::Base } def location - @location ||= { - # lat and lng are BigDecimal, so convert them to Float - lat: (lat.to_f if lat), - lng: (lng.to_f if lng), + @location ||= Location.new( + # lat and lng are BigDecimal, but converted to Float by the Location class + lat: lat, + lng: lng, radius: begin h = payload[:horizontal_accuracy].presence @@ -47,21 +49,8 @@ class Event < ActiveRecord::Base (h || v || payload[:accuracy]).to_f end end, - course: - begin - if (v = payload[:course].presence) && - (v = v.to_f) >= 0 - v - end - end, - speed: - begin - if (v = payload[:speed].presence) && - (v = v.to_f) >= 0 - v - end - end, - } + course: payload[:course], + speed: payload[:speed].presence) end # Emit this event again, as a new Event. diff --git a/lib/location.rb b/lib/location.rb new file mode 100644 index 00000000..74a4de51 --- /dev/null +++ b/lib/location.rb @@ -0,0 +1,82 @@ +Location = Struct.new(:lat, :lng, :radius, :speed, :course) + +class Location + protected :[]= + + def initialize(data = {}) + super() + + case data + when Array + raise ArgumentError, 'unsupported location data' unless data.size == 2 + self.lat, self.lng = data + when Hash, Location + data.each { |key, value| + begin + __send__("#{key}=", value) + rescue NameError + end + } + else + raise ArgumentError, 'unsupported location data' + end + + yield self if block_given? + end + + def lat=(value) + self[:lat] = floatify(value) { |f| + if f.abs <= 90 + f + else + raise ArgumentError, 'out of bounds' + end + } + end + + def lng=(value) + self[:lng] = floatify(value) { |f| + if f.abs <= 180 + f + else + raise ArgumentError, 'out of bounds' + end + } + end + + def radius=(value) + self[:radius] = floatify(value) { |f| f if f >= 0 } + end + + def speed=(value) + self[:speed] = floatify(value) { |f| f if f >= 0 } + end + + def course=(value) + self[:course] = floatify(value) { |f| f if (0..360).cover?(f) } + end + + def present? + lat && lng + end + + def empty? + !present? + end + + private + + def floatify(value) + case value + when nil, '' + return nil + else + float = Float(value) + if block_given? + yield(float) + else + float + end + end + end +end diff --git a/spec/lib/location_spec.rb b/spec/lib/location_spec.rb new file mode 100644 index 00000000..dfb3c8e3 --- /dev/null +++ b/spec/lib/location_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Location do + let(:location) { + Location.new( + lat: BigDecimal.new('2.0'), + lng: BigDecimal.new('3.0'), + radius: 300, + speed: 2, + course: 30) + } + + it "converts values to Float" do + expect(location.lat).to equal 2.0 + expect(location.lng).to equal 3.0 + expect(location.radius).to equal 300.0 + expect(location.speed).to equal 2.0 + expect(location.course).to equal 30.0 + end + + it "provides hash-style access to its properties with both symbol and string keys" do + expect(location[:lat]).to equal 2.0 + expect(location['lat']).to equal 2.0 + end + + it "does not allow hash-style assignment" do + expect { + location[:lat] = 2.0 + }.to raise_error + end + + it "ignores invalid values" do + location2 = Location.new( + lat: 2, + lng: 3, + radius: -1, + speed: -1, + course: -1) + expect(location2.radius).to be_nil + expect(location2.speed).to be_nil + expect(location2.course).to be_nil + end + + it "considers a location empty if either latitude or longitude is missing" do + expect(Location.new.empty?).to be_truthy + expect(Location.new(lat: 2, radius: 1).present?).to be_falsy + expect(Location.new(lng: 3, radius: 1).present?).to be_falsy + end +end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 3a13b623..a233fa4a 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -18,13 +18,12 @@ describe Event do describe "#location" do it "returns a default hash when an event does not have a location" do event = events(:bob_website_agent_event) - event.location.should == { + event.location.should == Location.new( lat: nil, lng: nil, radius: 0.0, speed: nil, - course: nil, - } + course: nil) end it "returns a hash containing location information" do @@ -37,31 +36,12 @@ describe Event do course: 90.0, } event.save! - event.location.should == { + event.location.should == Location.new( lat: 2.0, lng: 3.0, radius: 0.0, speed: 0.5, - course: 90.0, - } - end - - it "ignores invalid speed and course" do - event = events(:bob_website_agent_event) - event.lat = 2 - event.lng = 3 - event.payload = { - speed: -1, - course: -1, - } - event.save! - event.location.should == { - lat: 2.0, - lng: 3.0, - radius: 0.0, - speed: nil, - course: nil, - } + course: 90.0) end end From edcd80f2288c8077843e3c2b089e007ef01e225c Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 21:00:03 +0900 Subject: [PATCH 08/12] OK, Ruby 2.1 has introduced flonum, so do not expect identity. --- spec/lib/location_spec.rb | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/spec/lib/location_spec.rb b/spec/lib/location_spec.rb index dfb3c8e3..9718a343 100644 --- a/spec/lib/location_spec.rb +++ b/spec/lib/location_spec.rb @@ -11,16 +11,23 @@ describe Location do } it "converts values to Float" do - expect(location.lat).to equal 2.0 - expect(location.lng).to equal 3.0 - expect(location.radius).to equal 300.0 - expect(location.speed).to equal 2.0 - expect(location.course).to equal 30.0 + expect(location.lat).to be_a Float + expect(location.lat).to be 2.0 + expect(location.lng).to be_a Float + expect(location.lng).to be 3.0 + expect(location.radius).to be_a Float + expect(location.radius).to be 300.0 + expect(location.speed).to be_a Float + expect(location.speed).to be 2.0 + expect(location.course).to be_a Float + expect(location.course).to be 30.0 end it "provides hash-style access to its properties with both symbol and string keys" do - expect(location[:lat]).to equal 2.0 - expect(location['lat']).to equal 2.0 + expect(location[:lat]).to be_a Float + expect(location[:lat]).to be 2.0 + expect(location['lat']).to be_a Float + expect(location['lat']).to be 2.0 end it "does not allow hash-style assignment" do From 1406f37d48e4d02be082a4fb62c2b02180ca1ca9 Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 21:16:35 +0900 Subject: [PATCH 09/12] Add Event#location= and make location an accessible attribute. Add long aliases for lat and lng to Location. --- app/models/agents/user_location_agent.rb | 6 ++++-- app/models/event.rb | 15 ++++++++++++++- lib/location.rb | 6 ++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app/models/agents/user_location_agent.rb b/app/models/agents/user_location_agent.rb index 2575e800..e3164131 100644 --- a/app/models/agents/user_location_agent.rb +++ b/app/models/agents/user_location_agent.rb @@ -65,8 +65,10 @@ module Agents private def handle_payload(payload) - if payload[:latitude].present? && payload[:longitude].present? - create_event payload: payload, lat: payload[:latitude].to_f, lng: payload[:longitude].to_f + location = Location.new(payload) + + if location.present? + create_event payload: payload, location: location end end end diff --git a/app/models/event.rb b/app/models/event.rb index 178252f3..a0a83c3f 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -7,7 +7,7 @@ class Event < ActiveRecord::Base include JSONSerializedField include LiquidDroppable - attr_accessible :lat, :lng, :payload, :user_id, :user, :expires_at + attr_accessible :lat, :lng, :location, :payload, :user_id, :user, :expires_at acts_as_mappable @@ -53,6 +53,19 @@ class Event < ActiveRecord::Base speed: payload[:speed].presence) end + def location=(location) + case location + when nil + self.lat = self.lng = nil + return + when Location + else + location = Location.new(location) + end + self.lat, self.lng = location.lat, location.lng + location + end + # Emit this event again, as a new Event. def reemit! agent.create_event :payload => payload, :lat => lat, :lng => lng diff --git a/lib/location.rb b/lib/location.rb index 74a4de51..6f7a6634 100644 --- a/lib/location.rb +++ b/lib/location.rb @@ -34,6 +34,9 @@ class Location } end + alias latitude lat + alias latitude= lat= + def lng=(value) self[:lng] = floatify(value) { |f| if f.abs <= 180 @@ -44,6 +47,9 @@ class Location } end + alias longitude lng + alias longitude= lng= + def radius=(value) self[:radius] = floatify(value) { |f| f if f >= 0 } end From dd507e3cbfd396b3a17eb36e03e970ea18d03f84 Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 21:57:45 +0900 Subject: [PATCH 10/12] Make Event's location accessible from Liquid templates. --- app/models/event.rb | 4 ++++ lib/location.rb | 14 ++++++++++++++ spec/lib/location_spec.rb | 12 ++++++++++++ spec/models/event_spec.rb | 7 +++++++ 4 files changed, 37 insertions(+) diff --git a/app/models/event.rb b/app/models/event.rb index a0a83c3f..321fc233 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -117,4 +117,8 @@ class EventDrop @object.created_at } end + + def _location_ + @object.location + end end diff --git a/lib/location.rb b/lib/location.rb index 6f7a6634..54b7b7b4 100644 --- a/lib/location.rb +++ b/lib/location.rb @@ -1,6 +1,10 @@ +require 'liquid' + Location = Struct.new(:lat, :lng, :radius, :speed, :course) class Location + include LiquidDroppable + protected :[]= def initialize(data = {}) @@ -86,3 +90,13 @@ class Location end end end + +class LocationDrop + KEYS = Location.members.map(&:to_s).concat(%w[latitude longitude]) + + def before_method(key) + if KEYS.include?(key) + @object.__send__(key) + end + end +end diff --git a/spec/lib/location_spec.rb b/spec/lib/location_spec.rb index 9718a343..749f350a 100644 --- a/spec/lib/location_spec.rb +++ b/spec/lib/location_spec.rb @@ -53,4 +53,16 @@ describe Location do expect(Location.new(lat: 2, radius: 1).present?).to be_falsy expect(Location.new(lng: 3, radius: 1).present?).to be_falsy end + + it "is droppable" do + { + '{{location.lat}}' => '2.0', + '{{location.latitude}}' => '2.0', + '{{location.lng}}' => '3.0', + '{{location.longitude}}' => '3.0', + }.each { |template, result| + expect(Liquid::Template.parse(template).render('location' => location.to_liquid)).to eq(result), + "expected #{template.inspect} to expand to #{result.inspect}" + } + end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index a233fa4a..e7fd4bb8 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -174,6 +174,8 @@ describe EventDrop do 'title' => 'some title', 'url' => 'http://some.site.example.org/', } + @event.lat = 2 + @event.lng = 3 @event.save! end @@ -210,4 +212,9 @@ describe EventDrop do t = '{{created_at | date:"%FT%T%z" }}' interpolate(t, @event).should eq(@event.created_at.strftime("%FT%T%z")) end + + it 'should have _location_' do + t = '{{_location_.lat}},{{_location_.lng}}' + interpolate(t, @event).should eq("2.0,3.0") + end end From 10adab94dbfd781dc883c2469f09eabeeb849282 Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Sun, 21 Sep 2014 12:42:12 +0900 Subject: [PATCH 11/12] Avoid __send__. --- lib/location.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/location.rb b/lib/location.rb index 54b7b7b4..5e64cdcf 100644 --- a/lib/location.rb +++ b/lib/location.rb @@ -16,9 +16,17 @@ class Location self.lat, self.lng = data when Hash, Location data.each { |key, value| - begin - __send__("#{key}=", value) - rescue NameError + case key.to_sym + when :lat, :latitude + self.lat = value + when :lng, :longitude + self.lng = value + when :radius + self.radius = value + when :speed + self.speed = value + when :course + self.course = value end } else From be4dc34f8ef1d462b1a633bd0f934ebf220bda9a Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Sun, 21 Sep 2014 23:02:39 +0900 Subject: [PATCH 12/12] Apply a correct fix for the flonum issue. --- spec/lib/location_spec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/lib/location_spec.rb b/spec/lib/location_spec.rb index 749f350a..ea42fa40 100644 --- a/spec/lib/location_spec.rb +++ b/spec/lib/location_spec.rb @@ -12,22 +12,22 @@ describe Location do it "converts values to Float" do expect(location.lat).to be_a Float - expect(location.lat).to be 2.0 + expect(location.lat).to eq 2.0 expect(location.lng).to be_a Float - expect(location.lng).to be 3.0 + expect(location.lng).to eq 3.0 expect(location.radius).to be_a Float - expect(location.radius).to be 300.0 + expect(location.radius).to eq 300.0 expect(location.speed).to be_a Float - expect(location.speed).to be 2.0 + expect(location.speed).to eq 2.0 expect(location.course).to be_a Float - expect(location.course).to be 30.0 + expect(location.course).to eq 30.0 end it "provides hash-style access to its properties with both symbol and string keys" do expect(location[:lat]).to be_a Float - expect(location[:lat]).to be 2.0 + expect(location[:lat]).to eq 2.0 expect(location['lat']).to be_a Float - expect(location['lat']).to be 2.0 + expect(location['lat']).to eq 2.0 end it "does not allow hash-style assignment" do