diff --git a/Gemfile b/Gemfile index b462492..378ebd5 100644 --- a/Gemfile +++ b/Gemfile @@ -1,12 +1,12 @@ source 'https://rubygems.org' -gem 'rails', '~> 3.1' +ruby '1.9.3' + +gem 'rails', '~> 3.2' # Bundle edge Rails instead: # gem 'rails', :git => 'git://github.com/rails/rails.git' -gem 'sqlite3' - # Use unicorn as the web server # gem 'unicorn' @@ -28,16 +28,17 @@ gem 'sqlite3' # and rake tasks are available in development mode: group :development, :test do # RSpec - gem "rspec", "~> 2.6" - gem "rspec-rails", "~> 2.6" + gem "rspec" + gem "rspec-rails" # Cucmber gem "cucumber", "~> 1.0" gem "cucumber-rails", "~> 1.0", :require => false gem "database_cleaner" + gem "factory_girl_rails", ">= 4.1" gem "jasmine", "~> 1" - gem "ruby-debug19" + gem "debugger" gem "heroku" @@ -45,13 +46,17 @@ group :development, :test do gem "guard-coffeescript" end +# Mogoid +gem 'mongoid', ">= 3.1" + +gem 'geocoder' gem 'googlecharts' # Use JQuery not prototype gem 'jquery-rails' # Gems required for user authentication -gem 'devise' +gem 'devise', ">= 2.2" gem 'cancan' gem 'css3buttons' diff --git a/Gemfile.lock b/Gemfile.lock index 8c4547a..5dea252 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,39 +1,37 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (3.1.1) - actionpack (= 3.1.1) - mail (~> 2.3.0) - actionpack (3.1.1) - activemodel (= 3.1.1) - activesupport (= 3.1.1) + actionmailer (3.2.13) + actionpack (= 3.2.13) + mail (~> 2.5.3) + actionpack (3.2.13) + activemodel (= 3.2.13) + activesupport (= 3.2.13) builder (~> 3.0.0) erubis (~> 2.7.0) - i18n (~> 0.6) - rack (~> 1.3.2) - rack-cache (~> 1.1) - rack-mount (~> 0.8.2) + journey (~> 1.0.4) + rack (~> 1.4.5) + rack-cache (~> 1.2) rack-test (~> 0.6.1) - sprockets (~> 2.0.2) - activemodel (3.1.1) - activesupport (= 3.1.1) + sprockets (~> 2.2.1) + activemodel (3.2.13) + activesupport (= 3.2.13) builder (~> 3.0.0) - i18n (~> 0.6) - activerecord (3.1.1) - activemodel (= 3.1.1) - activesupport (= 3.1.1) - arel (~> 2.2.1) + activerecord (3.2.13) + activemodel (= 3.2.13) + activesupport (= 3.2.13) + arel (~> 3.0.2) tzinfo (~> 0.3.29) - activeresource (3.1.1) - activemodel (= 3.1.1) - activesupport (= 3.1.1) - activesupport (3.1.1) + activeresource (3.2.13) + activemodel (= 3.2.13) + activesupport (= 3.2.13) + activesupport (3.2.13) + i18n (= 0.6.1) multi_json (~> 1.0) - addressable (2.2.6) - archive-tar-minitar (0.5.2) - arel (2.2.1) + addressable (2.3.4) + arel (3.0.2) bcrypt-ruby (3.0.1) - builder (3.0.0) + builder (3.0.4) cancan (1.6.7) capybara (1.1.1) mime-types (>= 1.16) @@ -48,7 +46,7 @@ GEM coffee-script-source execjs coffee-script-source (1.1.2) - columnize (0.3.4) + columnize (0.3.6) css3buttons (1.0.1) actionpack (>= 3.0.0) cucumber (1.1.1) @@ -62,15 +60,29 @@ GEM cucumber (>= 1.1.1) nokogiri (>= 1.5.0) database_cleaner (0.6.7) - devise (1.4.9) + debugger (1.6.0) + columnize (>= 0.3.1) + debugger-linecache (~> 1.2.0) + debugger-ruby_core_source (~> 1.2.1) + debugger-linecache (1.2.0) + debugger-ruby_core_source (1.2.2) + devise (2.2.4) bcrypt-ruby (~> 3.0) - orm_adapter (~> 0.0.3) - warden (~> 1.0.3) - diff-lcs (1.1.3) + orm_adapter (~> 0.1) + railties (~> 3.1) + warden (~> 1.2.1) + diff-lcs (1.2.4) erubis (2.7.0) + excon (0.23.0) execjs (1.2.9) multi_json (~> 1.0) + factory_girl (4.1.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.1.0) + factory_girl (~> 4.1.0) + railties (>= 3.0.0) ffi (1.0.9) + geocoder (1.1.8) gherkin (2.6.2) json (>= 1.4.6) googlecharts (1.6.8) @@ -79,114 +91,116 @@ GEM guard-coffeescript (0.5.2) coffee-script (>= 2.2.0) guard (>= 0.8.3) - haml (3.1.3) - heroku (2.11.1) + haml (4.0.3) + tilt + heroku (2.39.4) + heroku-api (~> 0.3.7) launchy (>= 0.3.2) + netrc (~> 0.7.7) rest-client (~> 1.6.1) rubyzip - term-ansicolor (~> 1.0.5) - hike (1.2.1) - i18n (0.6.0) + heroku-api (0.3.12) + excon (~> 0.23.0) + hike (1.2.2) + i18n (0.6.1) jasmine (1.1.2) jasmine-core (>= 1.1.0) rack (>= 1.1) rspec (>= 1.3.1) selenium-webdriver (>= 0.1.3) jasmine-core (1.1.0) + journey (1.0.4) jquery-rails (1.0.16) railties (~> 3.0) thor (~> 0.14) - json (1.6.1) + json (1.8.0) json_pure (1.6.1) - launchy (2.0.5) - addressable (~> 2.2.6) - linecache19 (0.5.12) - ruby_core_source (>= 0.1.4) - mail (2.3.0) - i18n (>= 0.4.0) + launchy (2.3.0) + addressable (~> 2.3) + mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) - mime-types (1.17.2) - multi_json (1.0.3) + mime-types (1.23) + mongoid (3.1.4) + activemodel (~> 3.2) + moped (~> 1.4) + origin (~> 1.0) + tzinfo (~> 0.3.22) + moped (1.5.0) + multi_json (1.7.3) + netrc (0.7.7) nokogiri (1.5.0) - orm_adapter (0.0.5) + origin (1.1.0) + orm_adapter (0.4.0) polyglot (0.3.3) - rack (1.3.5) - rack-cache (1.1) + rack (1.4.5) + rack-cache (1.2) rack (>= 0.4) - rack-mount (0.8.3) - rack (>= 1.0.0) - rack-ssl (1.3.2) + rack-ssl (1.3.3) rack - rack-test (0.6.1) + rack-test (0.6.2) rack (>= 1.0) - rails (3.1.1) - actionmailer (= 3.1.1) - actionpack (= 3.1.1) - activerecord (= 3.1.1) - activeresource (= 3.1.1) - activesupport (= 3.1.1) + rails (3.2.13) + actionmailer (= 3.2.13) + actionpack (= 3.2.13) + activerecord (= 3.2.13) + activeresource (= 3.2.13) + activesupport (= 3.2.13) bundler (~> 1.0) - railties (= 3.1.1) - railties (3.1.1) - actionpack (= 3.1.1) - activesupport (= 3.1.1) + railties (= 3.2.13) + railties (3.2.13) + actionpack (= 3.2.13) + activesupport (= 3.2.13) rack-ssl (~> 1.3.2) rake (>= 0.8.7) rdoc (~> 3.4) - thor (~> 0.14.6) - rake (0.9.2.2) - rdoc (3.11) + thor (>= 0.14.6, < 2.0) + rake (10.0.4) + rdoc (3.12.2) json (~> 1.4) rest-client (1.6.7) mime-types (>= 1.16) - rspec (2.7.0) - rspec-core (~> 2.7.0) - rspec-expectations (~> 2.7.0) - rspec-mocks (~> 2.7.0) - rspec-core (2.7.1) - rspec-expectations (2.7.0) - diff-lcs (~> 1.1.2) - rspec-mocks (2.7.0) - rspec-rails (2.7.0) - actionpack (~> 3.0) - activesupport (~> 3.0) - railties (~> 3.0) - rspec (~> 2.7.0) - ruby-debug-base19 (0.11.25) - columnize (>= 0.3.1) - linecache19 (>= 0.5.11) - ruby_core_source (>= 0.1.4) - ruby-debug19 (0.11.6) - columnize (>= 0.3.1) - linecache19 (>= 0.5.11) - ruby-debug-base19 (>= 0.11.19) - ruby_core_source (0.1.5) - archive-tar-minitar (>= 0.5.2) - rubyzip (0.9.4) + rspec (2.13.0) + rspec-core (~> 2.13.0) + rspec-expectations (~> 2.13.0) + rspec-mocks (~> 2.13.0) + rspec-core (2.13.1) + rspec-expectations (2.13.0) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.13.1) + rspec-rails (2.13.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 2.13.0) + rspec-expectations (~> 2.13.0) + rspec-mocks (~> 2.13.0) + rubyzip (0.9.9) sass (3.1.10) selenium-webdriver (2.10.0) childprocess (>= 0.2.1) ffi (= 1.0.9) json_pure rubyzip - sprockets (2.0.3) + sprockets (2.2.2) hike (~> 1.2) + multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sqlite3 (1.3.4) - term-ansicolor (1.0.7) + term-ansicolor (1.2.2) + tins (~> 0.8) therubyracer-heroku (0.8.1.pre3) thor (0.14.6) - tilt (1.3.3) - treetop (1.4.10) + tilt (1.4.1) + tins (0.8.0) + treetop (1.4.12) polyglot polyglot (>= 0.3.1) - tzinfo (0.3.30) + tzinfo (0.3.37) uglifier (1.0.4) execjs (>= 0.3.0) multi_json (>= 1.0.2) - warden (1.0.6) + warden (1.2.1) rack (>= 1.0) xpath (0.1.4) nokogiri (~> 1.3) @@ -201,7 +215,10 @@ DEPENDENCIES cucumber (~> 1.0) cucumber-rails (~> 1.0) database_cleaner - devise + debugger + devise (>= 2.2) + factory_girl_rails (>= 4.1) + geocoder googlecharts guard guard-coffeescript @@ -209,11 +226,10 @@ DEPENDENCIES heroku jasmine (~> 1) jquery-rails - rails (~> 3.1) - rspec (~> 2.6) - rspec-rails (~> 2.6) - ruby-debug19 + mongoid (>= 3.1) + rails (~> 3.2) + rspec + rspec-rails sass - sqlite3 therubyracer-heroku (= 0.8.1.pre3) uglifier diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..455f398 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2011-2013 Simon Funke, Peter Hamilton, Kushal Pisavadia and +Florian Rathgeber + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile index 3678491..bb087a7 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,5 @@ default: - rake db:drop - rake db:create - rake db:migrate - rake db:fixtures:load + rake db:mongoid:create_indexes rake bootstrap:all rake test:units rake cucumber diff --git a/app/assets/javascripts/application.coffee b/app/assets/javascripts/application.coffee index 9714ad5..920633f 100644 --- a/app/assets/javascripts/application.coffee +++ b/app/assets/javascripts/application.coffee @@ -59,7 +59,7 @@ parseHospitalJSON = (hospitalJsonObjects, map = EMG.map) -> EMG.fit_zoom() resizeContentToWindow = -> - $('#main').height($(window).height() - 80) + $('#main').height($(window).height() - 160) bindFilterButtons = -> $('#hospital_filter_button_distance').bind 'click', (event) => diff --git a/app/assets/javascripts/geolocation_handler.coffee b/app/assets/javascripts/geolocation_handler.coffee index 11d8f68..63bb1e1 100644 --- a/app/assets/javascripts/geolocation_handler.coffee +++ b/app/assets/javascripts/geolocation_handler.coffee @@ -8,10 +8,7 @@ #Initiated process of setting the user's location locateUser: -> - if this.browserGeolocationEnabled() - this.setLocationUsingBrowser() - else - this.locateWithPostcode() + this.setLocationUsingBrowser() # Centers the map on the current user's recorded location centerMapOnLocation: -> @@ -81,6 +78,7 @@ # Zooms in to current location and initiates prompt to get user to verify # the suggested location. Opens the facebox and binds the buttons verifyLocation: -> + $.facebox.close() this.centerMapOnLocation() EMG.map.setZoom(16) if !EMG.loadHospitals() @@ -130,7 +128,7 @@ this.setLocation {'lat': position.coords.latitude, 'lon': position.coords.longitude, 'postcode': ''} this.verifyLocation() , => - $.facebox("Error: The Geolocation service failed.") + this.locateWithPostcode() else - $.facebox("Error: Your browser doesn't support geolocation.") + this.locateWithPostcode() diff --git a/app/assets/javascripts/location.coffee b/app/assets/javascripts/location.coffee index d866fa8..509deda 100644 --- a/app/assets/javascripts/location.coffee +++ b/app/assets/javascripts/location.coffee @@ -1,8 +1,9 @@ @module "EMG", -> class @Location constructor: (json) -> - @lat = json.latitude - @lon = json.longitude + @id = json._id + @lat = json.location[1] + @lon = json.location[0] @name = json.name @postcode = json.postcode @distance = json.distance @@ -14,9 +15,10 @@ @infowindow = new google.maps.InfoWindow content: "

