diff --git a/Gemfile b/Gemfile index 1991a97bf..0ac95bcb7 100644 --- a/Gemfile +++ b/Gemfile @@ -2,9 +2,9 @@ source 'https://rubygems.org' gem 'bundler', '>=1.1.5' -gem 'rails', '3.2.11' +gem 'rails', '3.2.12' gem 'rack', '~>1.4.5' - +gem 'json', '~>1.7.7' gem 'haml' gem 'cancan' @@ -26,7 +26,7 @@ group :assets do # long term, we'll probably want node.js for performance, but this will do for now as it's easier for new people to install gem 'therubyracer', '~> 0.10.2', :platforms => :ruby gem "less-rails" - gem "twitter-bootstrap-rails", '2.1.6' + gem "twitter-bootstrap-rails", '~> 2.2.2' gem 'uglifier', '>= 1.0.3' @@ -64,6 +64,8 @@ gem 'friendly_id' # gravatars gem 'gravatar-ultimate' +gem 'geocoder' + # for phusion passenger (i.e. mod_rails) on the server gem 'passenger' gem 'rake', '>= 10.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index a868300ba..6f7a54958 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,31 +1,31 @@ GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.11) - actionpack (= 3.2.11) + actionmailer (3.2.12) + actionpack (= 3.2.12) mail (~> 2.4.4) - actionpack (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) + actionpack (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) builder (~> 3.0.0) erubis (~> 2.7.0) journey (~> 1.0.4) - rack (~> 1.4.0) + rack (~> 1.4.5) rack-cache (~> 1.2) rack-test (~> 0.6.1) sprockets (~> 2.2.1) - activemodel (3.2.11) - activesupport (= 3.2.11) + activemodel (3.2.12) + activesupport (= 3.2.12) builder (~> 3.0.0) - activerecord (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) + activerecord (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activeresource (3.2.11) - activemodel (= 3.2.11) - activesupport (= 3.2.11) - activesupport (3.2.11) + activeresource (3.2.12) + activemodel (= 3.2.12) + activesupport (= 3.2.12) + activesupport (3.2.12) i18n (~> 0.6) multi_json (~> 1.0) arel (3.0.2) @@ -85,12 +85,14 @@ GEM fastthread (1.0.7) friendly_id (4.0.9) fssm (0.2.10) + geocoder (1.1.6) gravatar-ultimate (1.0.3) - haml (3.1.7) - haml-rails (0.3.5) + haml (4.0.0) + tilt + haml-rails (0.4) actionpack (>= 3.1, < 4.1) activesupport (>= 3.1, < 4.1) - haml (~> 3.1) + haml (>= 3.1, < 4.1) railties (>= 3.1, < 4.1) highline (1.6.15) hike (1.2.1) @@ -99,7 +101,7 @@ GEM jquery-rails (2.2.1) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) - json (1.7.6) + json (1.7.7) less (2.2.2) commonjs (~> 0.2.6) less-rails (2.2.6) @@ -110,8 +112,8 @@ GEM i18n (>= 0.4.0) mime-types (~> 1.16) treetop (~> 1.4.8) - mime-types (1.20.1) - multi_json (1.5.0) + mime-types (1.21) + multi_json (1.6.1) net-scp (1.1.0) net-ssh (>= 2.6.5) net-sftp (2.1.1) @@ -135,17 +137,17 @@ GEM rack rack-test (0.6.2) rack (>= 1.0) - rails (3.2.11) - actionmailer (= 3.2.11) - actionpack (= 3.2.11) - activerecord (= 3.2.11) - activeresource (= 3.2.11) - activesupport (= 3.2.11) + rails (3.2.12) + actionmailer (= 3.2.12) + actionpack (= 3.2.12) + activerecord (= 3.2.12) + activeresource (= 3.2.12) + activesupport (= 3.2.12) bundler (~> 1.0) - railties (= 3.2.11) - railties (3.2.11) - actionpack (= 3.2.11) - activesupport (= 3.2.11) + railties (= 3.2.12) + railties (3.2.12) + actionpack (= 3.2.12) + activesupport (= 3.2.12) rack-ssl (~> 1.3.2) rake (>= 0.8.7) rdoc (~> 3.4) @@ -189,7 +191,7 @@ GEM treetop (1.4.12) polyglot polyglot (>= 0.3.1) - twitter-bootstrap-rails (2.1.6) + twitter-bootstrap-rails (2.2.4) actionpack (>= 3.1) execjs railties (>= 3.1) @@ -222,15 +224,17 @@ DEPENDENCIES diff-lcs factory_girl_rails (~> 4.0) friendly_id + geocoder gravatar-ultimate haml haml-rails jquery-rails + json (~> 1.7.7) less-rails passenger pg rack (~> 1.4.5) - rails (= 3.2.11) + rails (= 3.2.12) rake (>= 10.0.0) rspec-rails (~> 2.12.1) rvm-capistrano @@ -239,7 +243,7 @@ DEPENDENCIES sqlite3 therubyracer (~> 0.10.2) thin - twitter-bootstrap-rails (= 2.1.6) + twitter-bootstrap-rails (~> 2.2.2) uglifier (>= 1.0.3) watchr webrat diff --git a/app/assets/javascripts/forums.js.coffee b/app/assets/javascripts/forums.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/forums.js.coffee @@ -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/ diff --git a/app/assets/javascripts/roles.js.coffee b/app/assets/javascripts/roles.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/roles.js.coffee @@ -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/ diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 60f15f7bb..d87769d3e 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -2,16 +2,16 @@ @import "twitter/bootstrap/responsive"; // Set the correct sprite paths -@iconSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings"); -@iconWhiteSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings-white"); +@iconSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings.png"); +@iconWhiteSpritePath: asset-path("twitter/bootstrap/glyphicons-halflings-white.png"); // Set the Font Awesome (Font Awesome is default. You can disable by commenting below lines) -// Note: If you use asset_path() here, your compiled boostrap_and_overrides.css will not +// Note: If you use asset_path() here, your compiled bootstrap_and_overrides.css will not // have the proper paths. So for now we use the absolute path. -@fontAwesomeEotPath: asset-path("fontawesome-webfont.eot"); -@fontAwesomeWoffPath: asset-path("fontawesome-webfont.woff"); -@fontAwesomeTtfPath: asset-path("fontawesome-webfont.ttf"); -@fontAwesomeSvgPath: asset-path("fontawesome-webfont.svg"); +@fontAwesomeEotPath: asset-path("fontawesome-webfont.eot?v=3.0.2"); +@fontAwesomeEotPath_iefix: asset-path("fontawesome-webfont.eot?#iefix&v=3.0.2"); +@fontAwesomeWoffPath: asset-path("fontawesome-webfont.woff?v=3.0.2"); +@fontAwesomeTtfPath: asset-path("fontawesome-webfont.ttf?v=3.0.2"); // Font Awesome @import "fontawesome"; diff --git a/app/controllers/forums_controller.rb b/app/controllers/forums_controller.rb new file mode 100644 index 000000000..6dbfb06ef --- /dev/null +++ b/app/controllers/forums_controller.rb @@ -0,0 +1,84 @@ +class ForumsController < ApplicationController + load_and_authorize_resource + # GET /forums + # GET /forums.json + def index + @forums = Forum.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @forums } + end + end + + # GET /forums/1 + # GET /forums/1.json + def show + @forum = Forum.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @forum } + end + end + + # GET /forums/new + # GET /forums/new.json + def new + @forum = Forum.new + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @forum } + end + end + + # GET /forums/1/edit + def edit + @forum = Forum.find(params[:id]) + end + + # POST /forums + # POST /forums.json + def create + @forum = Forum.new(params[:forum]) + + respond_to do |format| + if @forum.save + format.html { redirect_to @forum, notice: 'Forum was successfully created.' } + format.json { render json: @forum, status: :created, location: @forum } + else + format.html { render action: "new" } + format.json { render json: @forum.errors, status: :unprocessable_entity } + end + end + end + + # PUT /forums/1 + # PUT /forums/1.json + def update + @forum = Forum.find(params[:id]) + + respond_to do |format| + if @forum.update_attributes(params[:forum]) + format.html { redirect_to @forum, notice: 'Forum was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @forum.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /forums/1 + # DELETE /forums/1.json + def destroy + @forum = Forum.find(params[:id]) + @forum.destroy + + respond_to do |format| + format.html { redirect_to forums_url } + format.json { head :no_content } + end + end +end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index ba2ee6e04..f78a476c6 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -29,6 +29,7 @@ class PostsController < ApplicationController # GET /posts/new.json def new @post = Post.new + @forum = Forum.find_by_id(params[:forum_id]) || Forum.new respond_to do |format| format.html # new.html.haml diff --git a/app/controllers/roles_controller.rb b/app/controllers/roles_controller.rb new file mode 100644 index 000000000..04cdcecb6 --- /dev/null +++ b/app/controllers/roles_controller.rb @@ -0,0 +1,84 @@ +class RolesController < ApplicationController + load_and_authorize_resource + # GET /roles + # GET /roles.json + def index + @roles = Role.all + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @roles } + end + end + + # GET /roles/1 + # GET /roles/1.json + def show + @role = Role.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @role } + end + end + + # GET /roles/new + # GET /roles/new.json + def new + @role = Role.new + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @role } + end + end + + # GET /roles/1/edit + def edit + @role = Role.find(params[:id]) + end + + # POST /roles + # POST /roles.json + def create + @role = Role.new(params[:role]) + + respond_to do |format| + if @role.save + format.html { redirect_to @role, notice: 'Role was successfully created.' } + format.json { render json: @role, status: :created, location: @role } + else + format.html { render action: "new" } + format.json { render json: @role.errors, status: :unprocessable_entity } + end + end + end + + # PUT /roles/1 + # PUT /roles/1.json + def update + @role = Role.find(params[:id]) + + respond_to do |format| + if @role.update_attributes(params[:role]) + format.html { redirect_to @role, notice: 'Role was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @role.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /roles/1 + # DELETE /roles/1.json + def destroy + @role = Role.find(params[:id]) + @role.destroy + + respond_to do |format| + format.html { redirect_to roles_url } + format.json { head :no_content } + end + end +end diff --git a/app/helpers/forums_helper.rb b/app/helpers/forums_helper.rb new file mode 100644 index 000000000..2e531fd46 --- /dev/null +++ b/app/helpers/forums_helper.rb @@ -0,0 +1,2 @@ +module ForumsHelper +end diff --git a/app/helpers/roles_helper.rb b/app/helpers/roles_helper.rb new file mode 100644 index 000000000..6b6b6cc22 --- /dev/null +++ b/app/helpers/roles_helper.rb @@ -0,0 +1,2 @@ +module RolesHelper +end diff --git a/app/models/ability.rb b/app/models/ability.rb index 63f25f328..54052eebc 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -7,18 +7,27 @@ class Ability # everyone can do these things, even non-logged in can :read, :all + # nobody should be able to view this except admins + cannot :read, Role + if member + + if member.has_role? :admin + # admin user roles (for authorization) + can :read, Role + can :manage, Role + + # for now, only admins can create/edit forums + can :manage, Forum + end + # managing your own user settings can :update, Member, :id => member.id # for now, anyone can create/edit/destroy crops # (later, we probably want to limit this to a role) - can :create, Crop - can :update, Crop - can :destroy, Crop - can :create, ScientificName - can :update, ScientificName - can :destroy, ScientificName + can :manage, Crop + can :manage, ScientificName # anyone can create a post, or comment on a post, # but only the author can edit/destroy it. diff --git a/app/models/forum.rb b/app/models/forum.rb new file mode 100644 index 000000000..b673dbf19 --- /dev/null +++ b/app/models/forum.rb @@ -0,0 +1,12 @@ +class Forum < ActiveRecord::Base + extend FriendlyId + friendly_id :name, use: :slugged + attr_accessible :description, :name, :owner_id, :slug + has_many :posts + belongs_to :owner, :class_name => "Member" + + def to_s + return name + end + +end diff --git a/app/models/member.rb b/app/models/member.rb index cd7785c36..ed6dae747 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -5,6 +5,8 @@ class Member < ActiveRecord::Base has_many :posts, :foreign_key => 'author_id' has_many :comments, :foreign_key => 'author_id' has_many :gardens, :foreign_key => 'owner_id' + has_many :forums, :foreign_key => 'owner_id' + has_and_belongs_to_many :roles # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, @@ -15,7 +17,13 @@ class Member < ActiveRecord::Base # Setup accessible (or protected) attributes for your model attr_accessible :login_name, :email, :password, :password_confirmation, - :remember_me, :login, :tos_agreement, :show_email + :remember_me, :login, :tos_agreement, :show_email, + :location, :latitude, :longitude + + # set up geocoding + geocoded_by :location + after_validation :geocode + after_validation :empty_unwanted_geocodes # Virtual attribute for authenticating by either username or email # This is in addition to a real persisted field like 'username' @@ -51,4 +59,17 @@ class Member < ActiveRecord::Base def to_s return login_name end + + def has_role?(role_sym) + roles.any? { |r| r.name.underscore.to_sym == role_sym } + end + + protected + def empty_unwanted_geocodes + if self.location.to_s == '' + self.latitude = nil + self.longitude = nil + end + end + end diff --git a/app/models/post.rb b/app/models/post.rb index e38920623..3ff4fa8f4 100644 --- a/app/models/post.rb +++ b/app/models/post.rb @@ -1,8 +1,9 @@ class Post < ActiveRecord::Base extend FriendlyId friendly_id :author_date_subject, use: :slugged - attr_accessible :body, :subject, :author_id + attr_accessible :body, :subject, :author_id, :forum_id belongs_to :author, :class_name => 'Member' + belongs_to :forum has_many :comments, :dependent => :destroy default_scope order("created_at desc") @@ -11,4 +12,19 @@ class Post < ActiveRecord::Base time = created_at || Time.now "#{author.login_name} #{time.strftime("%Y%m%d")} #{subject}" end + + def comment_count + self.comments.count + end + + def recent_activity + self.comments.last ? self.comments.last.created_at : self.created_at + end + + def Post.recently_active + Post.all.sort do |a,b| + b.recent_activity <=> a.recent_activity + end + end + end diff --git a/app/models/role.rb b/app/models/role.rb new file mode 100644 index 000000000..f05ea0e3a --- /dev/null +++ b/app/models/role.rb @@ -0,0 +1,6 @@ +class Role < ActiveRecord::Base + extend FriendlyId + friendly_id :name, use: :slugged + attr_accessible :description, :name, :members, :slug + has_and_belongs_to_many :members +end diff --git a/app/views/devise/registrations/edit.html.haml b/app/views/devise/registrations/edit.html.haml index 05450c0b1..ebee1bfcb 100644 --- a/app/views/devise/registrations/edit.html.haml +++ b/app/views/devise/registrations/edit.html.haml @@ -11,28 +11,37 @@ = f.email_field :email %span.help-inline If you change your email address you will have to reconfirm + %h2 Profile details + + .control-group + =f.label :location, 'Your location', :class => 'control-label' + .controls + =f.text_field :location + %span.help-inline Be as detailed or vague as you like. + .control-group .controls = f.check_box :show_email Show email publicly on your profile page %h2 Change password - - .control-group - = f.label :password, :class => 'control-label' - .controls - = f.password_field :password, :autocomplete => "off" - %span.help-inline Leave blank if you don't want to change your password - - .control-group - = f.label :password_confirmation, :class => 'control-label' - .controls= f.password_field :password_confirmation + %p + %span.help-block Leave blank if you don't want to change your password .control-group = f.label :current_password, :class => 'control-label' .controls = f.password_field :current_password + .control-group + = f.label :password, "New password", :class => 'control-label' + .controls + = f.password_field :password, :autocomplete => "off" + + .control-group + = f.label :password_confirmation, :class => 'control-label' + .controls= f.password_field :password_confirmation + .form-actions = f.submit "Update", :class => 'btn' diff --git a/app/views/forums/_form.html.haml b/app/views/forums/_form.html.haml new file mode 100644 index 000000000..116af4f2e --- /dev/null +++ b/app/views/forums/_form.html.haml @@ -0,0 +1,19 @@ += form_for @forum, :html => { :class => 'form-horizontal' } do |f| + - if @forum.errors.any? + #error_explanation + %h2= "#{pluralize(@forum.errors.count, "error")} prohibited this forum from being saved:" + %ul + - @forum.errors.full_messages.each do |msg| + %li= msg + + .control-group + = f.label :name, :class => 'control-label' + .controls= f.text_field :name, :class => 'input-block-level' + .control-group + = f.label :description, :class => 'control-label' + .controls= f.text_area :description, :rows => 6, :class => 'input-block-level' + .control-group + = f.label :owner_id, :class => 'control-label' + .controls= collection_select(:forum, :owner_id, Member.all, :id, :login_name) + .form-actions + = f.submit 'Save', :class => 'btn' diff --git a/app/views/forums/edit.html.haml b/app/views/forums/edit.html.haml new file mode 100644 index 000000000..f334a2092 --- /dev/null +++ b/app/views/forums/edit.html.haml @@ -0,0 +1,7 @@ +- content_for :title, "Editing forum" + += render 'form' + += link_to 'Show', @forum +\| += link_to 'Back', forums_path diff --git a/app/views/forums/index.html.haml b/app/views/forums/index.html.haml new file mode 100644 index 000000000..eddb9c97d --- /dev/null +++ b/app/views/forums/index.html.haml @@ -0,0 +1,12 @@ +- content_for :title, "Forums" + +- @forums.each do |forum| + %h2= forum + %p + = pluralize(forum.posts.count, "post") + | + =link_to "Post", new_post_path(:forum_id => forum.id) + | + =link_to "Visit forum", forum + =render :partial => "posts/summary", :locals => { :posts => forum.posts, :howmany => 4 } + diff --git a/app/views/forums/new.html.haml b/app/views/forums/new.html.haml new file mode 100644 index 000000000..59e6ef782 --- /dev/null +++ b/app/views/forums/new.html.haml @@ -0,0 +1,5 @@ +- content_for :title, "New Forum" + += render 'form' + += link_to 'Back', forums_path diff --git a/app/views/forums/show.html.haml b/app/views/forums/show.html.haml new file mode 100644 index 000000000..eb678c971 --- /dev/null +++ b/app/views/forums/show.html.haml @@ -0,0 +1,25 @@ +- content_for :title, @forum.name + +%p#notice= notice + +%p + :markdown + #{ strip_tags(@forum.description) } +%p + This forum is run by + = link_to @forum.owner, @forum.owner + +- if can? :edit, @forum + =link_to "Edit", edit_forum_path(@forum), :class => 'btn' + +%h2 + Posts + =link_to "Post something", new_post_path(:forum_id => @forum.id), :class => 'btn' + +- if @forum.posts.count > 0 + =render :partial => "posts/summary", :locals => { :posts => @forum.posts } +- else + No posts yet. + + + diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml index d1bb8d7e0..0f7937a4e 100644 --- a/app/views/layouts/_header.html.haml +++ b/app/views/layouts/_header.html.haml @@ -11,6 +11,7 @@ %li= link_to "Crops", crops_path %li= link_to "Members", members_path %li= link_to "Posts", posts_path + %li= link_to "Forums", forums_path - if can? :create, Post %li= link_to("Post something", new_post_path) - if can? :create, Planting diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index dc8cdf754..c7f1132a2 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -8,8 +8,12 @@ %p = "Member since: #{@member.created_at.to_s(:date)}" - %p - Location: Unknown + - if @member.location.to_s != '' + %p + Location: + = @member.location + %br/ + = image_tag("http://maps.google.com/maps/api/staticmap?size=200x200&maptype=roadmap&sensor=false&markers=color:green|label:A|#{@member.latitude},#{@member.longitude}&zoom=12", :alt => "Map showing #{@member.location}", :width => 200, :height => 200 ) - if @member.show_email %p Email: @@ -23,7 +27,8 @@ %li{:class => first_garden ? 'active' : '' } - first_garden = false = link_to g.name, "#garden#{g.id}", 'data-toggle' => 'tab' - %li= link_to 'New Garden', '#garden_new', 'data-toggle' => 'tab' + - if current_member == @member + %li= link_to 'New Garden', '#garden_new', 'data-toggle' => 'tab' .tab-content - first_garden = true - @member.gardens.each do |g| @@ -42,9 +47,10 @@ %p = link_to "More about this garden...", url_for(g) - %div{:class => 'tab-pane', :id => "garden_new"} - %h3 Create a new garden - = render 'gardens/form' + - if current_member == @member + %div{:class => 'tab-pane', :id => "garden_new"} + %h3 Create a new garden + = render 'gardens/form' %h3 Updates - @member.posts.each do |post| diff --git a/app/views/posts/_form.html.haml b/app/views/posts/_form.html.haml index 429ce48e0..d4fe1704a 100644 --- a/app/views/posts/_form.html.haml +++ b/app/views/posts/_form.html.haml @@ -6,14 +6,18 @@ - @post.errors.full_messages.each do |msg| %li= msg - .field - = f.label :author_id - = f.number_field :author_id - .field - = f.label :subject - = f.text_field :subject - .field - = f.label :body - = f.text_area :body - .actions - = f.submit 'Save' + + = label_tag :post, "Subject" + = f.text_field :subject, :class => 'input-block-level' + = label_tag :body, "What's going on in your food garden?" + = f.text_area :body, :rows => 12, :class => 'input-block-level' + + - if @post.forum || @forum + - forum = @post.forum || @forum + %p + This post will be posted in the forum + =link_to forum.name, forum + .field + = f.hidden_field :forum_id, :value => forum.id + + = f.submit "Post", :class => 'btn' diff --git a/app/views/posts/_single.html.haml b/app/views/posts/_single.html.haml index 2040f02be..f07856c04 100644 --- a/app/views/posts/_single.html.haml +++ b/app/views/posts/_single.html.haml @@ -8,10 +8,14 @@ %h3= link_to strip_tags(post.subject), post .post-meta - Posted by - = link_to post.author.login_name, member_path(post.author) - at - = post.created_at + %p + Posted by + = link_to post.author.login_name, member_path(post.author) + - if post.forum + in + = link_to post.forum, post.forum + at + = post.created_at .post-body :markdown diff --git a/app/views/posts/_summary.html.haml b/app/views/posts/_summary.html.haml new file mode 100644 index 000000000..c2f369a20 --- /dev/null +++ b/app/views/posts/_summary.html.haml @@ -0,0 +1,23 @@ +- howmany ||= 100 +- if posts.length > 0 + %table.table.table-striped + %tr + %th Subject + %th Posted by + %th Posted + %th Most recent activity + %th Comments + + - posts.recently_active[0..howmany-1].each do |post| + %tr + %td + = link_to strip_tags(post.subject), post + %td + =link_to post.author, post.author + %td + = post.created_at.to_s(:date) + %td + = distance_of_time_in_words(post.recent_activity, Time.zone.now) + ago + %td + = post.comments.count.to_s diff --git a/app/views/posts/edit.html.haml b/app/views/posts/edit.html.haml index 502a49faa..fcf66a4cb 100644 --- a/app/views/posts/edit.html.haml +++ b/app/views/posts/edit.html.haml @@ -1,7 +1,4 @@ = content_for :title, "Edit post" -= form_for @post do |f| - = label_tag :subject, "Subject" - = f.text_field :subject, :class => 'input-block-level' - = f.text_area :body, :rows => 12, :class => 'input-block-level' - = f.submit "Post" +=render 'form' + diff --git a/app/views/posts/new.html.haml b/app/views/posts/new.html.haml index 8a5ac9b46..e1014c41f 100644 --- a/app/views/posts/new.html.haml +++ b/app/views/posts/new.html.haml @@ -1,9 +1,4 @@ -= content_for :title, "Post a post" += content_for :title, @forum.name ? "Post in #{@forum.name}" : "Post something" + +=render 'form' -= form_for @post, :url => { :action => "create" } do |f| - %fieldset - = label_tag :post, "Subject" - = f.text_field :subject, :class => 'input-block-level' - = label_tag :body, "What's going on in your food garden?" - = f.text_area :body, :rows => 12, :class => 'input-block-level' - = f.submit "Post", :class => 'btn' diff --git a/app/views/roles/_form.html.haml b/app/views/roles/_form.html.haml new file mode 100644 index 000000000..954a2f6a0 --- /dev/null +++ b/app/views/roles/_form.html.haml @@ -0,0 +1,16 @@ += form_for @role do |f| + - if @role.errors.any? + #error_explanation + %h2= "#{pluralize(@role.errors.count, "error")} prohibited this role from being saved:" + %ul + - @role.errors.full_messages.each do |msg| + %li= msg + + .field + = f.label :name + = f.text_field :name + .field + = f.label :description + = f.text_area :description + .actions + = f.submit 'Save' diff --git a/app/views/roles/edit.html.haml b/app/views/roles/edit.html.haml new file mode 100644 index 000000000..427975d14 --- /dev/null +++ b/app/views/roles/edit.html.haml @@ -0,0 +1,7 @@ +%h1 Editing role + += render 'form' + += link_to 'Show', @role +\| += link_to 'Back', roles_path diff --git a/app/views/roles/index.html.haml b/app/views/roles/index.html.haml new file mode 100644 index 000000000..c503b1284 --- /dev/null +++ b/app/views/roles/index.html.haml @@ -0,0 +1,21 @@ +%h1 Listing roles + +%table + %tr + %th Name + %th Description + %th + %th + %th + + - @roles.each do |role| + %tr + %td= role.name + %td= role.description + %td= link_to 'Show', role + %td= link_to 'Edit', edit_role_path(role) + %td= link_to 'Destroy', role, method: :delete, data: { confirm: 'Are you sure?' } + +%br + += link_to 'New Role', new_role_path diff --git a/app/views/roles/new.html.haml b/app/views/roles/new.html.haml new file mode 100644 index 000000000..6e07588dd --- /dev/null +++ b/app/views/roles/new.html.haml @@ -0,0 +1,5 @@ +%h1 New role + += render 'form' + += link_to 'Back', roles_path diff --git a/app/views/roles/show.html.haml b/app/views/roles/show.html.haml new file mode 100644 index 000000000..7bd0488c7 --- /dev/null +++ b/app/views/roles/show.html.haml @@ -0,0 +1,12 @@ +%p#notice= notice + +%p + %b Name: + = @role.name +%p + %b Description: + = @role.description + += link_to 'Edit', edit_role_path(@role) +\| += link_to 'Back', roles_path diff --git a/config/application.rb b/config/application.rb index 5a9c0d3a8..04451a75d 100644 --- a/config/application.rb +++ b/config/application.rb @@ -28,7 +28,7 @@ module Growstuff # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. config.time_zone = 'UTC' - config.active_record.default_timezone = 'UTC' + config.active_record.default_timezone = :local # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] diff --git a/config/locales/en.bootstrap.yml b/config/locales/en.bootstrap.yml new file mode 100644 index 000000000..271b49c4d --- /dev/null +++ b/config/locales/en.bootstrap.yml @@ -0,0 +1,17 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + helpers: + actions: "Actions" + links: + back: "Back" + cancel: "Cancel" + confirm: "Are you sure?" + destroy: "Delete" + new: "New" + titles: + edit: "Edit" + save: "Save" + new: "New" + delete: "Delete" diff --git a/config/routes.rb b/config/routes.rb index c3c818b43..8e19bc41b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,5 @@ Growstuff::Application.routes.draw do + devise_for :members, :controllers => { :registrations => "registrations" } resources :plantings @@ -8,6 +9,8 @@ Growstuff::Application.routes.draw do resources :crops resources :members resources :comments + resources :roles + resources :forums get "home/index" diff --git a/db/migrate/20130212001748_add_geo_to_members.rb b/db/migrate/20130212001748_add_geo_to_members.rb new file mode 100644 index 000000000..f14173795 --- /dev/null +++ b/db/migrate/20130212001748_add_geo_to_members.rb @@ -0,0 +1,7 @@ +class AddGeoToMembers < ActiveRecord::Migration + def change + add_column :members, :location, :string + add_column :members, :latitude, :float + add_column :members, :longitude, :float + end +end diff --git a/db/migrate/20130213014511_create_forums.rb b/db/migrate/20130213014511_create_forums.rb new file mode 100644 index 000000000..6388876e6 --- /dev/null +++ b/db/migrate/20130213014511_create_forums.rb @@ -0,0 +1,11 @@ +class CreateForums < ActiveRecord::Migration + def change + create_table :forums do |t| + t.string :name, :null => false + t.text :description, :null => false + t.integer :owner_id, :null => false + + t.timestamps + end + end +end diff --git a/db/migrate/20130213015708_add_forum_to_posts.rb b/db/migrate/20130213015708_add_forum_to_posts.rb new file mode 100644 index 000000000..904183f90 --- /dev/null +++ b/db/migrate/20130213015708_add_forum_to_posts.rb @@ -0,0 +1,5 @@ +class AddForumToPosts < ActiveRecord::Migration + def change + add_column :posts, :forum_id, :integer + end +end diff --git a/db/migrate/20130214024117_create_roles.rb b/db/migrate/20130214024117_create_roles.rb new file mode 100644 index 000000000..8ccaf5ca6 --- /dev/null +++ b/db/migrate/20130214024117_create_roles.rb @@ -0,0 +1,10 @@ +class CreateRoles < ActiveRecord::Migration + def change + create_table :roles do |t| + t.string :name, :null => false + t.text :description + + t.timestamps + end + end +end diff --git a/db/migrate/20130214034838_add_members_roles_table.rb b/db/migrate/20130214034838_add_members_roles_table.rb new file mode 100644 index 000000000..a8044ebc8 --- /dev/null +++ b/db/migrate/20130214034838_add_members_roles_table.rb @@ -0,0 +1,8 @@ +class AddMembersRolesTable < ActiveRecord::Migration + def change + create_table :members_roles, :id => false do |t| + t.integer :member_id + t.integer :role_id + end + end +end diff --git a/db/migrate/20130220044605_add_slug_to_forums.rb b/db/migrate/20130220044605_add_slug_to_forums.rb new file mode 100644 index 000000000..6b7db208f --- /dev/null +++ b/db/migrate/20130220044605_add_slug_to_forums.rb @@ -0,0 +1,6 @@ +class AddSlugToForums < ActiveRecord::Migration + def change + add_column :forums, :slug, :string + add_index :forums, :slug, unique: true + end +end diff --git a/db/migrate/20130220044642_add_slug_to_roles.rb b/db/migrate/20130220044642_add_slug_to_roles.rb new file mode 100644 index 000000000..bc4578b10 --- /dev/null +++ b/db/migrate/20130220044642_add_slug_to_roles.rb @@ -0,0 +1,6 @@ +class AddSlugToRoles < ActiveRecord::Migration + def change + add_column :roles, :slug, :string + add_index :roles, :slug, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 2c8ae9f27..ac82e569f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,14 +11,14 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130208034248) do +ActiveRecord::Schema.define(:version => 20130220044642) do create_table "comments", :force => true do |t| - t.integer "post_id", :limit => 255, :null => false - t.integer "author_id", :limit => 255, :null => false - t.text "body", :limit => 255, :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.integer "post_id", :null => false + t.integer "author_id", :null => false + t.text "body", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false end create_table "crops", :force => true do |t| @@ -32,6 +32,17 @@ ActiveRecord::Schema.define(:version => 20130208034248) do add_index "crops", ["slug"], :name => "index_crops_on_slug", :unique => true add_index "crops", ["system_name"], :name => "index_crops_on_system_name" + create_table "forums", :force => true do |t| + t.string "name", :null => false + t.text "description", :null => false + t.integer "owner_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "slug" + end + + add_index "forums", ["slug"], :name => "index_forums_on_slug", :unique => true + create_table "gardens", :force => true do |t| t.string "name", :null => false t.integer "owner_id" @@ -68,6 +79,9 @@ ActiveRecord::Schema.define(:version => 20130208034248) do t.string "slug" t.boolean "tos_agreement" t.boolean "show_email" + t.string "location" + t.float "latitude" + t.float "longitude" end add_index "members", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true @@ -76,6 +90,11 @@ ActiveRecord::Schema.define(:version => 20130208034248) do add_index "members", ["slug"], :name => "index_users_on_slug", :unique => true add_index "members", ["unlock_token"], :name => "index_users_on_unlock_token", :unique => true + create_table "members_roles", :id => false, :force => true do |t| + t.integer "member_id" + t.integer "role_id" + end + create_table "plantings", :force => true do |t| t.integer "garden_id", :null => false t.integer "crop_id", :null => false @@ -96,11 +115,22 @@ ActiveRecord::Schema.define(:version => 20130208034248) do t.datetime "created_at", :null => false t.datetime "updated_at", :null => false t.string "slug" + t.integer "forum_id" end add_index "posts", ["created_at", "author_id"], :name => "index_updates_on_created_at_and_user_id" add_index "posts", ["slug"], :name => "index_updates_on_slug", :unique => true + create_table "roles", :force => true do |t| + t.string "name", :null => false + t.text "description" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "slug" + end + + add_index "roles", ["slug"], :name => "index_roles_on_slug", :unique => true + create_table "scientific_names", :force => true do |t| t.string "scientific_name", :null => false t.integer "crop_id", :null => false diff --git a/lib/tasks/growstuff.rake b/lib/tasks/growstuff.rake new file mode 100644 index 000000000..485889c87 --- /dev/null +++ b/lib/tasks/growstuff.rake @@ -0,0 +1,14 @@ +namespace :growstuff do + + desc "Add an admin user to Growstuff, by name" + # usage: rake growstuff:admin_user name=skud + + task :admin_user => :environment do + + member = Member.find(ENV['name']) or raise "Usage: rake growstuff:admin_user name=whoever" + admin = Role.find_or_create_by_name!('admin') + member.roles << admin + + end + +end diff --git a/spec/controllers/forums_controller_spec.rb b/spec/controllers/forums_controller_spec.rb new file mode 100644 index 000000000..743f71cb8 --- /dev/null +++ b/spec/controllers/forums_controller_spec.rb @@ -0,0 +1,146 @@ +require 'spec_helper' + +describe ForumsController do + + login_admin_member + + def valid_attributes + { + "name" => "MyString", + "description" => "Something", + "owner_id" => 1 + } + end + + def valid_session + {} + end + + describe "GET index" do + it "assigns all forums as @forums" do + forum = Forum.create! valid_attributes + get :index, {} + assigns(:forums).should eq([forum]) + end + end + + describe "GET show" do + it "assigns the requested forum as @forum" do + forum = Forum.create! valid_attributes + get :show, {:id => forum.to_param} + assigns(:forum).should eq(forum) + end + end + + describe "GET new" do + it "assigns a new forum as @forum" do + get :new, {} + assigns(:forum).should be_a_new(Forum) + end + end + + describe "GET edit" do + it "assigns the requested forum as @forum" do + forum = Forum.create! valid_attributes + get :edit, {:id => forum.to_param} + assigns(:forum).should eq(forum) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new Forum" do + expect { + post :create, {:forum => valid_attributes} + }.to change(Forum, :count).by(1) + end + + it "assigns a newly created forum as @forum" do + post :create, {:forum => valid_attributes} + assigns(:forum).should be_a(Forum) + assigns(:forum).should be_persisted + end + + it "redirects to the created forum" do + post :create, {:forum => valid_attributes} + response.should redirect_to(Forum.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved forum as @forum" do + # Trigger the behavior that occurs when invalid params are submitted + Forum.any_instance.stub(:save).and_return(false) + post :create, {:forum => { "name" => "invalid value" }} + assigns(:forum).should be_a_new(Forum) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + Forum.any_instance.stub(:save).and_return(false) + post :create, {:forum => { "name" => "invalid value" }} + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested forum" do + forum = Forum.create! valid_attributes + # Assuming there are no other forums in the database, this + # specifies that the Forum created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + Forum.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) + put :update, {:id => forum.to_param, :forum => { "name" => "MyString" }} + end + + it "assigns the requested forum as @forum" do + forum = Forum.create! valid_attributes + put :update, {:id => forum.to_param, :forum => valid_attributes} + assigns(:forum).should eq(forum) + end + + it "redirects to the forum" do + forum = Forum.create! valid_attributes + put :update, {:id => forum.to_param, :forum => valid_attributes} + response.should redirect_to(forum) + end + end + + describe "with invalid params" do + it "assigns the forum as @forum" do + forum = Forum.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Forum.any_instance.stub(:save).and_return(false) + put :update, {:id => forum.to_param, :forum => { "name" => "invalid value" }} + assigns(:forum).should eq(forum) + end + + it "re-renders the 'edit' template" do + forum = Forum.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Forum.any_instance.stub(:save).and_return(false) + put :update, {:id => forum.to_param, :forum => { "name" => "invalid value" }} + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested forum" do + forum = Forum.create! valid_attributes + expect { + delete :destroy, {:id => forum.to_param} + }.to change(Forum, :count).by(-1) + end + + it "redirects to the forums list" do + forum = Forum.create! valid_attributes + delete :destroy, {:id => forum.to_param} + response.should redirect_to(forums_url) + end + end + +end diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb index c8ad395c6..4eb13d9f6 100644 --- a/spec/controllers/posts_controller_spec.rb +++ b/spec/controllers/posts_controller_spec.rb @@ -39,6 +39,17 @@ describe PostsController do get :new, {} assigns(:post).should be_a_new(Post) end + + it "picks up forum from params" do + forum = FactoryGirl.create(:forum) + get :new, {:forum_id => forum.id} + assigns(:forum).should eq(forum) + end + + it "doesn't die if no forum specified" do + get :new, {} + assigns(:forum).should be_a_new(Forum) + end end describe "GET edit" do diff --git a/spec/controllers/roles_controller_spec.rb b/spec/controllers/roles_controller_spec.rb new file mode 100644 index 000000000..b2c2f6bc2 --- /dev/null +++ b/spec/controllers/roles_controller_spec.rb @@ -0,0 +1,143 @@ +require 'spec_helper' + +describe RolesController do + + def valid_attributes + { "name" => "MyString" } + end + + def valid_session + {} + end + + login_admin_member + + describe "GET index" do + it "assigns all roles as @roles" do + role = Role.create! valid_attributes + get :index, {} + # note that admin role exists because of login_admin_member + assigns(:roles).should eq([Role.find_by_name('admin'), role]) + end + end + + describe "GET show" do + it "assigns the requested role as @role" do + role = Role.create! valid_attributes + get :show, {:id => role.to_param} + assigns(:role).should eq(role) + end + end + + describe "GET new" do + it "assigns a new role as @role" do + get :new, {} + assigns(:role).should be_a_new(Role) + end + end + + describe "GET edit" do + it "assigns the requested role as @role" do + role = Role.create! valid_attributes + get :edit, {:id => role.to_param} + assigns(:role).should eq(role) + end + end + + describe "POST create" do + describe "with valid params" do + it "creates a new Role" do + expect { + post :create, {:role => valid_attributes} + }.to change(Role, :count).by(1) + end + + it "assigns a newly created role as @role" do + post :create, {:role => valid_attributes} + assigns(:role).should be_a(Role) + assigns(:role).should be_persisted + end + + it "redirects to the created role" do + post :create, {:role => valid_attributes} + response.should redirect_to(Role.last) + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved role as @role" do + # Trigger the behavior that occurs when invalid params are submitted + Role.any_instance.stub(:save).and_return(false) + post :create, {:role => { "name" => "invalid value" }} + assigns(:role).should be_a_new(Role) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + Role.any_instance.stub(:save).and_return(false) + post :create, {:role => { "name" => "invalid value" }} + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested role" do + role = Role.create! valid_attributes + # Assuming there are no other roles in the database, this + # specifies that the Role created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + Role.any_instance.should_receive(:update_attributes).with({ "name" => "MyString" }) + put :update, {:id => role.to_param, :role => { "name" => "MyString" }} + end + + it "assigns the requested role as @role" do + role = Role.create! valid_attributes + put :update, {:id => role.to_param, :role => valid_attributes} + assigns(:role).should eq(role) + end + + it "redirects to the role" do + role = Role.create! valid_attributes + put :update, {:id => role.to_param, :role => valid_attributes} + response.should redirect_to(role) + end + end + + describe "with invalid params" do + it "assigns the role as @role" do + role = Role.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Role.any_instance.stub(:save).and_return(false) + put :update, {:id => role.to_param, :role => { "name" => "invalid value" }} + assigns(:role).should eq(role) + end + + it "re-renders the 'edit' template" do + role = Role.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Role.any_instance.stub(:save).and_return(false) + put :update, {:id => role.to_param, :role => { "name" => "invalid value" }} + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested role" do + role = Role.create! valid_attributes + expect { + delete :destroy, {:id => role.to_param} + }.to change(Role, :count).by(-1) + end + + it "redirects to the roles list" do + role = Role.create! valid_attributes + delete :destroy, {:id => role.to_param} + response.should redirect_to(roles_url) + end + end + +end diff --git a/spec/factories/forums.rb b/spec/factories/forums.rb new file mode 100644 index 000000000..0d8cff596 --- /dev/null +++ b/spec/factories/forums.rb @@ -0,0 +1,9 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :forum do + name "Permaculture" + description "*Everything* about permaculture!" + owner + end +end diff --git a/spec/factories/member.rb b/spec/factories/member.rb index 7c7c08ed0..05cc1ceff 100644 --- a/spec/factories/member.rb +++ b/spec/factories/member.rb @@ -28,6 +28,18 @@ FactoryGirl.define do show_email true end + factory :geolocated_member do + login_name 'JohnH' # for the astronomer who figured out longitude + location 'Greenwich, UK' + # including lat/long explicitly because geocoder doesn't work with FG + latitude 51.483 + longitude 0.004 + end + + factory :admin_member do + roles { [ FactoryGirl.create(:admin) ] } + end + end end diff --git a/spec/factories/post.rb b/spec/factories/post.rb index 0f056691e..d9e6e0be5 100644 --- a/spec/factories/post.rb +++ b/spec/factories/post.rb @@ -15,6 +15,10 @@ FactoryGirl.define do factory :html_post do body 'EVIL' end + + factory :forum_post do + forum + end end end diff --git a/spec/factories/roles.rb b/spec/factories/roles.rb new file mode 100644 index 000000000..b94a3f1f0 --- /dev/null +++ b/spec/factories/roles.rb @@ -0,0 +1,13 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :role do + name "Moderator" + description "These people moderate the forums" + + factory :admin do + name "admin" + end + end + +end diff --git a/spec/models/forum_spec.rb b/spec/models/forum_spec.rb new file mode 100644 index 000000000..6cfe3ea65 --- /dev/null +++ b/spec/models/forum_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe Forum do + before(:each) do + @forum = FactoryGirl.create(:forum) + end + + it "belongs to an owner" do + @forum.owner.should be_an_instance_of Member + end + + it "stringifies nicely" do + "#{@forum}".should eq @forum.name + end + + it 'has a slug' do + @forum.slug.should eq 'permaculture' + end + + it "has many posts" do + @post1 = FactoryGirl.create(:forum_post, :forum => @forum) + @post2 = FactoryGirl.create(:forum_post, :forum => @forum) + @forum.posts.length.should == 2 + end + + it "orders posts in reverse chron order" do + @post1 = FactoryGirl.create(:forum_post, :forum => @forum) + @post2 = FactoryGirl.create(:forum_post, :forum => @forum) + @forum.posts.first.should eq @post2 + end + +end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 0a4570ac2..f86b3f3b4 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -56,6 +56,29 @@ describe 'member' do @comment2 = FactoryGirl.create(:comment, :author => @member) @member.comments.length.should == 2 end + + it "has many forums" do + @member.save + @forum1 = FactoryGirl.create(:forum, :owner => @member) + @forum2 = FactoryGirl.create(:forum, :owner => @member) + @member.forums.length.should == 2 + end + + it 'has location and lat/long fields' do + @member.update_attributes(:location => 'Greenwich, UK') + @member.location.should eq 'Greenwich, UK' + @member.latitude.round(2).should eq 51.48 + @member.longitude.round(2).should eq 0.00 + end + + it 'empties the lat/long if location removed' do + @member.update_attributes(:location => 'Greenwich, UK') + @member.update_attributes(:location => '') + @member.location.should eq '' + @member.latitude.should be_nil + @member.longitude.should be_nil + end + end context 'no TOS agreement' do @@ -77,4 +100,22 @@ describe 'member' do end end + context 'roles' do + before(:each) do + @member = FactoryGirl.create(:member) + @role = FactoryGirl.create(:role) + @member.roles << @role + end + + it 'has a role' do + @member.roles.first.should eq @role + @member.has_role?(:moderator).should eq true + end + + it 'sets up roles in factories' do + @admin = FactoryGirl.create(:admin_member) + @admin.has_role?(:admin).should eq true + end + end + end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb index 784bad0d3..450a31893 100644 --- a/spec/models/post_spec.rb +++ b/spec/models/post_spec.rb @@ -37,4 +37,35 @@ describe Post do @post.destroy Comment.count.should == all - 2 end + + it "belongs to a forum" do + @post = FactoryGirl.create(:forum_post) + @post.forum.should be_an_instance_of Forum + end + + context "recent activity" do + before(:each) do + Time.stub(:now => Time.now) + @post = FactoryGirl.create(:post) + end + + it "sets recent activity to post time" do + @post.recent_activity.to_i.should eq @post.created_at.to_i + end + + it "sets recent activity to comment time" do + @comment = FactoryGirl.create(:comment, :post => @post) + @post.recent_activity.to_i.should eq @comment.created_at.to_i + end + + it "sorts recently active" do + # create a shiny new post + @post2 = FactoryGirl.create(:post) + Post.recently_active.first.should eq @post2 + # now comment on an older post + @comment = FactoryGirl.create(:comment, :post => @post) + Post.recently_active.first.should eq @post + end + end + end diff --git a/spec/models/role_spec.rb b/spec/models/role_spec.rb new file mode 100644 index 000000000..8250e064d --- /dev/null +++ b/spec/models/role_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Role do + before(:each) do + @member = FactoryGirl.create(:member) + @role = FactoryGirl.create(:role) + @role.members << @member + end + + it 'has members' do + @role.members.first.should eq @member + end + + it 'has a slug' do + @role.slug.should eq 'moderator' + end +end diff --git a/spec/requests/forums_spec.rb b/spec/requests/forums_spec.rb new file mode 100644 index 000000000..ee85dacfd --- /dev/null +++ b/spec/requests/forums_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe "Forums" do + describe "GET /forums" do + it "works! (now write some real specs)" do + # Run the generator again with the --webrat flag if you want to use webrat methods/matchers + get forums_path + response.status.should be(200) + end + end +end diff --git a/spec/routing/forums_routing_spec.rb b/spec/routing/forums_routing_spec.rb new file mode 100644 index 000000000..c783d5199 --- /dev/null +++ b/spec/routing/forums_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe ForumsController do + describe "routing" do + + it "routes to #index" do + get("/forums").should route_to("forums#index") + end + + it "routes to #new" do + get("/forums/new").should route_to("forums#new") + end + + it "routes to #show" do + get("/forums/1").should route_to("forums#show", :id => "1") + end + + it "routes to #edit" do + get("/forums/1/edit").should route_to("forums#edit", :id => "1") + end + + it "routes to #create" do + post("/forums").should route_to("forums#create") + end + + it "routes to #update" do + put("/forums/1").should route_to("forums#update", :id => "1") + end + + it "routes to #destroy" do + delete("/forums/1").should route_to("forums#destroy", :id => "1") + end + + end +end diff --git a/spec/routing/roles_routing_spec.rb b/spec/routing/roles_routing_spec.rb new file mode 100644 index 000000000..4084a6e7e --- /dev/null +++ b/spec/routing/roles_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe RolesController do + describe "routing" do + + it "routes to #index" do + get("/roles").should route_to("roles#index") + end + + it "routes to #new" do + get("/roles/new").should route_to("roles#new") + end + + it "routes to #show" do + get("/roles/1").should route_to("roles#show", :id => "1") + end + + it "routes to #edit" do + get("/roles/1/edit").should route_to("roles#edit", :id => "1") + end + + it "routes to #create" do + post("/roles").should route_to("roles#create") + end + + it "routes to #update" do + put("/roles/1").should route_to("roles#update", :id => "1") + end + + it "routes to #destroy" do + delete("/roles/1").should route_to("roles#destroy", :id => "1") + end + + end +end diff --git a/spec/support/controller_macros.rb b/spec/support/controller_macros.rb index fd7b1ae29..fef814405 100644 --- a/spec/support/controller_macros.rb +++ b/spec/support/controller_macros.rb @@ -7,4 +7,12 @@ module ControllerMacros sign_in member end end + + def login_admin_member + before(:each) do + @request.env["devise.mapping"] = Devise.mappings[:member] + member = FactoryGirl.create(:admin_member) + sign_in member + end + end end diff --git a/spec/views/devise/registrations/edit_spec.rb b/spec/views/devise/registrations/edit_spec.rb index 8eedcc41a..d04bc44df 100644 --- a/spec/views/devise/registrations/edit_spec.rb +++ b/spec/views/devise/registrations/edit_spec.rb @@ -18,18 +18,28 @@ describe 'devise/registrations/edit.html.haml', :type => "view" do rendered.should contain 'Email' end - context 'email section' + context 'email section' do it 'has a heading' do assert_select "h2", "Email address" end + end + context 'profile section' do + it 'has a heading' do + assert_select "h2", "Profile details" + end it 'shows show_email checkbox' do assert_select "input[id=member_show_email][type=checkbox]" end + it 'shows location field' do + assert_select "input[id=member_location][type=text]" + end + end it 'should have a password section' do assert_select "h2", "Change password" end end + end diff --git a/spec/views/forums/edit.html.haml_spec.rb b/spec/views/forums/edit.html.haml_spec.rb new file mode 100644 index 000000000..306f3bbf0 --- /dev/null +++ b/spec/views/forums/edit.html.haml_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe "forums/edit" do + before(:each) do + @forum = assign(:forum, stub_model(Forum, + :name => "MyString", + :description => "MyText", + :owner_id => 1 + )) + end + + it "renders the edit forum form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => forums_path(@forum), :method => "post" do + assert_select "input#forum_name", :name => "forum[name]" + assert_select "textarea#forum_description", :name => "forum[description]" + assert_select "select#forum_owner_id", :name => "forum[owner_id]" + end + end +end diff --git a/spec/views/forums/index.html.haml_spec.rb b/spec/views/forums/index.html.haml_spec.rb new file mode 100644 index 000000000..4816d5fcc --- /dev/null +++ b/spec/views/forums/index.html.haml_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe "forums/index" do + before(:each) do + @forum1 = FactoryGirl.create(:forum) + @forum2 = FactoryGirl.create(:forum) + assign(:forums, [ @forum1, @forum2 ]) + end + + it "renders a list of forums" do + render + assert_select "h2", :text => @forum1.name, :count => 2 + end + + it "doesn't display posts for empty forums" do + render + assert_select "table", false + end + + context "posts" do + before(:each) do + @post = FactoryGirl.create(:forum_post, :forum => @forum1) + @comment = FactoryGirl.create(:comment, :post => @post) + render + end + + it "displays posts" do + assert_select "table" + rendered.should contain @post.subject + rendered.should contain @post.created_at.to_s(:date) + rendered.should contain "less than a minute ago" + end + + it "displays comment count" do + assert_select "td", :text => "1" + end + + end +end diff --git a/spec/views/forums/new.html.haml_spec.rb b/spec/views/forums/new.html.haml_spec.rb new file mode 100644 index 000000000..bfda7bfa1 --- /dev/null +++ b/spec/views/forums/new.html.haml_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe "forums/new" do + before(:each) do + @forum = assign(:forum, FactoryGirl.create(:forum)) + render + end + + it "renders new forum form" do + assert_select "form", :action => forums_path, :method => "post" do + assert_select "input#forum_name", :name => "forum[name]" + assert_select "textarea#forum_description", :name => "forum[description]" + assert_select "select#forum_owner_id", :name => "forum[owner_id]" + end + end +end diff --git a/spec/views/forums/show.html.haml_spec.rb b/spec/views/forums/show.html.haml_spec.rb new file mode 100644 index 000000000..735f5d482 --- /dev/null +++ b/spec/views/forums/show.html.haml_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe "forums/show" do + before(:each) do + controller.stub(:current_user) { nil } + @forum = assign(:forum, FactoryGirl.create(:forum)) + end + + it "renders attributes" do + render + rendered.should contain "Everything about permaculture" + rendered.should contain @forum.owner.to_s + end + + it "parses markdown description into html" do + render + assert_select "em", "Everything" + end + + it 'links to new post with the forum id' do + render + assert_select "a[href=#{new_post_path(:forum_id => @forum.id)}]" + end + + it 'has no posts' do + render + rendered.should contain "No posts yet." + end + + it 'shows posts' do + @post = FactoryGirl.create(:post, :forum => @forum) + render + assert_select "table" + rendered.should contain @post.subject + rendered.should contain @post.author.to_s + end +end diff --git a/spec/views/members/show.html.haml_spec.rb b/spec/views/members/show.html.haml_spec.rb index 98718d6fd..6bb57b6ef 100644 --- a/spec/views/members/show.html.haml_spec.rb +++ b/spec/views/members/show.html.haml_spec.rb @@ -1,53 +1,71 @@ require 'spec_helper' describe "members/show" do - before(:each) do controller.stub(:current_user) { nil } @member = FactoryGirl.create(:member) - @time = @member.created_at - - @garden = FactoryGirl.create(:garden, :owner => @member) - @planting = FactoryGirl.create(:planting, :garden => @garden) - render + @garden = FactoryGirl.create(:garden, :owner => @member) end - it "shows account creation date" do - rendered.should contain "Member since" - rendered.should contain @time.strftime("%B %d, %Y") + context "the basics" do + before(:each) do + render + end + + it "shows account creation date" do + @time = @member.created_at + rendered.should contain "Member since" + rendered.should contain @time.strftime("%B %d, %Y") + end + + it "contains a gravatar icon" do + assert_select "img", :src => /gravatar\.com\/avatar/ + end end - it "contains a gravatar icon" do - assert_select "img", :src => /gravatar\.com\/avatar/ - end + context "gardens and plantings" do + before(:each) do + @planting = FactoryGirl.create(:planting, :garden => @garden) + render + end - it "shows the auto-created garden" do - assert_select "li.active>a", :text => "Garden" - end + it "shows the auto-created garden" do + assert_select "li.active>a", :text => "Garden" + end - it 'shows the garden description' do - rendered.should contain "totally cool garden" - end + it 'shows the garden description' do + rendered.should contain "totally cool garden" + end - it 'renders markdown in the garden description' do - assert_select "strong", "totally" - end + it 'renders markdown in the garden description' do + assert_select "strong", "totally" + end - it "shows the plantings in the garden" do - rendered.should contain @planting.crop.system_name - end + it "shows the plantings in the garden" do + rendered.should contain @planting.crop.system_name + end - it "doesn't show the note about random plantings" do - rendered.should_not contain "Note: these are a random selection" - end + it "doesn't show the note about random plantings" do + rendered.should_not contain "Note: these are a random selection" + end - it "doesn't show the email address" do - rendered.should_not contain @member.email + it "doesn't show the email address" do + rendered.should_not contain @member.email + end + + it "does not contain a 'New Garden' link" do + assert_select "a[href=#garden_new]", false + end + + it "does not contain a 'New Garden' tab" do + assert_select "#garden_new", false + end end context "signed in member" do before(:each) do sign_in @member + controller.stub(:current_user) { @member } render end @@ -56,6 +74,23 @@ describe "members/show" do end end + context "signed in as different member" do + before(:each) do + @member2 = FactoryGirl.create(:member) + sign_in @member2 + controller.stub(:current_user) { @member2 } + render + end + + it "does not contain a 'New Garden' link" do + assert_select "a[href=#garden_new]", false + end + + it "does not contain a 'New Garden' tab" do + assert_select "#garden_new", false + end + end + context "public member" do before(:each) do @member = FactoryGirl.create(:public_member) @@ -67,4 +102,33 @@ describe "members/show" do end end + context "geolocations" do + before(:each) do + @member = FactoryGirl.create(:geolocated_member) + render + end + it "shows the location" do + rendered.should contain @member.location + end + it "shows a map" do + assert_select "img", :src => /maps\.google\.com/ + end + end + + context "no location stated" do + before(:each) do + @member = FactoryGirl.create(:member) + render + end + it "doesn't have a location" do + @member.location.to_s.should eq '' + end + it "doesn't show the location" do + rendered.should_not contain "Location:" + end + it "doesn't show a map" do + assert_select "img[src*=maps]", false + end + end + end diff --git a/spec/views/posts/edit.html.haml_spec.rb b/spec/views/posts/edit.html.haml_spec.rb index bb67e9dfa..91e2d9b4f 100644 --- a/spec/views/posts/edit.html.haml_spec.rb +++ b/spec/views/posts/edit.html.haml_spec.rb @@ -19,5 +19,33 @@ describe "posts/edit" do assert_select "textarea#post_body", :name => "post[body]" end end + + it 'no hidden forum field' do + assert_select "input#post_forum_id[type=hidden]", false + end + + it 'no forum mentioned' do + rendered.should_not contain "This post will be posted in the forum" + end + + context "forum specified" do + before(:each) do + @forum = assign(:forum, FactoryGirl.create(:forum)) + assign(:post, FactoryGirl.create( :post, + :forum => @forum, + :author => @author + )) + render + end + + it 'creates a hidden field' do + assert_select "input#post_forum_id[type=hidden][value=#{@forum.id}]" + end + + it 'tells the user what forum it will be posted in' do + rendered.should contain "This post will be posted in the forum #{@forum.name}" + end + end + end end diff --git a/spec/views/posts/new.html.haml_spec.rb b/spec/views/posts/new.html.haml_spec.rb index 99e644ed4..aa7da6fba 100644 --- a/spec/views/posts/new.html.haml_spec.rb +++ b/spec/views/posts/new.html.haml_spec.rb @@ -4,15 +4,41 @@ describe "posts/new" do before(:each) do @author = FactoryGirl.create(:member) assign(:post, FactoryGirl.create(:post, :author => @author)) + assign(:forum, Forum.new) sign_in @author controller.stub(:current_user) { @author } - render end it "renders new post form" do + render assert_select "form", :action => posts_path, :method => "post" do assert_select "input#post_subject", :name => "post[subject]" assert_select "textarea#post_body", :name => "post[body]" end end + + it 'no hidden forum field' do + assert_select "input#post_forum_id[type=hidden]", false + end + + it 'no forum mentioned' do + rendered.should_not contain "This post will be posted in the forum" + end + + context "forum specified" do + before(:each) do + @forum = assign(:forum, FactoryGirl.create(:forum)) + assign(:post, FactoryGirl.create(:post, :forum => @forum)) + render + end + + it 'creates a hidden field' do + assert_select "input#post_forum_id[type=hidden][value=#{@forum.id}]" + end + + it 'tells the user what forum it will be posted in' do + rendered.should contain "This post will be posted in the forum #{@forum.name}" + end + end + end diff --git a/spec/views/posts/show.html.haml_spec.rb b/spec/views/posts/show.html.haml_spec.rb index 703d50347..4785412d5 100644 --- a/spec/views/posts/show.html.haml_spec.rb +++ b/spec/views/posts/show.html.haml_spec.rb @@ -42,6 +42,15 @@ describe "posts/show" do rendered.should contain @comment.body end + context "forum post" do + it "shows forum name" do + @post = assign(:post, + FactoryGirl.create(:forum_post, :author => @author)) + render + rendered.should contain "in #{@post.forum.name}" + end + end + context "signed in" do before(:each) do sign_in @author diff --git a/spec/views/roles/edit.html.haml_spec.rb b/spec/views/roles/edit.html.haml_spec.rb new file mode 100644 index 000000000..2e2f66c7b --- /dev/null +++ b/spec/views/roles/edit.html.haml_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe "roles/edit" do + before(:each) do + @role = assign(:role, stub_model(Role, + :name => "MyString", + :description => "MyText" + )) + end + + it "renders the edit role form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => roles_path(@role), :method => "post" do + assert_select "input#role_name", :name => "role[name]" + assert_select "textarea#role_description", :name => "role[description]" + end + end +end diff --git a/spec/views/roles/index.html.haml_spec.rb b/spec/views/roles/index.html.haml_spec.rb new file mode 100644 index 000000000..508f29017 --- /dev/null +++ b/spec/views/roles/index.html.haml_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe "roles/index" do + before(:each) do + assign(:roles, [ + stub_model(Role, + :name => "Name", + :description => "MyText" + ), + stub_model(Role, + :name => "Name", + :description => "MyText" + ) + ]) + end + + it "renders a list of roles" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "tr>td", :text => "Name".to_s, :count => 2 + assert_select "tr>td", :text => "MyText".to_s, :count => 2 + end +end diff --git a/spec/views/roles/new.html.haml_spec.rb b/spec/views/roles/new.html.haml_spec.rb new file mode 100644 index 000000000..e15e14226 --- /dev/null +++ b/spec/views/roles/new.html.haml_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe "roles/new" do + before(:each) do + assign(:role, stub_model(Role, + :name => "MyString", + :description => "MyText" + ).as_new_record) + end + + it "renders new role form" do + render + + # Run the generator again with the --webrat flag if you want to use webrat matchers + assert_select "form", :action => roles_path, :method => "post" do + assert_select "input#role_name", :name => "role[name]" + assert_select "textarea#role_description", :name => "role[description]" + end + end +end diff --git a/spec/views/roles/show.html.haml_spec.rb b/spec/views/roles/show.html.haml_spec.rb new file mode 100644 index 000000000..389a5c8c3 --- /dev/null +++ b/spec/views/roles/show.html.haml_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe "roles/show" do + before(:each) do + @role = assign(:role, stub_model(Role, + :name => "Name", + :description => "MyText" + )) + end + + it "renders attributes in

" do + render + # Run the generator again with the --webrat flag if you want to use webrat matchers + rendered.should match(/Name/) + rendered.should match(/MyText/) + end +end