@@ -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 |