| @@ -2,11 +2,6 @@ source 'https://rubygems.org' | |||||
| gem 'rails', '3.2.12' | gem 'rails', '3.2.12' | ||||
| # Bundle edge Rails instead: | |||||
| # gem 'rails', :git => 'git://github.com/rails/rails.git' | |||||
| # Gems used only for assets and not required | # Gems used only for assets and not required | ||||
| # in production environments by default. | # in production environments by default. | ||||
| group :assets do | group :assets do | ||||
| @@ -36,11 +31,16 @@ gem 'jquery-rails' | |||||
| # To use debugger | # To use debugger | ||||
| # gem 'debugger' | # gem 'debugger' | ||||
| gem 'mongo_mapper' | |||||
| gem 'bson_ext' | |||||
| gem 'haml-rails' | gem 'haml-rails' | ||||
| gem 'mm-multi-parameter-attributes' | |||||
| gem 'feedzirra' | gem 'feedzirra' | ||||
| gem 'htmlentities' | gem 'htmlentities' | ||||
| gem 'sorcery' | gem 'sorcery' | ||||
| gem 'will_paginate', '~> 3.0' | gem 'will_paginate', '~> 3.0' | ||||
| gem 'activerecord-postgresql-adapter' | |||||
| gem 'pg' | |||||
| group :development do | |||||
| gem 'meta_request' | |||||
| gem 'better_errors' | |||||
| gem 'binding_of_caller' | |||||
| end | |||||
| @@ -22,6 +22,8 @@ GEM | |||||
| activesupport (= 3.2.12) | activesupport (= 3.2.12) | ||||
| arel (~> 3.0.2) | arel (~> 3.0.2) | ||||
| tzinfo (~> 0.3.29) | tzinfo (~> 0.3.29) | ||||
| activerecord-postgresql-adapter (0.0.1) | |||||
| pg | |||||
| activeresource (3.2.12) | activeresource (3.2.12) | ||||
| activemodel (= 3.2.12) | activemodel (= 3.2.12) | ||||
| activesupport (= 3.2.12) | activesupport (= 3.2.12) | ||||
| @@ -30,10 +32,13 @@ GEM | |||||
| multi_json (~> 1.0) | multi_json (~> 1.0) | ||||
| arel (3.0.2) | arel (3.0.2) | ||||
| bcrypt-ruby (3.0.1) | bcrypt-ruby (3.0.1) | ||||
| bson (1.8.2) | |||||
| bson_ext (1.8.2) | |||||
| bson (~> 1.8.2) | |||||
| better_errors (0.6.0) | |||||
| coderay (>= 1.0.0) | |||||
| erubis (>= 2.6.6) | |||||
| binding_of_caller (0.7.1) | |||||
| debug_inspector (>= 0.0.1) | |||||
| builder (3.0.4) | builder (3.0.4) | ||||
| coderay (1.0.9) | |||||
| coffee-rails (3.2.2) | coffee-rails (3.2.2) | ||||
| coffee-script (>= 2.2.0) | coffee-script (>= 2.2.0) | ||||
| railties (~> 3.2.0) | railties (~> 3.2.0) | ||||
| @@ -42,6 +47,7 @@ GEM | |||||
| execjs | execjs | ||||
| coffee-script-source (1.4.0) | coffee-script-source (1.4.0) | ||||
| curb (0.7.18) | curb (0.7.18) | ||||
| debug_inspector (0.0.2) | |||||
| erubis (2.7.0) | erubis (2.7.0) | ||||
| execjs (1.4.0) | execjs (1.4.0) | ||||
| multi_json (~> 1.0) | multi_json (~> 1.0) | ||||
| @@ -81,16 +87,10 @@ GEM | |||||
| i18n (>= 0.4.0) | i18n (>= 0.4.0) | ||||
| mime-types (~> 1.16) | mime-types (~> 1.16) | ||||
| treetop (~> 1.4.8) | treetop (~> 1.4.8) | ||||
| meta_request (0.2.1) | |||||
| rack-contrib | |||||
| rails | |||||
| mime-types (1.21) | mime-types (1.21) | ||||
| mm-multi-parameter-attributes (0.2.2) | |||||
| mongo_mapper (>= 0.9.0) | |||||
| tzinfo | |||||
| mongo (1.8.2) | |||||
| bson (~> 1.8.2) | |||||
| mongo_mapper (0.12.0) | |||||
| activemodel (~> 3.0) | |||||
| activesupport (~> 3.0) | |||||
| plucky (~> 0.5.2) | |||||
| multi_json (1.6.1) | multi_json (1.6.1) | ||||
| multipart-post (1.1.5) | multipart-post (1.1.5) | ||||
| nokogiri (1.4.7) | nokogiri (1.4.7) | ||||
| @@ -101,12 +101,13 @@ GEM | |||||
| jwt (~> 0.1.4) | jwt (~> 0.1.4) | ||||
| multi_json (~> 1.0) | multi_json (~> 1.0) | ||||
| rack (~> 1.2) | rack (~> 1.2) | ||||
| plucky (0.5.2) | |||||
| mongo (~> 1.5) | |||||
| pg (0.14.1) | |||||
| polyglot (0.3.3) | polyglot (0.3.3) | ||||
| rack (1.4.5) | rack (1.4.5) | ||||
| rack-cache (1.2) | rack-cache (1.2) | ||||
| rack (>= 0.4) | rack (>= 0.4) | ||||
| rack-contrib (1.1.0) | |||||
| rack (>= 0.9.1) | |||||
| rack-ssl (1.3.3) | rack-ssl (1.3.3) | ||||
| rack | rack | ||||
| rack-test (0.6.2) | rack-test (0.6.2) | ||||
| @@ -160,14 +161,16 @@ PLATFORMS | |||||
| ruby | ruby | ||||
| DEPENDENCIES | DEPENDENCIES | ||||
| bson_ext | |||||
| activerecord-postgresql-adapter | |||||
| better_errors | |||||
| binding_of_caller | |||||
| coffee-rails (~> 3.2.1) | coffee-rails (~> 3.2.1) | ||||
| feedzirra | feedzirra | ||||
| haml-rails | haml-rails | ||||
| htmlentities | htmlentities | ||||
| jquery-rails | jquery-rails | ||||
| mm-multi-parameter-attributes | |||||
| mongo_mapper | |||||
| meta_request | |||||
| pg | |||||
| rails (= 3.2.12) | rails (= 3.2.12) | ||||
| sass-rails (~> 3.2.3) | sass-rails (~> 3.2.3) | ||||
| sorcery | sorcery | ||||
| @@ -0,0 +1,3 @@ | |||||
| # Place all the behaviors and hooks related to the matching controller here. | |||||
| # All this logic will automatically be available in application.js. | |||||
| # You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ | |||||
| @@ -0,0 +1,3 @@ | |||||
| // Place all the styles related to the Layers controller here. | |||||
| // They will automatically be included in application.css. | |||||
| // You can use Sass (SCSS) here: http://sass-lang.com/ | |||||
| @@ -17,8 +17,8 @@ class FeedsController < ApplicationController | |||||
| # GET /feeds/1.json | # GET /feeds/1.json | ||||
| def show | def show | ||||
| @feed = Feed.find(params[:id]) | @feed = Feed.find(params[:id]) | ||||
| @posts = @feed.posts.sort(:published.desc).paginate(:page => params[:page], :per_page => 20) | |||||
| @posts = @feed.posts.order("published desc").paginate(:page => params[:page], :per_page => 20) | |||||
| respond_to do |format| | respond_to do |format| | ||||
| format.html # show.html.erb | format.html # show.html.erb | ||||
| format.json { render json: @feed } | format.json { render json: @feed } | ||||
| @@ -44,11 +44,27 @@ class FeedsController < ApplicationController | |||||
| # POST /feeds | # POST /feeds | ||||
| # POST /feeds.json | # POST /feeds.json | ||||
| def create | def create | ||||
| @feed = Feed.new(params[:feed]) | |||||
| Rails.logger.debug "Feed URL: %s" % params['feed']['feed_url'] | |||||
| if Feed.where(:feed_url => params['feed']['feed_url']).size == 1 # ensure this test returns the values we expect | |||||
| Rails.logger.debug "Adding existing feed to a new layer" | |||||
| @feed = Feed.where(:feed_url => params['feed']['feed_url']).first | |||||
| @layer = Layer.find(params['feed']['new_layer_id']) # assumes that the specified layer exists | |||||
| # Attach the existing feed to the specified layer (making sure we only add each one once) | |||||
| @feed.layers << @layer unless @feed.layers.include?(@layer) | |||||
| else | |||||
| # Create a new feed | |||||
| Rails.logger.debug "Creating a new feed" | |||||
| @feed = Feed.new(params[:feed]) | |||||
| @layer = Layer.find(params['feed']['new_layer_id']) # assumes that the specified layer exists | |||||
| @feed.layers << @layer unless @feed.layers.include?(@layer) | |||||
| end | |||||
| respond_to do |format| | respond_to do |format| | ||||
| if @feed.save | if @feed.save | ||||
| format.html { redirect_to '/', notice: 'Feed added OK' } | |||||
| format.html { redirect_to @layer, notice: 'Feed added OK' } | |||||
| format.json { render json: @feed, status: :created, location: @feed } | format.json { render json: @feed, status: :created, location: @feed } | ||||
| else | else | ||||
| format.html { render action: "new" } | format.html { render action: "new" } | ||||
| @@ -87,12 +103,12 @@ class FeedsController < ApplicationController | |||||
| def fetch | def fetch | ||||
| @feed = Feed.find(params[:id]) | @feed = Feed.find(params[:id]) | ||||
| @feed.get | |||||
| @feed.fetch | |||||
| redirect_to :back, notice: 'Feed fetched OK' | redirect_to :back, notice: 'Feed fetched OK' | ||||
| end | end | ||||
| def fetch_all | def fetch_all | ||||
| Feed.get_all | |||||
| Feed.fetch_all | |||||
| redirect_to :back, notice: 'All feeds fetched OK' | redirect_to :back, notice: 'All feeds fetched OK' | ||||
| end | end | ||||
| end | end | ||||
| @@ -0,0 +1,85 @@ | |||||
| class LayersController < ApplicationController | |||||
| # GET /layers | |||||
| # GET /layers.json | |||||
| def index | |||||
| @layers = Layer.all | |||||
| respond_to do |format| | |||||
| format.html # index.html.erb | |||||
| format.json { render json: @layers } | |||||
| end | |||||
| end | |||||
| # GET /layers/1 | |||||
| # GET /layers/1.json | |||||
| def show | |||||
| @layer = Layer.find(params[:id]) | |||||
| @feed = Feed.new | |||||
| @feed.new_layer_id = @layer.id | |||||
| respond_to do |format| | |||||
| format.html # show.html.erb | |||||
| format.json { render json: @layer } | |||||
| end | |||||
| end | |||||
| # GET /layers/new | |||||
| # GET /layers/new.json | |||||
| def new | |||||
| @layer = Layer.new | |||||
| respond_to do |format| | |||||
| format.html # new.html.erb | |||||
| format.json { render json: @layer } | |||||
| end | |||||
| end | |||||
| # GET /layers/1/edit | |||||
| def edit | |||||
| @layer = Layer.find(params[:id]) | |||||
| end | |||||
| # POST /layers | |||||
| # POST /layers.json | |||||
| def create | |||||
| @layer = Layer.new(params[:layer]) | |||||
| respond_to do |format| | |||||
| if @layer.save | |||||
| format.html { redirect_to @layer, notice: 'Layer was successfully created.' } | |||||
| format.json { render json: @layer, status: :created, location: @layer } | |||||
| else | |||||
| format.html { render action: "new" } | |||||
| format.json { render json: @layer.errors, status: :unprocessable_entity } | |||||
| end | |||||
| end | |||||
| end | |||||
| # PUT /layers/1 | |||||
| # PUT /layers/1.json | |||||
| def update | |||||
| @layer = Layer.find(params[:id]) | |||||
| respond_to do |format| | |||||
| if @layer.update_attributes(params[:layer]) | |||||
| format.html { redirect_to @layer, notice: 'Layer was successfully updated.' } | |||||
| format.json { head :no_content } | |||||
| else | |||||
| format.html { render action: "edit" } | |||||
| format.json { render json: @layer.errors, status: :unprocessable_entity } | |||||
| end | |||||
| end | |||||
| end | |||||
| # DELETE /layers/1 | |||||
| # DELETE /layers/1.json | |||||
| def destroy | |||||
| @layer = Layer.find(params[:id]) | |||||
| @layer.destroy | |||||
| respond_to do |format| | |||||
| format.html { redirect_to layers_url } | |||||
| format.json { head :no_content } | |||||
| end | |||||
| end | |||||
| end | |||||
| @@ -2,13 +2,13 @@ class PostsController < ApplicationController | |||||
| include PostsHelper | include PostsHelper | ||||
| def near | def near | ||||
| @posts = Post.near(params[:lat].to_f, params[:lon].to_f, params[:radius].to_f) | |||||
| @posts = Post.near(params[:lat].to_f, params[:lon].to_f, params[:radius].to_f, params[:layer_id]) | |||||
| ErrorLog.create( | |||||
| :ts => Time.now, | |||||
| :params => params, | |||||
| :pois_returned => @posts.size | |||||
| ) | |||||
| # ErrorLog.create( | |||||
| # :ts => Time.now, | |||||
| # :params => params, | |||||
| # :pois_returned => @posts.size | |||||
| # ) | |||||
| layar_response = { | layar_response = { | ||||
| :layer => 'hyparlocal', | :layer => 'hyparlocal', | ||||
| @@ -0,0 +1,2 @@ | |||||
| module LayersHelper | |||||
| end | |||||
| @@ -6,15 +6,15 @@ module PostsHelper | |||||
| :imageURL => "%s%s/assets/layar-icons/tal-logo-100.png" % [ request.protocol, request.env['HTTP_HOST'] ], | :imageURL => "%s%s/assets/layar-icons/tal-logo-100.png" % [ request.protocol, request.env['HTTP_HOST'] ], | ||||
| :anchor => { | :anchor => { | ||||
| :geolocation => { | :geolocation => { | ||||
| :lat => post.loc['lat'], | |||||
| :lon => post.loc['lng'], | |||||
| :lat => post.lat, | |||||
| :lon => post.lon, | |||||
| :alt => 0 | :alt => 0 | ||||
| } | } | ||||
| }, | }, | ||||
| :text => { | :text => { | ||||
| :title => decode_entities(post.title), | :title => decode_entities(post.title), | ||||
| :description => clean_description(post.summary), | :description => clean_description(post.summary), | ||||
| :footnote => "From #{post.feed.title}" | |||||
| :footnote => "From #{post.feed_title}" | |||||
| }, | }, | ||||
| :actions => [ | :actions => [ | ||||
| { | { | ||||
| @@ -1,57 +1,45 @@ | |||||
| class Feed | |||||
| include MongoMapper::Document | |||||
| class Feed < ActiveRecord::Base | |||||
| has_many :posts, :dependent => :destroy | |||||
| has_and_belongs_to_many :layers | |||||
| attr_accessible :title, :url, :description, :generator, :last_fetched, :feed_url | |||||
| attr_accessor :new_layer_id # non model attribute used when creating new feeds from within a layer | |||||
| key :title, String, :default => "[New feed - hasn't been fetched yet]" | |||||
| key :feed_url, String # The URL of the RSS feed, not the website that owns it | |||||
| key :url, String # The URL of website. Called "link" in RSS 2.0 | |||||
| key :description, String | |||||
| key :guid, String # Atom id or RSS guid | |||||
| key :generator, String | |||||
| key :last_fetched, Time, :default => nil | |||||
| timestamps! | |||||
| ensure_index :title | |||||
| many :posts, :dependent => :destroy | |||||
| validates :title, :presence => true | |||||
| validates_format_of :feed_url, :with => URI::regexp(%w(http https)), :message => "must be a valid URL" | validates_format_of :feed_url, :with => URI::regexp(%w(http https)), :message => "must be a valid URL" | ||||
| after_create :get | |||||
| after_create :fetch | |||||
| # Fetch and parse feed contents from web | |||||
| def self.get_all | |||||
| Feed.all.each { |f| f.get } | |||||
| def self.fetch_all | |||||
| Feed.all.each { |f| f.fetch } | |||||
| end | end | ||||
| def get | |||||
| puts "Fetching feed: #{@url}" | |||||
| # Fetch and parse feed contents from web | |||||
| def fetch | |||||
| puts "Fetching feed: #{self.feed_url}" | |||||
| Feedzirra::Feed.add_common_feed_entry_element('georss:point', :as => :point) | Feedzirra::Feed.add_common_feed_entry_element('georss:point', :as => :point) | ||||
| Feedzirra::Feed.add_common_feed_entry_element('geo:lat', :as => :geo_lat) | Feedzirra::Feed.add_common_feed_entry_element('geo:lat', :as => :geo_lat) | ||||
| Feedzirra::Feed.add_common_feed_entry_element('geo:long', :as => :geo_long) | Feedzirra::Feed.add_common_feed_entry_element('geo:long', :as => :geo_long) | ||||
| Feedzirra::Feed.add_common_feed_element('generator', :as => :generator) | Feedzirra::Feed.add_common_feed_element('generator', :as => :generator) | ||||
| feed = Feedzirra::Feed.fetch_and_parse(@feed_url) | |||||
| feed = Feedzirra::Feed.fetch_and_parse(self.feed_url) | |||||
| self.set( | |||||
| self.update_attributes( | |||||
| :title => feed.title, | :title => feed.title, | ||||
| :url => feed.url, | :url => feed.url, | ||||
| :description => feed.description, | :description => feed.description, | ||||
| :generator => feed.generator, | :generator => feed.generator, | ||||
| :last_fetched => Time.now | |||||
| :last_fetched => DateTime.now | |||||
| ) | ) | ||||
| feed.entries.each do |e| | feed.entries.each do |e| | ||||
| if e.geo_lat && e.geo_long | if e.geo_lat && e.geo_long | ||||
| latlng = [e.geo_lat, e.geo_long] | |||||
| latlon = [e.geo_lat, e.geo_long] | |||||
| elsif e.point | elsif e.point | ||||
| latlng = e.point.split(' ') | |||||
| latlon = e.point.split(' ') | |||||
| else | else | ||||
| next | next | ||||
| end | |||||
| end | |||||
| attrs = { | attrs = { | ||||
| :title => e.title, | :title => e.title, | ||||
| :url => e.url, | :url => e.url, | ||||
| @@ -60,20 +48,15 @@ class Feed | |||||
| :content => e.content, | :content => e.content, | ||||
| :published => e.published, | :published => e.published, | ||||
| :guid => e.id, | :guid => e.id, | ||||
| :loc => { | |||||
| :lng => latlng[1].to_f, | |||||
| :lat => latlng[0].to_f | |||||
| } | |||||
| :lon => latlon[1].to_f, | |||||
| :lat => latlon[0].to_f | |||||
| } | } | ||||
| if Post.where(:url => e.url).size == 0 | |||||
| self.posts << Post.create(attrs) | |||||
| else | |||||
| Post.set({:url => e.url}, attrs) | |||||
| end | |||||
| # Create a new post or update an existing one | |||||
| post = Post.find_or_initialize_by_url(e.url) | |||||
| post.feed = self | |||||
| post.assign_attributes(attrs) | |||||
| post.save | |||||
| end | end | ||||
| end | end | ||||
| end | end | ||||
| @@ -0,0 +1,4 @@ | |||||
| class Layer < ActiveRecord::Base | |||||
| attr_accessible :name | |||||
| has_and_belongs_to_many :feeds | |||||
| end | |||||
| @@ -1,26 +1,70 @@ | |||||
| class Post | |||||
| include MongoMapper::Document | |||||
| key :title, String | |||||
| key :url, String | |||||
| key :author, String | |||||
| key :summary, String | |||||
| key :content, String | |||||
| key :published, Time | |||||
| key :loc, Hash # { lng, lat } | |||||
| timestamps! | |||||
| ensure_index [[:loc, '2d']] | |||||
| class Post < ActiveRecord::Base | |||||
| belongs_to :feed | belongs_to :feed | ||||
| EARTH_RADIUS_M = 6378000 | |||||
| EARTH_RADIUS_METRES = 6378000 | |||||
| def self.near(lat, lng, radius_m) | |||||
| all( | |||||
| :loc => { | |||||
| '$nearSphere' => [ lng, lat ], | |||||
| '$maxDistance' => radius_m / EARTH_RADIUS_M | |||||
| }) | |||||
| def self.near(lat, lon, radius_metres, layer_id) | |||||
| # Santize inputs. Is this necessary? | |||||
| lat = lat.to_f | |||||
| lon = lon.to_f | |||||
| radius_metres = radius_metres.to_i | |||||
| layer_id = layer_id.to_i | |||||
| # Calculate distance using the Haversine formula | |||||
| self.find_by_sql(<<-ENDQUERY | |||||
| SELECT | |||||
| p.id, | |||||
| p.title, | |||||
| p.summary, | |||||
| p.url, | |||||
| p.lat, | |||||
| p.lon, | |||||
| p.published, | |||||
| f.title as feed_title, | |||||
| ( #{EARTH_RADIUS_METRES} | |||||
| * acos( cos( radians('#{lat}') ) | |||||
| * cos( radians( p.lat ) ) | |||||
| * cos( radians( p.lon ) | |||||
| - radians('#{lon}') ) | |||||
| + sin( radians('#{lat}') ) | |||||
| * sin( radians( p.lat ) ) ) ) | |||||
| As distance | |||||
| FROM posts p | |||||
| INNER JOIN feeds f | |||||
| ON p.feed_id = f.id | |||||
| WHERE | |||||
| p.id IN | |||||
| ( -- Subquery returns a list of post_ids for posts on this layer | |||||
| SELECT p.id | |||||
| FROM feeds_layers fl | |||||
| INNER JOIN feeds f | |||||
| ON fl.feed_id = f.id | |||||
| INNER JOIN posts p | |||||
| ON p.feed_id = f.id | |||||
| WHERE fl.layer_id = #{layer_id} | |||||
| ) | |||||
| AND | |||||
| ( | |||||
| #{EARTH_RADIUS_METRES} | |||||
| * acos( cos( radians('#{lat}') ) | |||||
| * cos( radians( p.lat ) ) | |||||
| * cos( radians( p.lon ) | |||||
| - radians('#{lon}') ) | |||||
| + sin( radians('#{lat}') ) | |||||
| * sin( radians( p.lat ) ) ) | |||||
| ) | |||||
| <= #{radius_metres} | |||||
| ORDER BY distance | |||||
| ENDQUERY | |||||
| ) | |||||
| end | end | ||||
| end | end | ||||
| @@ -1,11 +1,4 @@ | |||||
| class User | |||||
| include MongoMapper::Document | |||||
| key :email, String | |||||
| key :crypted_password, String | |||||
| key :salt, String | |||||
| timestamps! | |||||
| class User < ActiveRecord::Base | |||||
| authenticates_with_sorcery! | authenticates_with_sorcery! | ||||
| # attr_accessible :email, :password, :password_confirmation | # attr_accessible :email, :password, :password_confirmation | ||||
| @@ -14,5 +7,4 @@ class User | |||||
| validates_uniqueness_of :email | validates_uniqueness_of :email | ||||
| validates_length_of :password, :minimum => 3, :message => "password must be at least 3 characters long", :if => :password | validates_length_of :password, :minimum => 3, :message => "password must be at least 3 characters long", :if => :password | ||||
| validates_confirmation_of :password, :message => "should match confirmation", :if => :password | validates_confirmation_of :password, :message => "should match confirmation", :if => :password | ||||
| end | end | ||||
| @@ -15,6 +15,7 @@ | |||||
| .field | .field | ||||
| = f.label "URL" | = f.label "URL" | ||||
| = f.text_field :feed_url, :size => 120 | = f.text_field :feed_url, :size => 120 | ||||
| = f.hidden_field :new_layer_id, :value => @feed.new_layer_id | |||||
| = f.submit 'Save', :id => 'submit' | = f.submit 'Save', :id => 'submit' | ||||
| -# | -# | ||||
| .field | .field | ||||
| @@ -28,7 +28,7 @@ | |||||
| - @posts.each do |p| | - @posts.each do |p| | ||||
| %tr | %tr | ||||
| %td= link_to p.title, p.url | %td= link_to p.title, p.url | ||||
| %td= link_to "Map", "https://maps.google.co.uk/maps?q=%s,%s&hl=en&z=18" % [ p.loc['lat'], p.loc['lng'] ], :target => "_blank" | |||||
| %td= link_to "Map", "https://maps.google.co.uk/maps?q=%s,%s&hl=en&z=18" % [ p.lat, p.lon ], :target => "_blank" | |||||
| %td | %td | ||||
| - unless p.published.nil? | - unless p.published.nil? | ||||
| = p.published.strftime("%d %b %Y %H:%M") | = p.published.strftime("%d %b %Y %H:%M") | ||||
| @@ -0,0 +1,13 @@ | |||||
| = form_for @layer do |f| | |||||
| - if @layer.errors.any? | |||||
| #error_explanation | |||||
| %h2= "#{pluralize(@layer.errors.count, "error")} prohibited this layer from being saved:" | |||||
| %ul | |||||
| - @layer.errors.full_messages.each do |msg| | |||||
| %li= msg | |||||
| .field | |||||
| = f.label :name | |||||
| = f.text_field :name | |||||
| .actions | |||||
| = f.submit 'Save' | |||||
| @@ -0,0 +1,7 @@ | |||||
| %h1 Editing layer | |||||
| = render 'form' | |||||
| = link_to 'Show', @layer | |||||
| \| | |||||
| = link_to 'Back', layers_path | |||||
| @@ -0,0 +1,10 @@ | |||||
| %h1 Layers | |||||
| = link_to 'New Layer', new_layer_path, :class => 'button' | |||||
| %table | |||||
| - @layers.each do |layer| | |||||
| %tr | |||||
| %td= link_to layer.name, layer | |||||
| %td= link_to 'Edit', edit_layer_path(layer) | |||||
| %td= link_to 'Delete', layer, :method => :delete, :data => { :confirm => 'Are you sure?' } | |||||
| @@ -0,0 +1,5 @@ | |||||
| %h1 New layer | |||||
| = render 'form' | |||||
| = link_to 'Back', layers_path | |||||
| @@ -0,0 +1,51 @@ | |||||
| %h1 | |||||
| Layer: | |||||
| = @layer.name | |||||
| %p | |||||
| API URL: | |||||
| = "#{request.protocol}#{request.host_with_port}/posts/near.json?layer_id=#{@layer.id}" | |||||
| %h2 Feeds | |||||
| #new_feed | |||||
| = render 'feeds/form' | |||||
| %p= link_to "Fetch all", :fetch_all, :class => "button" | |||||
| %table | |||||
| %tr | |||||
| %th | |||||
| %th Posts | |||||
| %th Fetched | |||||
| %th | |||||
| %th | |||||
| %th | |||||
| - @layer.feeds.each do |f| | |||||
| %tr | |||||
| %td | |||||
| .feed_title | |||||
| = link_to f.title, f | |||||
| %td.right= f.posts.count | |||||
| %td | |||||
| - if f.last_fetched.nil? | |||||
| never | |||||
| - else | |||||
| = (time_ago_in_words(f.last_fetched) + " ago").gsub(/ +/, " ").html_safe | |||||
| %td= link_to "Fetch", fetch_feed_url(f), :class => "button" | |||||
| %td= link_to 'Edit', edit_feed_path(f), :class => "button" | |||||
| %td= link_to 'Delete', f, :confirm => "Delete #{f.title} and all its posts?", :method => :delete, :class => "button" | |||||
| %tr | |||||
| %td | |||||
| %td.right [number of posts in this layer] | |||||
| %td | |||||
| %td | |||||
| %td | |||||
| %td | |||||
| = link_to 'Edit', edit_layer_path(@layer) | |||||
| \| | |||||
| = link_to 'Back', layers_path | |||||
| @@ -1,11 +1,20 @@ | |||||
| %h1 Posts#near | %h1 Posts#near | ||||
| %table | %table | ||||
| %thead | |||||
| %tr | |||||
| %th | |||||
| %th Latitude | |||||
| %th Longitude | |||||
| %th Distance (metres) | |||||
| %th Published | |||||
| %tbody | |||||
| - @posts.each do |p| | - @posts.each do |p| | ||||
| %tr | %tr | ||||
| %td= link_to p.title, p.url | %td= link_to p.title, p.url | ||||
| %td= p.loc['lat'] | |||||
| %td= p.loc['lng'] | |||||
| %td= p.lat | |||||
| %td= p.lon | |||||
| %td= p.distance.to_i | |||||
| %td | %td | ||||
| - unless p.published.nil? | - unless p.published.nil? | ||||
| = p.published.strftime("%d %b %Y %H:%M") | = p.published.strftime("%d %b %Y %H:%M") | ||||
| @@ -1,12 +1,13 @@ | |||||
| require File.expand_path('../boot', __FILE__) | require File.expand_path('../boot', __FILE__) | ||||
| # Pick the frameworks you want: | # Pick the frameworks you want: | ||||
| require 'rails/all' | |||||
| # require "active_record/railtie" | # require "active_record/railtie" | ||||
| require "action_controller/railtie" | |||||
| require "action_mailer/railtie" | |||||
| require "active_resource/railtie" | |||||
| require "sprockets/railtie" | |||||
| require "rails/test_unit/railtie" | |||||
| # require "action_controller/railtie" | |||||
| # require "action_mailer/railtie" | |||||
| # require "active_resource/railtie" | |||||
| # require "sprockets/railtie" | |||||
| # require "rails/test_unit/railtie" | |||||
| if defined?(Bundler) | if defined?(Bundler) | ||||
| # If you precompile assets before deploying to production, use this line | # If you precompile assets before deploying to production, use this line | ||||
| @@ -15,7 +16,6 @@ if defined?(Bundler) | |||||
| # Bundler.require(:default, :assets, Rails.env) | # Bundler.require(:default, :assets, Rails.env) | ||||
| end | end | ||||
| module Apollo | module Apollo | ||||
| class Application < Rails::Application | class Application < Rails::Application | ||||
| config.action_mailer.default_url_options = { :host => ENV['APOLLO_HOSTNAME'] } | config.action_mailer.default_url_options = { :host => ENV['APOLLO_HOSTNAME'] } | ||||
| @@ -72,10 +72,7 @@ module Apollo | |||||
| # http://mongomapper.com/documentation/getting-started/rails.html | # http://mongomapper.com/documentation/getting-started/rails.html | ||||
| config.generators do |g| | config.generators do |g| | ||||
| g.template_engine :haml | g.template_engine :haml | ||||
| g.orm :mongo_mapper | |||||
| end | end | ||||
| MongoMapper::Document.plugin(MongoMapper::Plugins::MultiParameterAttributes) | |||||
| # Time.zone = 'London' | # Time.zone = 'London' | ||||
| end | end | ||||
| end | end | ||||
| @@ -0,0 +1,26 @@ | |||||
| development: | |||||
| adapter: postgresql | |||||
| encoding: unicode | |||||
| database: apollo_development | |||||
| pool: 5 | |||||
| username: postgres | |||||
| password: | |||||
| port: 5433 | |||||
| # Warning: The database defined as "test" will be erased and | |||||
| # re-generated from your development database when you run "rake". | |||||
| # Do not set this db to the same as development or production. | |||||
| test: | |||||
| adapter: postgresql | |||||
| encoding: unicode | |||||
| database: apollo_test | |||||
| pool: 5 | |||||
| username: postgres | |||||
| password: | |||||
| port: 5433 | |||||
| production: | |||||
| adapter: sqlite3 | |||||
| database: db/production.sqlite3 | |||||
| pool: 5 | |||||
| timeout: 5000 | |||||
| @@ -1,15 +0,0 @@ | |||||
| defaults: &defaults | |||||
| host: 127.0.0.1 | |||||
| port: 27017 | |||||
| development: | |||||
| <<: *defaults | |||||
| database: apollo_development | |||||
| test: | |||||
| <<: *defaults | |||||
| database: apollo_test | |||||
| # set these environment variables on your prod server | |||||
| production: | |||||
| uri: <%= ENV['MONGOLAB_URI'] %> | |||||
| @@ -2,6 +2,7 @@ Apollo::Application.routes.draw do | |||||
| get "logout" => "sessions#destroy", :as => "logout" | get "logout" => "sessions#destroy", :as => "logout" | ||||
| get "login" => "sessions#new", :as => "login" | get "login" => "sessions#new", :as => "login" | ||||
| resources :layers | |||||
| resources :users | resources :users | ||||
| resources :sessions | resources :sessions | ||||
| resources :password_resets | resources :password_resets | ||||
| @@ -65,7 +66,7 @@ Apollo::Application.routes.draw do | |||||
| # You can have the root of your site routed with "root" | # You can have the root of your site routed with "root" | ||||
| # just remember to delete public/index.html. | # just remember to delete public/index.html. | ||||
| root :to => 'feeds#index' | |||||
| root :to => 'layers#index' | |||||
| # See how all your routes lay out with "rake routes" | # See how all your routes lay out with "rake routes" | ||||
| @@ -0,0 +1,16 @@ | |||||
| class SorceryCore < ActiveRecord::Migration | |||||
| def self.up | |||||
| create_table :users do |t| | |||||
| t.string :username, :null => false # if you use another field as a username, for example email, you can safely remove this field. | |||||
| t.string :email, :default => nil # if you use this field as a username, you might want to make it :null => false. | |||||
| t.string :crypted_password, :default => nil | |||||
| t.string :salt, :default => nil | |||||
| t.timestamps | |||||
| end | |||||
| end | |||||
| def self.down | |||||
| drop_table :users | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,9 @@ | |||||
| class RemoveUserUsername < ActiveRecord::Migration | |||||
| def up | |||||
| remove_column :users, :username | |||||
| end | |||||
| def down | |||||
| add_column :users, :username, :string, :null => false | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,6 @@ | |||||
| class AddGuidToPostsRemoveGuidFromFeeds < ActiveRecord::Migration | |||||
| def change | |||||
| remove_column :feeds, :guid | |||||
| add_column :posts, :guid, :string | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,11 @@ | |||||
| class FeedsChangeLastFetchedToDatetime < ActiveRecord::Migration | |||||
| def up | |||||
| remove_column :feeds, :last_fetched | |||||
| add_column :feeds, :last_fetched, :datetime | |||||
| end | |||||
| def down | |||||
| remove_column :feeds, :last_fetched | |||||
| add_column :feeds, :last_fetched, :time | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,11 @@ | |||||
| class PostPublishedChangeToDatetime < ActiveRecord::Migration | |||||
| def up | |||||
| remove_column :posts, :published | |||||
| add_column :posts, :published, :datetime | |||||
| end | |||||
| def down | |||||
| remove_column :posts, :published | |||||
| add_column :posts, :published, :time | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,15 @@ | |||||
| class CreateLayers < ActiveRecord::Migration | |||||
| def change | |||||
| create_table :layers do |t| | |||||
| t.string :name | |||||
| t.timestamps | |||||
| end | |||||
| create_table :feeds_layers, :id => false do |t| | |||||
| t.integer :feed_id | |||||
| t.integer :layer_id | |||||
| end | |||||
| add_index :feeds_layers, [ :feed_id, :layer_id ] | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,63 @@ | |||||
| # encoding: UTF-8 | |||||
| # This file is auto-generated from the current state of the database. Instead | |||||
| # of editing this file, please use the migrations feature of Active Record to | |||||
| # incrementally modify your database, and then regenerate this schema definition. | |||||
| # | |||||
| # Note that this schema.rb definition is the authoritative source for your | |||||
| # database schema. If you need to create the application database on another | |||||
| # system, you should be using db:schema:load, not running all the migrations | |||||
| # from scratch. The latter is a flawed and unsustainable approach (the more migrations | |||||
| # you'll amass, the slower it'll run and the greater likelihood for issues). | |||||
| # | |||||
| # It's strongly recommended to check this file into your version control system. | |||||
| ActiveRecord::Schema.define(:version => 20130228150425) do | |||||
| create_table "feeds", :force => true do |t| | |||||
| t.string "title" | |||||
| t.string "feed_url" | |||||
| t.string "url" | |||||
| t.string "description" | |||||
| t.string "generator" | |||||
| t.datetime "created_at", :null => false | |||||
| t.datetime "updated_at", :null => false | |||||
| t.datetime "last_fetched" | |||||
| end | |||||
| create_table "feeds_layers", :id => false, :force => true do |t| | |||||
| t.integer "feed_id" | |||||
| t.integer "layer_id" | |||||
| end | |||||
| add_index "feeds_layers", ["feed_id", "layer_id"], :name => "index_feeds_layers_on_feed_id_and_layer_id" | |||||
| create_table "layers", :force => true do |t| | |||||
| t.string "name" | |||||
| t.datetime "created_at", :null => false | |||||
| t.datetime "updated_at", :null => false | |||||
| end | |||||
| create_table "posts", :force => true do |t| | |||||
| t.string "title" | |||||
| t.string "url" | |||||
| t.string "author" | |||||
| t.text "summary" | |||||
| t.text "content" | |||||
| t.decimal "lat" | |||||
| t.decimal "lon" | |||||
| t.integer "feed_id" | |||||
| t.datetime "created_at", :null => false | |||||
| t.datetime "updated_at", :null => false | |||||
| t.string "guid" | |||||
| t.datetime "published" | |||||
| end | |||||
| create_table "users", :force => true do |t| | |||||
| t.string "email" | |||||
| t.string "crypted_password" | |||||
| t.string "salt" | |||||
| t.datetime "created_at", :null => false | |||||
| t.datetime "updated_at", :null => false | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,7 @@ | |||||
| # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html | |||||
| one: | |||||
| name: MyString | |||||
| two: | |||||
| name: MyString | |||||
| @@ -0,0 +1,49 @@ | |||||
| require 'test_helper' | |||||
| class LayersControllerTest < ActionController::TestCase | |||||
| setup do | |||||
| @layer = layers(:one) | |||||
| end | |||||
| test "should get index" do | |||||
| get :index | |||||
| assert_response :success | |||||
| assert_not_nil assigns(:layers) | |||||
| end | |||||
| test "should get new" do | |||||
| get :new | |||||
| assert_response :success | |||||
| end | |||||
| test "should create layer" do | |||||
| assert_difference('Layer.count') do | |||||
| post :create, layer: { name: @layer.name } | |||||
| end | |||||
| assert_redirected_to layer_path(assigns(:layer)) | |||||
| end | |||||
| test "should show layer" do | |||||
| get :show, id: @layer | |||||
| assert_response :success | |||||
| end | |||||
| test "should get edit" do | |||||
| get :edit, id: @layer | |||||
| assert_response :success | |||||
| end | |||||
| test "should update layer" do | |||||
| put :update, id: @layer, layer: { name: @layer.name } | |||||
| assert_redirected_to layer_path(assigns(:layer)) | |||||
| end | |||||
| test "should destroy layer" do | |||||
| assert_difference('Layer.count', -1) do | |||||
| delete :destroy, id: @layer | |||||
| end | |||||
| assert_redirected_to layers_path | |||||
| end | |||||
| end | |||||
| @@ -0,0 +1,4 @@ | |||||
| require 'test_helper' | |||||
| class LayersHelperTest < ActionView::TestCase | |||||
| end | |||||
| @@ -0,0 +1,7 @@ | |||||
| require 'test_helper' | |||||
| class LayerTest < ActiveSupport::TestCase | |||||
| # test "the truth" do | |||||
| # assert true | |||||
| # end | |||||
| end | |||||