" + @name + "

" + @postcode + "
" + - "Distance: " + @distance/1000 + " km

" + + "Distance: " + @distance + " km

" + "

Current waiting time: " + @delay + " min

" + - "Updated " + @last_updated_at + "Updated " + @last_updated_at + + "
Last week's waiting time" getLocation: -> return { @@ -27,6 +29,9 @@ getHashcode: -> return @hashcode + getId: -> + @id + getName: -> @name diff --git a/app/assets/stylesheets/style.scss b/app/assets/stylesheets/style.scss index 432732a..5500776 100644 --- a/app/assets/stylesheets/style.scss +++ b/app/assets/stylesheets/style.scss @@ -4,7 +4,7 @@ #= require html5-boilerplate /* Core styles for body/containers */ -html { height: 100% } +html { height: 100%; overflow: hidden } body { height: 100%; margin: 0; padding: 0 } #container{ width: 100%; height: 100%; position: relative} #main{ width: 100%; height: 100%; position: relative;} @@ -19,28 +19,45 @@ body, select, input, textarea { font-variant: normal; font-weight: 300; } +::-webkit-scrollbar { + display: none; +} .clear{clear:both} /* Fluid Layout */ -#header{min-height: 90px;} +#header{ + min-height: 120px; + height:90px; + background: hsla(207, 45%, 55%, 1); +} #wrapper{float:left;width:100%;} -#map_canvas{float:left;width:100%} -#sidebar{float:right;width:300px;margin: 0;} -#footer{clear:both;width:100%} - -/* Fluid Layout Styles */ -#header{height:90px; background: hsla(207, 45%, 55%, 1)} -#map_canvas p{line-height:1.4} -#sidebar{background:#FFF} -#footer{background: #333;color: #FFF} -#footer p{margin:0;padding:5px 10px} +#map_canvas{ + float:left; + width:100%; + p { + line-height:1.4; + } +} +#footer{ + clear:both; + width:100%; + background: #333; + color: #FFF; + p { + margin:0; + padding:5px 10px; + } +} #sidebar{ - float: right; - height: 100%; - position: absolute; - right: 0; - overflow: scroll; + background:#FFF; + float: right; + width:280px; + height: 100%; + margin: 0; + position: absolute; + right: 0; + overflow-x: hidden; } /* Buttons */ @@ -93,14 +110,22 @@ body, select, input, textarea { .header_desc{ font-size: 24px; float:left; - padding-top: 45px; } .header_buttons{ + position: absolute; + top: 0; + right: 0; padding: 5px; float: right; } +.button-group{ + display: block; + float: right; + clear: both; +} + .header_text{ color: hsla(0, 0%, 95%, .85); font-size: 16px; @@ -110,7 +135,8 @@ body, select, input, textarea { } #hospital_filters{ - padding: 15px; + margin: 15px; + height: 30px; .hospital_filter_button{ float: left; padding: 5px; @@ -131,7 +157,7 @@ body, select, input, textarea { #hospital_list{ - margin: 15px; + margin: 0; border-top: 1px #E5E5E5 solid; li{ @@ -320,7 +346,4 @@ body, select, input, textarea { .message { text-align: center; padding: 10px; - margin-bottom: 10px; } - - diff --git a/app/controllers/delays_controller.rb b/app/controllers/delays_controller.rb index d7c7e11..53169a9 100644 --- a/app/controllers/delays_controller.rb +++ b/app/controllers/delays_controller.rb @@ -18,11 +18,11 @@ def get_hospital # GET /delays # GET /delays.xml def index - nb_days = 7 - start = nb_days.days.ago - dates = [start.to_f] + Delay.all(:conditions => {:created_at => start..0.days.ago, :hospital_id => get_hospital.id}).map{|d| d.created_at.to_f}.reverse + [0.days.ago.to_f] # We add to additional nodes at the beginning and the end of the timespan + start = 7.days.ago + delays = @hospital.delays.where(:created_at => start..0.days.ago) + dates = [start.to_f] + delays.map{|d| d.created_at.to_f}.reverse + [0.days.ago.to_f] # We add to additional nodes at the beginning and the end of the timespan dates2 = dates.map{|d| (d-start.to_f)/20} - delays = Delay.all(:conditions => {:created_at => start..0.days.ago, :hospital_id => get_hospital.id}, :select => :minutes).map(&:minutes).reverse + delays = delays.map(&:minutes).reverse if delays.empty? delays2 = [0.0, 0.1] else @@ -30,12 +30,12 @@ def index end dates2 = dates2.collect { |d| d * delays2.max / dates2.max if dates2.max > 0 } data = [dates2, delays2] - wdays = ['Sun', 'Sat', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] + wdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] (0..Date.today.wday+1).each do |i| d = wdays.shift wdays.push(d) end - @graph_url = Gchart.line_xy(:size => '500x300', + @graph_url = Gchart.line_xy(:size => '500x300', :title => "Last weeks waiting time", :data => data, :axis_with_label => 'x,y', diff --git a/app/controllers/hospitals_controller.rb b/app/controllers/hospitals_controller.rb index 148c5c6..c7b3dbd 100644 --- a/app/controllers/hospitals_controller.rb +++ b/app/controllers/hospitals_controller.rb @@ -3,16 +3,21 @@ class HospitalsController < ApplicationController def index - max_distance = 500000 + max_distance = 5 # Distance in kilometers max_results = 20 + units = 'km' - location = {lat: params[:lat].to_f, lon: params[:lon].to_f, radius: (params[:radius] || max_distance).to_i} + if params[:postcode] then + lat, lng = Geocoder.search(params[:postcode]).first.coordinates + else + lat, lng = params[:lat].to_f, params[:lon].to_f + end - @hospitals = Hospital.find_hospitals_sorted(location[:lat], - location[:lon], - location[:radius], + @hospitals = Hospital.find_hospitals_sorted(lat, lng, + (params[:radius] || max_distance).to_f, params[:sort], - (params[:max_results] || max_results).to_i) + (params[:max_results] || max_results).to_i, + params[:units] || units) respond_to do |format| format.html # index.html.haml format.json { render :json => @hospitals.as_json(:mobile => params[:mobile]) } diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ea13482..57acf6d 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -17,7 +17,7 @@ def index # GET /users/1/show # GET /users/1/show.xml - def show + def show @user = User.find(params[:id]) respond_to do |format| @@ -33,7 +33,7 @@ def edit_hospital if not @user.hospital.nil? @hospital = Hospital.new(:id => 0, :name => @user.hospital.name) end - @hospitals = Hospital.find(:all, :order => "name").map {|p| [p.name, p.id] } + @hospitals = Hospital.order_by(:name.asc).map {|p| [p.name, p.id] } respond_to do |format| format.html # index.html.erb @@ -56,7 +56,7 @@ def edit_role # POST /users/1/update_role def update_role @user = User.find(params[:id]) - @user.roles = [Role.find_by_id(params["post"]["id"])] + @user.roles = [Role.find_by_id(params["post"]["id"])] respond_to do |format| if @user.save diff --git a/app/models/delay.rb b/app/models/delay.rb index fa44fca..14cc5cf 100644 --- a/app/models/delay.rb +++ b/app/models/delay.rb @@ -1,16 +1,14 @@ -class Delay < ActiveRecord::Base - belongs_to :hospital - after_initialize :init +class Delay + include Mongoid::Document + + embedded_in :hospital + field :minutes, type: Float, default: 0.0 + field :created_at, type: Time, default: ->{ Time.now } + field :updated_at, type: Time, default: ->{ Time.now } validates_numericality_of :minutes, :greater_than_or_equal_to => 0 validates_presence_of :hospital - default_scope :order => 'created_at DESC' - - def init - self.minutes ||= 0.0 #will set the default value only if it's nil - self.created_at ||= Time.now - self.updated_at ||= Time.now - end + default_scope order_by(:created_at => :desc) end diff --git a/app/models/hospital.rb b/app/models/hospital.rb index cbdc08b..76d52e6 100644 --- a/app/models/hospital.rb +++ b/app/models/hospital.rb @@ -1,64 +1,66 @@ -class Hospital < ActiveRecord::Base +class Hospital + include Mongoid::Document include ActionView::Helpers::DateHelper - has_many :delays - has_many :users - validates_presence_of :latitude, :longitude, :name + embeds_many :delays + has_many :users + field :source_uri, type: String + field :name, type: String + field :updated, type: Date + field :odscode, type: String + field :postcode, type: String + field :location, type: Array + index({ location: "2d" }) + + validates_presence_of :location, :name validates_uniqueness_of :odscode - validates_numericality_of :longitude, :greater_than_or_equal_to => -180.0, :less_than_or_equal_to => 180.0 - validates_numericality_of :latitude, :greater_than_or_equal_to => -90.0, :less_than_or_equal_to => 90.0 - - attr_accessor :distance + validate :validate_location + def validate_location + errors.add(:location, 'invalid location') unless !self.location.nil? and + self.location.length == 2 and + self.location[0].is_a?(Numeric) and + (-180..180).include?(self.location[0]) and + self.location[1].is_a?(Numeric) and + (-90..90).include?(self.location[1]) + end def as_json(options={}) if options[:mobile] return super(:only => [:odscode], :methods => [:delay]) end - j = super(:only => [:odscode, :postcode, :name, :longitude, :latitude, :distance], + j = super(:only => [:_id, :odscode, :postcode, :name, :location, :distance], :methods => [:delay, :last_updated_at]) - j[:distance] = "%.f" % self.distance + j[:distance] = self.geo_near_distance.round(2) if self.geo_near_distance return j end def compute_distance(lat, lon) - lat2 = self.latitude - lon2 = self.longitude - distance = Hospital.compute_distance(lat, lon, lat2, lon2) + distance = Hospital.compute_distance(lat, lon, self.location[1], self.location[0]) end - def self.find_hospitals_sorted(lat, lon, max_distance, sort, max_results) - hospitals = Hospital.find_hospitals_near_latlon(lat, lon, max_distance, max_results) + def self.find_hospitals_sorted(lat, lon, max_distance, sort, max_results=20, units='km') + hospitals = Hospital.find_hospitals_near_latlon(lat, lon, max_distance, max_results, units) case sort - when "agony" # Our custom ranking algorithm - # FIXME: replace by some smart algorithm when we have one - # Weigh delay against distance, assuming you travel 100m / min - hospitals.sort!{|a,b| a.delay*100+a.distance <=> b.delay*100+b.distance} + when "distance" # By distance + hospitals # No need to sort, as the sorting by distance is the default when "wait" # By wait time hospitals.sort!{|a,b| a.delay <=> b.delay} - else # By distance - hospitals # No need to sort, as the sorting by distance is the default + else # Our custom ranking algorithm + # FIXME: replace by some smart algorithm when we have one + # Weigh delay against distance, assuming you travel 100m / min + hospitals.sort!{|a,b| a.delay+10*a.geo_near_distance <=> b.delay+10*b.geo_near_distance} end end - # Max distance must be provided in meter - def self.find_hospitals_near_latlon(lat, lon, max_distance=500000, max_results=20) - hospitals_bb = Hospital.find_hospitals_in_bb(lat, lon, max_distance) - - # Precompute the distance for these hospitals - hospitals = [] - hospitals_bb.each do |hospital| - hospital.distance = hospital.compute_distance(lat, lon) - if hospital.distance <= max_distance - hospitals.push(hospital) - end - end - - return hospitals + # Max distance must be provided in the given units (km or mi) + def self.find_hospitals_near_latlon(lat, lon, max_distance=5, max_results=20, units='km') + earth_radius = units == 'km' ? 6371.0 : 3959.0 + Hospital.limit(max_results).geo_near([lon, lat]).spherical.distance_multiplier(earth_radius).max_distance(max_distance/earth_radius).unique(true).to_a end - def self.find_hospitals_in_bb(lat, lon, max_distance) + def self.find_hospitals_in_bb(lat, lon, max_distance, max_results) earth_radius = 6371000.0 earth_radius_at_lat = Math.cos(Hospital.to_rad(lat))*earth_radius @@ -69,7 +71,7 @@ def self.find_hospitals_in_bb(lat, lon, max_distance) delta_max_lon = Hospital.to_deg(Float(max_distance)/earth_radius_at_lat) delta_max_lat = Hospital.to_deg(Float(max_distance)/earth_radius) - hospitals_bb = Hospital.where(:longitude => (lon-delta_max_lon..lon+delta_max_lon), :latitude => (lat-delta_max_lat..lat+delta_max_lat)) + hospitals_bb = Hospital.within_box(:location => [ [lon-delta_max_lon, lat-delta_max_lat], [lon+delta_max_lon, lat+delta_max_lat] ]).limit(max_results) return hospitals_bb end @@ -79,7 +81,7 @@ def current_delay end def delay - self.current_delay.try(:minutes) or 0 + (self.current_delay.try(:minutes) or 0).round(2) end def last_updated_at @@ -90,6 +92,14 @@ def last_updated_at end end + def latitude + self.location[1] + end + + def longitude + self.location[0] + end + ### Class methods ### def self.to_rad(ang) diff --git a/app/models/role.rb b/app/models/role.rb index 33cd819..bdbc0c9 100644 --- a/app/models/role.rb +++ b/app/models/role.rb @@ -1,3 +1,5 @@ -class Role < ActiveRecord::Base +class Role + include Mongoid::Document + has_and_belongs_to_many :users end diff --git a/app/models/roles_user.rb b/app/models/roles_user.rb index 3918403..ba92cbf 100644 --- a/app/models/roles_user.rb +++ b/app/models/roles_user.rb @@ -1,4 +1,6 @@ -class RolesUser < ActiveRecord::Base +class RolesUser + include Mongoid::Document + belongs_to :user belongs_to :role end diff --git a/app/models/user.rb b/app/models/user.rb index 306de4b..ffbdf72 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,5 @@ -class User < ActiveRecord::Base +class User + include Mongoid::Document has_and_belongs_to_many :roles belongs_to :hospital @@ -8,17 +9,38 @@ class User < ActiveRecord::Base devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable + ## Database authenticatable + field :email, :type => String, :default => "" + field :encrypted_password, :type => String, :default => "" + + validates_presence_of :email + validates_presence_of :encrypted_password + + ## Recoverable + field :reset_password_token, :type => String + field :reset_password_sent_at, :type => Time + + ## Rememberable + field :remember_created_at, :type => Time + + ## Trackable + field :sign_in_count, :type => Integer, :default => 0 + field :current_sign_in_at, :type => Time + field :last_sign_in_at, :type => Time + field :current_sign_in_ip, :type => String + field :last_sign_in_ip, :type => String + # Setup accessible (or protected) attributes for your model attr_accessible :email, :password, :password_confirmation, :remember_me after_create :set_default_role def role?(role) - return !!self.roles.find_by_name(role.to_s) + return !!self.roles.find_by(name: role.to_s) end def set_default_role - RolesUser.create(:user_id => self.id, :role_id => Role.find_by_name('guest').id) + RolesUser.create(:user_id => self.id, :role_id => Role.find_by(name: 'guest').id) end end diff --git a/app/views/layouts/_flashes.html.haml b/app/views/layouts/_flashes.html.haml index 68bd151..ea21f52 100644 --- a/app/views/layouts/_flashes.html.haml +++ b/app/views/layouts/_flashes.html.haml @@ -1,4 +1,9 @@ #flash + .message{ :class => 'notice' } + %strong Disclaimer: + %em This site is a prototype in pre-alpha stage and does not show real live data! + %span Open source, MIT licensed. + %a{:href => "https://github.com/e-mergency/site"} Get the code! - flash.each do |key, value| .message{ :class => key } - %p= value \ No newline at end of file + %p= value diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 4ab938f..6b4d076 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -20,6 +20,6 @@ -# All JavaScript at the bottom, except for Modernizr and Respond. -# Modernizr enables HTML5 elements & feature detects; Respond is a polyfill for min/max-width CSS3 Media Queries -= javascript_include_tag 'modernizr.min', 'respond.min' += javascript_include_tag 'modernizr.min', 'respond.min', 'jquery.min' = csrf_meta_tag diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index 54acd55..2267047 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -3,7 +3,7 @@ .title e-mergency .header_desc - Find out the waiting time of hospitals near you. + Find out the waiting time of urgent care services near you. - if current_user .header_buttons .header_text @@ -11,6 +11,7 @@ = button_group do - if current_user.roles.length>0 and (current_user.roles[0].name == 'hospital' or current_user.roles[0].name == 'admin' ) and not current_user.hospital.nil? = button_link_to "Update waiting time for #{current_user.hospital.name}", new_hospital_delay_path(current_user.hospital) + = button_group do - if current_user.roles.length>0 and current_user.roles[0].name == 'admin' = button_link_to "Administration", user_registration_path = button_link_to 'Sign out', destroy_user_session_path, :method => :delete diff --git a/app/views/layouts/_javascripts.html.haml b/app/views/layouts/_javascripts.html.haml index 945a753..17dd356 100644 --- a/app/views/layouts/_javascripts.html.haml +++ b/app/views/layouts/_javascripts.html.haml @@ -1,7 +1,4 @@ -:javascript - window.jQuery || document.write("