From b25142e7931c102b74f0aa5ae8e8974f1c91af8f Mon Sep 17 00:00:00 2001 From: Akinori MUSHA Date: Fri, 19 Sep 2014 19:06:55 +0900 Subject: [PATCH] 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