diff --git a/.gitignore b/.gitignore index c6a731582..7801d8f9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ /log/* -/doc/* +/doc /db/*.sqlite3 /tmp/* .pt diff --git a/Gemfile b/Gemfile index 0a2115ddc..6c8ced112 100644 --- a/Gemfile +++ b/Gemfile @@ -45,6 +45,7 @@ group :assets do end gem 'jquery-rails' +gem 'flickraw' # To use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 9bc276930..f45975c9b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -96,6 +96,7 @@ GEM factory_girl (~> 4.2.0) railties (>= 3.0.0) fastthread (1.0.7) + flickraw (0.9.6) friendly_id (4.0.9) fssm (0.2.10) geocoder (1.1.6) @@ -259,6 +260,7 @@ DEPENDENCIES devise diff-lcs factory_girl_rails (~> 4.0) + flickraw friendly_id geocoder gravatar-ultimate diff --git a/app/assets/javascripts/photos.js.coffee b/app/assets/javascripts/photos.js.coffee new file mode 100644 index 000000000..761567942 --- /dev/null +++ b/app/assets/javascripts/photos.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/controllers/authentications_controller.rb b/app/controllers/authentications_controller.rb index 96456d996..b4e376dca 100644 --- a/app/controllers/authentications_controller.rb +++ b/app/controllers/authentications_controller.rb @@ -37,7 +37,7 @@ class AuthenticationsController < ApplicationController else flash[:notice] = "Authentication failed." end - redirect_to edit_member_registration_path + redirect_to request.env['omniauth.origin'] || edit_member_registration_path end # DELETE /authentications/1 diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index d05c85f01..846d28f0a 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -12,8 +12,8 @@ class MembersController < ApplicationController def show @member = Member.confirmed.find(params[:id]) - @twitter_auth = @member.authentications.find_by_provider('twitter') - @flickr_auth = @member.authentications.find_by_provider('flickr') + @twitter_auth = @member.auth('twitter') + @flickr_auth = @member.auth('flickr') @posts = @member.posts # The garden form partial is called from the "New Garden" tab; # it requires a garden to be passed in @garden. diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb new file mode 100644 index 000000000..f9d6bf4c5 --- /dev/null +++ b/app/controllers/photos_controller.rb @@ -0,0 +1,106 @@ +class PhotosController < ApplicationController + load_and_authorize_resource + # GET /photos + # GET /photos.json + def index + @photos = Photo.paginate(:page => params[:page]) + + respond_to do |format| + format.html # index.html.erb + format.json { render json: @photos } + end + end + + # GET /photos/1 + # GET /photos/1.json + def show + @photo = Photo.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.json { render json: @photo } + end + end + + # GET /photos/new + # GET /photos/new.json + def new + @photo = Photo.new + @planting_id = params[:planting_id] + + @flickr_auth = current_member.auth('flickr') + if @flickr_auth + @photos = current_member.flickr_photos + end + + respond_to do |format| + format.html # new.html.erb + format.json { render json: @photo } + end + end + + # GET /photos/1/edit + def edit + @photo = Photo.find(params[:id]) + end + + # POST /photos + # POST /photos.json + def create + @photo = Photo.find_by_flickr_photo_id(params[:photo][:flickr_photo_id]) || + Photo.new(params[:photo]) + @photo.owner_id = current_member.id + @photo.set_flickr_metadata + + if params[:planting_id] + planting = Planting.find_by_id(params[:planting_id]) + if planting + if planting.owner.id == current_member.id + @photo.plantings << planting unless @photo.plantings.include?(planting) + else + flash[:alert] = "You must own both the planting and the photo." + end + else + flash[:alert] = "Couldn't find planting to connect to photo." + end + end + + respond_to do |format| + if @photo.save + format.html { redirect_to @photo, notice: 'Photo was successfully added.' } + format.json { render json: @photo, status: :created, location: @photo } + else + format.html { render action: "new" } + format.json { render json: @photo.errors, status: :unprocessable_entity } + end + end + end + + # PUT /photos/1 + # PUT /photos/1.json + def update + @photo = Photo.find(params[:id]) + + respond_to do |format| + if @photo.update_attributes(params[:photo]) + format.html { redirect_to @photo, notice: 'Photo was successfully updated.' } + format.json { head :no_content } + else + format.html { render action: "edit" } + format.json { render json: @photo.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /photos/1 + # DELETE /photos/1.json + def destroy + @photo = Photo.find(params[:id]) + @photo.destroy + + respond_to do |format| + format.html { redirect_to photos_url } + format.json { head :no_content } + end + end +end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index e23bebf0c..2be1c9c33 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -2,8 +2,8 @@ class RegistrationsController < Devise::RegistrationsController def edit - @twitter_auth = current_member.authentications.find_by_provider('twitter') - @flickr_auth = current_member.authentications.find_by_provider('flickr') + @twitter_auth = current_member.auth('twitter') + @flickr_auth = current_member.auth('flickr') render "edit" end diff --git a/app/models/ability.rb b/app/models/ability.rb index 4be2e10a5..21a1fbf0e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -63,6 +63,10 @@ class Ability can :update, Planting, :garden => { :owner_id => member.id } can :destroy, Planting, :garden => { :owner_id => member.id } + can :create, Photo + can :update, Photo, :owner_id => member.id + can :destroy, Photo, :owner_id => member.id + # orders/shop/etc can :create, Order can :read, Order, :member_id => member.id diff --git a/app/models/member.rb b/app/models/member.rb index aa3387a43..efbd19eeb 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -4,17 +4,24 @@ class Member < ActiveRecord::Base has_many :posts, :foreign_key => 'author_id' has_many :comments, :foreign_key => 'author_id' + has_many :forums, :foreign_key => 'owner_id' + has_many :gardens, :foreign_key => 'owner_id' has_many :plantings, :through => :gardens - has_many :forums, :foreign_key => 'owner_id' + has_and_belongs_to_many :roles + has_many :notifications, :foreign_key => 'recipient_id' has_many :sent_notifications, :foreign_key => 'sender_id' + has_many :authentications + has_many :orders has_one :account has_one :account_type, :through => :account + has_many :photos + default_scope order("lower(login_name) asc") scope :confirmed, where('confirmed_at IS NOT NULL') scope :located, where('location IS NOT NULL') @@ -127,6 +134,28 @@ class Member < ActiveRecord::Base end end + def auth(provider) + return authentications.find_by_provider(provider) + end + + def flickr + if @flickr.nil? + flickr_auth = auth('flickr') + if flickr_auth + FlickRaw.api_key = ENV['FLICKR_KEY'] + FlickRaw.shared_secret = ENV['FLICKR_SECRET'] + @flickr = FlickRaw::Flickr.new + @flickr.access_token = flickr_auth.token + @flickr.access_secret = flickr_auth.secret + end + end + return @flickr + end + + def flickr_photos + return flickr.people.getPhotos(:user_id => 'me', :per_page => 30) + end + protected def empty_unwanted_geocodes if self.location.to_s == '' diff --git a/app/models/photo.rb b/app/models/photo.rb new file mode 100644 index 000000000..ac67273ab --- /dev/null +++ b/app/models/photo.rb @@ -0,0 +1,31 @@ +class Photo < ActiveRecord::Base + attr_accessible :flickr_photo_id, :owner_id, :title, :license_name, + :license_url, :thumbnail_url, :fullsize_url, :link_url + belongs_to :owner, :class_name => 'Member' + has_and_belongs_to_many :plantings + + default_scope order("created_at desc") + + # This is split into a side-effect free method and a side-effecting method + # for easier stubbing and testing. + def flickr_metadata + flickr = owner.flickr + info = flickr.photos.getInfo(:photo_id => flickr_photo_id) + licenses = flickr.photos.licenses.getInfo() + license = licenses.find { |l| l.id == info.license } + return { + :title => info.title || "Untitled", + :license_name => license.name, + :license_url => license.url, + :thumbnail_url => FlickRaw.url_q(info), + :fullsize_url => FlickRaw.url_z(info), + :link_url => FlickRaw.url_photopage(info) + } + + end + + def set_flickr_metadata + self.update_attributes(flickr_metadata) + end + +end diff --git a/app/models/planting.rb b/app/models/planting.rb index d5c477957..1535c798b 100644 --- a/app/models/planting.rb +++ b/app/models/planting.rb @@ -7,6 +7,7 @@ class Planting < ActiveRecord::Base belongs_to :garden belongs_to :crop + has_and_belongs_to_many :photos default_scope order("created_at desc") @@ -16,6 +17,7 @@ class Planting < ActiveRecord::Base :plantings_count, :to => :crop, :prefix => true + delegate :owner, :to => :garden default_scope order("created_at desc") @@ -33,10 +35,6 @@ class Planting < ActiveRecord::Base return "#{garden.owner.login_name}'s #{garden}" end - def owner - return garden.owner - end - def planted_at_string if planted_at planted_at.strftime("%F") @@ -48,4 +46,8 @@ class Planting < ActiveRecord::Base def planted_at_string=(str) self.planted_at = str == '' ? nil : Time.parse(str) end + + def to_s + self.crop_system_name + " in " + self.location + end end diff --git a/app/views/photos/edit.html.haml b/app/views/photos/edit.html.haml new file mode 100644 index 000000000..5a43dd437 --- /dev/null +++ b/app/views/photos/edit.html.haml @@ -0,0 +1,2 @@ +- content_for :title, "Edit Photo" + diff --git a/app/views/photos/index.html.haml b/app/views/photos/index.html.haml new file mode 100644 index 000000000..09a8e1bd7 --- /dev/null +++ b/app/views/photos/index.html.haml @@ -0,0 +1,26 @@ +- content_for :title, "Photos" + +%div.pagination + = page_entries_info @photos, :model => "photos" + = will_paginate @photos + +- c = 0 +%ul.thumbnails + .row + - @photos.each do |p| + - c += 1 + %li.span2 + .thumbnail(style='height: 220px') + = link_to image_tag(p.thumbnail_url, :alt => p.title, :class => 'img-rounded'), p + %p + = link_to p.title, p + by + = link_to p.owner, p.owner + - if (c % 6) == 0 + .row + +%div.pagination + = page_entries_info @photos, :model => "photos" + = will_paginate @photos + + diff --git a/app/views/photos/new.html.haml b/app/views/photos/new.html.haml new file mode 100644 index 000000000..ca363dc26 --- /dev/null +++ b/app/views/photos/new.html.haml @@ -0,0 +1,30 @@ +- content_for :title, "New Photo" + +- if @flickr_auth + %p + Connected to Flickr as + = succeed "." do + = link_to @flickr_auth.name, "http://flickr.com/photos/#{@flickr_auth.uid}" + + %p Select a photo from your recent uploads: + + - c = 0 + %ul.thumbnails + .row + - @photos.each do |p| + - c += 1 + %li.span2 + .thumbnail(style='height: 220px') + = link_to image_tag(FlickRaw.url_q(p), :alt => '', :class => 'img-rounded'), photos_path(:photo => { :flickr_photo_id => p.id }, :planting_id => @planting_id), :method => :post + %p + =p.title + - if (c % 6) == 0 + .row + + +- else + .alert + You must + =link_to "connect your account to Flickr", '/auth/flickr' + to add photos. + diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml new file mode 100644 index 000000000..47a2b7ecc --- /dev/null +++ b/app/views/photos/show.html.haml @@ -0,0 +1,26 @@ +-content_for :title, @photo.title + +.row + .span6 + %p + %strong Posted by: + = link_to @photo.owner, @photo.owner + %p + %strong License: + - if @photo.license_url + = link_to @photo.license_name, @photo.license_url + - else + = succeed "." do + = @photo.license_name + %p + = link_to "View on Flickr", @photo.link_url + + .span6 + - if @photo.plantings.count > 0 + %p This photo depicts: + %ul + - @photo.plantings.each do |p| + %li= link_to p, p + +%p= image_tag(@photo.fullsize_url, :alt => @photo.title, :class => 'img-rounded') + diff --git a/app/views/plantings/show.html.haml b/app/views/plantings/show.html.haml index f8e375166..137ffaaa4 100644 --- a/app/views/plantings/show.html.haml +++ b/app/views/plantings/show.html.haml @@ -46,12 +46,6 @@ = pluralize(@planting.crop_plantings_count, "time") by #{Growstuff::Application.config.site_name} members -%h2 Pictures -.row - - (1..6).each do - .span2 - = image_tag('http://placehold.it/150x150', :alt => '', :class => 'img-rounded') - %h2 Notes @@ -60,3 +54,27 @@ - if can? :edit, @planting = link_to 'Edit', edit_planting_path(@planting), :class => 'btn btn-mini' + + +- if @planting.photos.count > 0 or (can? :edit, @planting and can? :create, Photo) + %h2 Pictures + +- c = 0 +%ul.thumbnails + .row + - @planting.photos.each do |p| + - c += 1 + %li.span2 + .thumbnail(style='height: 220px') + = link_to image_tag(p.thumbnail_url, :alt => p.title, :class => 'img-rounded'), p + %p + = link_to p.title, p + by + = link_to p.owner, p.owner + - if (c % 6) == 0 + .row + - if can? :create, Photo and can? :edit, @planting + %li.span2 + .thumbnail(style='height: 220px') + %p{:style => 'text-align: center; padding-top: 50px'} + = link_to "Add photo", new_photo_path(:planting_id => @planting.id), :class => 'btn btn-primary' diff --git a/config/routes.rb b/config/routes.rb index f69f07355..c14a07e76 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,5 +1,8 @@ Growstuff::Application.routes.draw do + resources :photos + + devise_for :members, :controllers => { :registrations => "registrations" } resources :authentications diff --git a/db/migrate/20130508104506_create_photos.rb b/db/migrate/20130508104506_create_photos.rb new file mode 100644 index 000000000..f7c56188d --- /dev/null +++ b/db/migrate/20130508104506_create_photos.rb @@ -0,0 +1,12 @@ +class CreatePhotos < ActiveRecord::Migration + def change + create_table :photos do |t| + t.integer :owner_id, :null => false + t.integer :flickr_photo_id, :null => false + t.string :thumbnail_url, :null => false + t.string :fullsize_url, :null => false + + t.timestamps + end + end +end diff --git a/db/migrate/20130509123711_add_metadata_to_photos.rb b/db/migrate/20130509123711_add_metadata_to_photos.rb new file mode 100644 index 000000000..9e73a6272 --- /dev/null +++ b/db/migrate/20130509123711_add_metadata_to_photos.rb @@ -0,0 +1,22 @@ +class AddMetadataToPhotos < ActiveRecord::Migration + def up + change_table :photos do |t| + t.string :title + t.string :license_name + t.string :license_url + t.string :link_url + t.change :title, :string, :null => false + t.change :license_name, :string, :null => false + t.change :link_url, :string, :null => false + end + end + + def down + change_table :photos do |t| + t.remove :title + t.remove :license_name + t.remove :license_url + t.remove :link_url + end + end +end diff --git a/db/migrate/20130531110729_add_photos_plantings_table.rb b/db/migrate/20130531110729_add_photos_plantings_table.rb new file mode 100644 index 000000000..07040ed93 --- /dev/null +++ b/db/migrate/20130531110729_add_photos_plantings_table.rb @@ -0,0 +1,8 @@ +class AddPhotosPlantingsTable < ActiveRecord::Migration + def change + create_table :photos_plantings, :id => false do |t| + t.integer :photo_id + t.integer :planting_id + end + end +end diff --git a/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb b/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb new file mode 100644 index 000000000..2dd847ac4 --- /dev/null +++ b/db/migrate/20130601011725_change_flickr_photo_id_to_string.rb @@ -0,0 +1,8 @@ +class ChangeFlickrPhotoIdToString < ActiveRecord::Migration + def up + change_column :photos, :flickr_photo_id, :string + end + def down + change_column :photos, :flickr_photo_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index c68bde39d..fae5ca762 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130529032813) do +ActiveRecord::Schema.define(:version => 20130601011725) do create_table "account_types", :force => true do |t| t.string "name", :null => false @@ -160,6 +160,24 @@ ActiveRecord::Schema.define(:version => 20130529032813) do t.integer "product_id" end + create_table "photos", :force => true do |t| + t.integer "owner_id", :null => false + t.string "flickr_photo_id", :null => false + t.string "thumbnail_url", :null => false + t.string "fullsize_url", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "title", :null => false + t.string "license_name", :null => false + t.string "license_url" + t.string "link_url", :null => false + end + + create_table "photos_plantings", :id => false, :force => true do |t| + t.integer "photo_id" + t.integer "planting_id" + end + create_table "plantings", :force => true do |t| t.integer "garden_id", :null => false t.integer "crop_id", :null => false diff --git a/script/deploy-tasks.sh b/script/deploy-tasks.sh new file mode 100755 index 000000000..dc18a2390 --- /dev/null +++ b/script/deploy-tasks.sh @@ -0,0 +1,14 @@ +# tasks to run at deploy time, usually after 'rake db:migrate' + +# When adding tasks, do so in chronological order, and note the date +# when it was added. This will help us know which ones have been run +# and can safely be commented out or removed. + +echo "2013-05-?? - Replacing empty post/notification subjects with '(no subject)'" +rake growstuff:oneoff:empty_subjects + +echo "2013-06-03 - replace empty garden names with 'Garden'" +rake growstuff:oneoff:empty_garden_names + + + diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb new file mode 100644 index 000000000..d31edabc4 --- /dev/null +++ b/spec/controllers/photos_controller_spec.rb @@ -0,0 +1,235 @@ +require 'spec_helper' + +describe PhotosController do + + login_member + + def valid_attributes + { + "owner_id" => "1", + "flickr_photo_id" => 1, + "title" => "Photo", + "license_name" => "CC-BY", + "thumbnail_url" => 'http://example.com/thumb.jpg', + "fullsize_url" => 'http://example.com/full.jpg', + "link_url" => 'http://example.com' + } + end + + def valid_session + {} + end + + describe "GET index" do + it "assigns all photos as @photos" do + photo = Photo.create! valid_attributes + get :index, {} + assigns(:photos).should eq([photo]) + end + end + + describe "GET show" do + it "assigns the requested photo as @photo" do + photo = Photo.create! valid_attributes + get :show, {:id => photo.to_param} + assigns(:photo).should eq(photo) + end + end + + describe "GET new" do + it "assigns the flickr auth as @flickr_auth" do + @member = FactoryGirl.create(:member) + sign_in @member + @member.stub(:flickr_photos) { [] } + controller.stub(:current_member) { @member } + @auth = FactoryGirl.create(:flickr_authentication, :member => @member) + get :new, {} + assigns(:flickr_auth).should be_an_instance_of(Authentication) + end + + it "assigns a new photo as @photo" do + get :new, {} + assigns(:photo).should be_a_new(Photo) + end + + it "assigns a planting id" do + get :new, { :planting_id => 5 } + assigns(:planting_id).should eq "5" + end + + end + + describe "GET edit" do + it "assigns the requested photo as @photo" do + photo = Photo.create! valid_attributes + get :edit, {:id => photo.to_param} + assigns(:photo).should eq(photo) + end + end + + describe "POST create" do + before(:each) do + Photo.any_instance.stub(:flickr_metadata).and_return( { + :title => "A Heartbreaking work of staggering genius", + :license_name => "CC-BY", + :license_url => "http://example.com/aybpl", + :thumbnail_url => "http://example.com/thumb.jpg", + :fullsize_url => "http://example.com/full.jpg", + :link_url => "http://example.com" + }) + end + describe "with valid params" do + it "creates a new Photo" do + expect { + post :create, {:photo => { :flickr_photo_id => 1 } } + }.to change(Photo, :count).by(1) + end + + it "assigns a newly created photo as @photo" do + post :create, {:photo => { :flickr_photo_id => 1 } } + assigns(:photo).should be_a(Photo) + assigns(:photo).should be_persisted + end + + it "redirects to the created photo" do + post :create, {:photo => { :flickr_photo_id => 1 } } + response.should redirect_to(Photo.last) + end + + it "attaches the photo to a planting" do + member = FactoryGirl.create(:member) + controller.stub(:current_member) { member } + garden = FactoryGirl.create(:garden, :owner => member) + planting = FactoryGirl.create(:planting, :garden => garden) + photo = FactoryGirl.create(:photo, :owner => member) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :planting_id => planting.id } + Photo.last.plantings.first.should eq planting + end + + it "doesn't attach a photo to a planting twice" do + member = FactoryGirl.create(:member) + controller.stub(:current_member) { member } + garden = FactoryGirl.create(:garden, :owner => member) + planting = FactoryGirl.create(:planting, :garden => garden) + photo = FactoryGirl.create(:photo, :owner => member) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :planting_id => planting.id } + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :planting_id => planting.id } + Photo.last.plantings.count.should eq 1 + end + end + + describe "for the second time" do + it "does not add a photo twice" do + expect { + post :create, {:photo => { :flickr_photo_id => 1 } } + }.to change(Photo, :count).by(1) + expect { + post :create, {:photo => { :flickr_photo_id => 1 } } + }.to change(Photo, :count).by(0) + end + end + + describe "with matching owners" do + it "creates the planting/photo link" do + member = FactoryGirl.create(:member) + controller.stub(:current_member) { member } + garden = FactoryGirl.create(:garden, :owner => member) + planting = FactoryGirl.create(:planting, :garden => garden) + photo = FactoryGirl.create(:photo, :owner => member) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :planting_id => planting.id } + Photo.last.plantings.first.should eq planting + end + end + + describe "with mismatched owners" do + it "creates the planting/photo link" do + # members will be auto-created, and different + planting = FactoryGirl.create(:planting) + photo = FactoryGirl.create(:photo) + post :create, {:photo => { :flickr_photo_id => photo.flickr_photo_id }, + :planting_id => planting.id } + Photo.last.plantings.first.should_not eq planting + end + end + + describe "with invalid params" do + it "assigns a newly created but unsaved photo as @photo" do + # Trigger the behavior that occurs when invalid params are submitted + Photo.any_instance.stub(:save).and_return(false) + post :create, {:photo => { "owner_id" => "invalid value" }} + assigns(:photo).should be_a_new(Photo) + end + + it "re-renders the 'new' template" do + # Trigger the behavior that occurs when invalid params are submitted + Photo.any_instance.stub(:save).and_return(false) + post :create, {:photo => { "owner_id" => "invalid value" }} + response.should render_template("new") + end + end + end + + describe "PUT update" do + describe "with valid params" do + it "updates the requested photo" do + photo = Photo.create! valid_attributes + # Assuming there are no other photos in the database, this + # specifies that the Photo created on the previous line + # receives the :update_attributes message with whatever params are + # submitted in the request. + Photo.any_instance.should_receive(:update_attributes).with({ "owner_id" => "1" }) + put :update, {:id => photo.to_param, :photo => { "owner_id" => "1" }} + end + + it "assigns the requested photo as @photo" do + photo = Photo.create! valid_attributes + put :update, {:id => photo.to_param, :photo => valid_attributes} + assigns(:photo).should eq(photo) + end + + it "redirects to the photo" do + photo = Photo.create! valid_attributes + put :update, {:id => photo.to_param, :photo => valid_attributes} + response.should redirect_to(photo) + end + end + + describe "with invalid params" do + it "assigns the photo as @photo" do + photo = Photo.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Photo.any_instance.stub(:save).and_return(false) + put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }} + assigns(:photo).should eq(photo) + end + + it "re-renders the 'edit' template" do + photo = Photo.create! valid_attributes + # Trigger the behavior that occurs when invalid params are submitted + Photo.any_instance.stub(:save).and_return(false) + put :update, {:id => photo.to_param, :photo => { "owner_id" => "invalid value" }} + response.should render_template("edit") + end + end + end + + describe "DELETE destroy" do + it "destroys the requested photo" do + photo = Photo.create! valid_attributes + expect { + delete :destroy, {:id => photo.to_param} + }.to change(Photo, :count).by(-1) + end + + it "redirects to the photos list" do + photo = Photo.create! valid_attributes + delete :destroy, {:id => photo.to_param} + response.should redirect_to(photos_url) + end + end + +end diff --git a/spec/factories/photos.rb b/spec/factories/photos.rb new file mode 100644 index 000000000..70c1fb4fb --- /dev/null +++ b/spec/factories/photos.rb @@ -0,0 +1,19 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :photo do + owner + flickr_photo_id 1 + title "Still life with chillies" + license_name "CC-BY" + license_url "http://example.com/license.html" + thumbnail_url "http://example.com/thumb.jpg" + fullsize_url "http://example.com/full.jpg" + link_url "http://example.com/" + + factory :unlicensed_photo do + license_name "All rights reserved" + license_url "" + end + end +end diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb new file mode 100644 index 000000000..8ac067ab7 --- /dev/null +++ b/spec/models/photo_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Photo do + + describe 'flickr_metadata' do + # Any further tests led to us MOCKING ALL THE THINGS + # which was epistemologically unsatisfactory. + # So we're just going to test that the method exists. + it 'exists' do + photo = Photo.new(:owner_id => 1) + photo.should.respond_to? :flickr_metadata + end + end +end diff --git a/spec/models/planting_spec.rb b/spec/models/planting_spec.rb index ca3c67c0c..8a9a75e93 100644 --- a/spec/models/planting_spec.rb +++ b/spec/models/planting_spec.rb @@ -86,4 +86,13 @@ describe Planting do @planting.errors[:sunniness].should include("not valid is not a valid sunniness value") end + context 'photos' do + it 'has a photo' do + @planting = FactoryGirl.create(:planting) + @photo = FactoryGirl.create(:photo) + @planting.photos << @photo + @planting.photos.first.should eq @photo + end + end + end diff --git a/spec/requests/photos_spec.rb b/spec/requests/photos_spec.rb new file mode 100644 index 000000000..bfb595245 --- /dev/null +++ b/spec/requests/photos_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe "Photos" do + describe "GET /photos" 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 photos_path + response.status.should be(200) + end + end +end diff --git a/spec/routing/photos_routing_spec.rb b/spec/routing/photos_routing_spec.rb new file mode 100644 index 000000000..d7dc394ed --- /dev/null +++ b/spec/routing/photos_routing_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe PhotosController do + describe "routing" do + + it "routes to #index" do + get("/photos").should route_to("photos#index") + end + + it "routes to #new" do + get("/photos/new").should route_to("photos#new") + end + + it "routes to #show" do + get("/photos/1").should route_to("photos#show", :id => "1") + end + + it "routes to #edit" do + get("/photos/1/edit").should route_to("photos#edit", :id => "1") + end + + it "routes to #create" do + post("/photos").should route_to("photos#create") + end + + it "routes to #update" do + put("/photos/1").should route_to("photos#update", :id => "1") + end + + it "routes to #destroy" do + delete("/photos/1").should route_to("photos#destroy", :id => "1") + end + + end +end diff --git a/spec/views/photos/edit.html.haml_spec.rb b/spec/views/photos/edit.html.haml_spec.rb new file mode 100644 index 000000000..3b8cde557 --- /dev/null +++ b/spec/views/photos/edit.html.haml_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe "photos/edit" do + before(:each) do + @photo = assign(:photo, stub_model(Photo, + :owner_id => 1, + :flickr_photo_id => 1, + :thumbnail_url => "MyString", + :fullsize_url => "MyString" + )) + end + +end diff --git a/spec/views/photos/index.html.haml_spec.rb b/spec/views/photos/index.html.haml_spec.rb new file mode 100644 index 000000000..bf4699c48 --- /dev/null +++ b/spec/views/photos/index.html.haml_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe "photos/index" do + before(:each) do + page = 1 + per_page = 2 + total_entries = 2 + photos = WillPaginate::Collection.create(page, per_page, total_entries) do |pager| + pager.replace([ + FactoryGirl.create(:photo), + FactoryGirl.create(:photo) + ]) + end + assign(:photos, photos) + end + + it "renders a gallery of photos" do + render + assert_select ".thumbnail", :count => 2 + assert_select "img", :count => 2 + end +end diff --git a/spec/views/photos/new.html.haml_spec.rb b/spec/views/photos/new.html.haml_spec.rb new file mode 100644 index 000000000..1ea5a34d9 --- /dev/null +++ b/spec/views/photos/new.html.haml_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe "photos/new" do + before(:each) do + @member = FactoryGirl.create(:member) + controller.stub(:current_user) { @member } + assign(:photos, []) + assign(:flickr_auth, FactoryGirl.create(:flickr_authentication, :member => @member)) + end + + it "shows a list of photos" do + render + assert_select "ul.thumbnails" + end +end diff --git a/spec/views/photos/show.html.haml_spec.rb b/spec/views/photos/show.html.haml_spec.rb new file mode 100644 index 000000000..fa03e08d6 --- /dev/null +++ b/spec/views/photos/show.html.haml_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe "photos/show" do + context "CC-licensed photo" do + before(:each) do + @photo = assign(:photo, FactoryGirl.create(:photo)) + render + end + + it "shows the image" do + assert_select "img[src=#{@photo.fullsize_url}]" + end + + it "links to the owner's profile" do + assert_select "a", :href => @photo.owner + end + + it "links to the CC license" do + assert_select "a", :href => @photo.license_url, + :text => @photo.license_name + end + + it "shows a link to the original image" do + assert_select "a", :href => @photo.link_url, :text => "View on Flickr" + end + end + + context "unlicensed photo" do + before(:each) do + @photo = assign(:photo, FactoryGirl.create(:unlicensed_photo)) + render + end + + it "contains the phrase 'All rights reserved'" do + rendered.should contain "All rights reserved" + end + end + + context "linked to a planting" do + before(:each) do + @photo = FactoryGirl.create(:photo) + @planting = FactoryGirl.create(:planting) + @planting.photos << @photo + @photo = assign(:photo, @photo) + render + end + + it "shows link to planting" do + assert_select "a[href=#{planting_path(@planting)}]" + end + + end + +end diff --git a/spec/views/plantings/show.html.haml_spec.rb b/spec/views/plantings/show.html.haml_spec.rb index 414209f1d..71c6c94d1 100644 --- a/spec/views/plantings/show.html.haml_spec.rb +++ b/spec/views/plantings/show.html.haml_spec.rb @@ -29,6 +29,24 @@ describe "plantings/show" do rendered.should_not contain 'sun' end + it "shows photos" do + @member = FactoryGirl.create(:member) + controller.stub(:current_user) { @member } + @p = create_planting_for(@member) + @photo = FactoryGirl.create(:photo, :owner => @member) + @p.photos << @photo + render + assert_select "img[src=#{@photo.thumbnail_url}]" + end + + it "shows a link to add photos" do + @member = FactoryGirl.create(:member) + controller.stub(:current_user) { @member } + @p = create_planting_for(@member) + render + rendered.should contain "Add photo" + end + context "no location set" do before(:each) do controller.stub(:current_user) { nil }