Merge pull request #427 from oshiho3/PT60321968_corps_posts

Added crops-posts association as well as updated GUI for crop show
This commit is contained in:
Skud
2014-10-15 12:18:54 +01:00
23 changed files with 287 additions and 46 deletions

View File

@@ -69,6 +69,7 @@ class CropsController < ApplicationController
# GET /crops/1.json
def show
@crop = Crop.includes(:scientific_names, {:plantings => :photos}).find(params[:id])
@posts = @crop.posts.paginate(:page => params[:page])
respond_to do |format|
format.html # show.html.haml

View File

@@ -6,23 +6,21 @@ class HarvestsController < ApplicationController
# GET /harvests.json
def index
@owner = Member.find_by_slug(params[:owner])
@crop = Crop.find_by_slug(params[:crop])
if @owner
@harvests = @owner.harvests.includes(:owner, :crop).paginate(:page => params[:page])
@harvests = @owner.harvests.includes(:owner, :crop)
elsif @crop
@harvests = @crop.harvests.includes(:owner, :crop)
else
@harvests = Harvest.includes(:owner, :crop).paginate(:page => params[:page])
@harvests = Harvest.includes(:owner, :crop)
end
respond_to do |format|
format.html # index.html.erb
format.html { @harvests = @harvests.paginate(:page => params[:page]) }
format.json { render json: @harvests }
format.csv do
if @owner
@filename = "Growstuff-#{@owner}-Harvests-#{Time.zone.now.to_s(:number)}.csv"
@harvests = @owner.harvests.includes(:owner, :crop)
else
@filename = "Growstuff-Harvests-#{Time.zone.now.to_s(:number)}.csv"
@harvests = Harvest.includes(:owner, :crop)
end
specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil)
@filename = "Growstuff-#{specifics}Harvests-#{Time.zone.now.to_s(:number)}.csv"
render :csv => @harvests
end
end

View File

@@ -7,24 +7,22 @@ class PlantingsController < ApplicationController
# GET /plantings.json
def index
@owner = Member.find_by_slug(params[:owner])
@crop = Crop.find_by_slug(params[:crop])
if @owner
@plantings = @owner.plantings.includes(:owner, :crop, :garden).paginate(:page => params[:page])
@plantings = @owner.plantings.includes(:owner, :crop, :garden)
elsif @crop
@plantings = @crop.plantings.includes(:owner, :crop, :garden)
else
@plantings = Planting.includes(:owner, :crop, :garden).paginate(:page => params[:page])
@plantings = Planting.includes(:owner, :crop, :garden)
end
respond_to do |format|
format.html # index.html.erb
format.html { @plantings = @plantings.paginate(:page => params[:page]) }
format.json { render json: @plantings }
format.rss { render :layout => false } #index.rss.builder
format.csv do
if @owner
@filename = "Growstuff-#{@owner}-Plantings-#{Time.zone.now.to_s(:number)}.csv"
@plantings = @owner.plantings.includes(:owner, :crop, :garden)
else
@filename = "Growstuff-Plantings-#{Time.zone.now.to_s(:number)}.csv"
@plantings = Planting.includes(:owner, :crop, :garden)
end
specifics = (@owner ? "#{@owner.name}-" : @crop ? "#{@crop.name}-" : nil)
@filename = "Growstuff-#{specifics}Plantings-#{Time.zone.now.to_s(:number)}.csv"
render :csv => @plantings
end
end

View File

@@ -17,6 +17,9 @@ class Crop < ActiveRecord::Base
belongs_to :parent, :class_name => 'Crop'
has_many :varieties, :class_name => 'Crop', :foreign_key => 'parent_id'
has_and_belongs_to_many :posts
before_destroy {|crop| crop.posts.clear}
default_scope order("lower(name) asc")
scope :recent, reorder("created_at desc")

View File

@@ -5,6 +5,9 @@ class Post < ActiveRecord::Base
belongs_to :author, :class_name => 'Member'
belongs_to :forum
has_many :comments, :dependent => :destroy
has_and_belongs_to_many :crops
before_destroy {|post| post.crops.clear}
after_save :update_crops_posts_association
# also has_many notifications, but kinda meaningless to get at them
# from this direction, so we won't set up an association for now.
@@ -39,4 +42,15 @@ class Post < ActiveRecord::Base
end
end
private
def update_crops_posts_association
self.crops.destroy_all
# look for crops mentioned in the post. eg. [tomato](crop)
self.body.scan(Haml::Filters::GrowstuffMarkdown::CROP_REGEX) do |m|
# find crop case-insensitively
crop = Crop.where('lower(name) = ?', $1.downcase).first
# create association
self.crops << crop if crop and not self.crops.include?(crop)
end
end
end

View File

@@ -4,16 +4,18 @@
Nobody has harvested this crop yet.
- else
%ul
- crop.harvests.each do |harvest|
- crop.harvests.take(3).each do |harvest|
%li
= link_to "#{harvest.owner} harvested #{display_quantity(harvest)}.", harvest_path(harvest)
= render :partial => 'members/location', :locals => { :member => harvest.owner }
%small
= distance_of_time_in_words(harvest.created_at, Time.zone.now)
ago.
%p.col-md-offset-1
= link_to "See all #{crop.name} harvests", harvests_by_crop_path(crop)
- if current_member
= link_to "Track your #{crop.name} harvests.", new_harvest_path()
%p.col-md-offset-1
= link_to "Track your #{crop.name} harvests.", new_harvest_path(:crop_id => crop.id)
- else
= render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} harvests" }

View File

@@ -0,0 +1,21 @@
%h4 Plantings
- if crop.plantings.empty?
%p
Nobody has planted this crop yet.
- else
%ul
- crop.plantings.take(3).each do |planting|
%li
= link_to "#{planting.owner} planted #{planting.quantity} #{planting.planted_from}.", planting_path(planting)
= render :partial => 'members/location', :locals => { :member => planting.owner }
%small
= distance_of_time_in_words(planting.created_at, Time.zone.now)
ago.
%p.col-md-offset-1
= link_to "See all #{crop.name} plantings", plantings_by_crop_path(crop)
- if current_member
%p.col-md-offset-1
= link_to "Track your #{crop.name} plantings.", new_planting_path(:crop_id => crop.id)
- else
= render :partial => 'shared/signin_signup', :locals => { :to => "track your #{crop.name} plantings" }

View File

@@ -33,12 +33,18 @@
%div#cropmap
- if @crop.plantings.size > 0
%a{:name => 'posts'}
%div.pagination
= page_entries_info @posts, :model => "posts"
= will_paginate @posts, :params => {:anchor => "posts"}
%h2 All plantings
- unless @posts.empty?
- @posts.each do |post|
= render :partial => "posts/single", :locals => { :post => post, :subject => true }
- @crop.plantings.each do |p|
= render :partial => "plantings/thumbnail", :locals => { :planting => p, :title => 'owner' }
%div.pagination
= page_entries_info @posts, :model => "posts"
= will_paginate @posts, :params => {:anchor => "posts"}
.col-md-3
- if can? :edit, @crop or can? :destroy, @crop
@@ -76,5 +82,6 @@
%ul
%li= link_to 'Wikipedia (English)', @crop.en_wikipedia_url
= render :partial => 'plantings', :locals => { :crop => @crop }
= render :partial => 'harvests', :locals => { :crop => @crop }
= render :partial => 'find_seeds', :locals => { :crop => @crop }

View File

@@ -1,4 +1,4 @@
- content_for :title, @owner ? "#{@owner}'s harvests" : "Everyone's harvests"
- content_for :title, @owner ? "#{@owner}'s harvests" : @crop ? "Everyone's #{@crop.name} harvests" : "Everyone's harvests"
%p
#{ENV['GROWSTUFF_SITE_NAME']} helps you track what you're

View File

@@ -1,4 +1,4 @@
- content_for :title, @owner ? "#{@owner}'s plantings" : "Everyone's plantings"
- content_for :title, @owner ? "#{@owner}'s plantings" : @crop ? "Everyone's #{@crop.name} plantings" : "Everyone's plantings"
%p
- if can? :create, Planting

View File

@@ -12,6 +12,7 @@ Growstuff::Application.routes.draw do
resources :plantings
match '/plantings/owner/:owner' => 'plantings#index', :as => 'plantings_by_owner'
match '/plantings/crop/:crop' => 'plantings#index', :as => 'plantings_by_crop'
resources :gardens
match '/gardens/owner/:owner' => 'gardens#index', :as => 'gardens_by_owner'
@@ -21,6 +22,7 @@ Growstuff::Application.routes.draw do
resources :harvests
match '/harvests/owner/:owner' => 'harvests#index', :as => 'harvests_by_owner'
match '/harvests/crop/:crop' => 'harvests#index', :as => 'harvests_by_crop'
resources :posts
match '/posts/author/:author' => 'posts#index', :as => 'posts_by_author'

View File

@@ -0,0 +1,10 @@
class AddCropsPostsTable < ActiveRecord::Migration
def change
create_table :crops_posts, :id => false do |t|
t.integer :crop_id
t.integer :post_id
end
add_index :crops_posts, [:crop_id, :post_id]
add_index :crops_posts, :crop_id
end
end

View File

@@ -64,6 +64,14 @@ ActiveRecord::Schema.define(:version => 20141002022459) do
add_index "crops", ["name"], :name => "index_crops_on_name"
add_index "crops", ["slug"], :name => "index_crops_on_slug", :unique => true
create_table "crops_posts", :id => false, :force => true do |t|
t.integer "crop_id"
t.integer "post_id"
end
add_index "crops_posts", ["crop_id", "post_id"], :name => "index_crops_posts_on_crop_id_and_post_id"
add_index "crops_posts", ["crop_id"], :name => "index_crops_posts_on_crop_id"
create_table "forums", :force => true do |t|
t.string "name", :null => false
t.text "description", :null => false

View File

@@ -2,12 +2,13 @@ require 'bluecloth'
module Haml::Filters
module GrowstuffMarkdown
CROP_REGEX = /\[([^\[\]]+?)\]\(crop\)/
include Haml::Filters::Base
def render(text)
# turn [tomato](crop) into [tomato](http://growstuff.org/crops/tomato)
expanded = text.gsub(/\[([^\[\]]+?)\]\(crop\)/) do |m|
expanded = text.gsub(CROP_REGEX) do |m|
crop_str = $1
# find crop case-insensitively
crop = Crop.where('lower(name) = ?', crop_str.downcase).first

View File

@@ -264,6 +264,12 @@ namespace :growstuff do
end
end
desc "October 2014: generate crops_posts records for existing posts"
task :generate_crops_posts_records => :environment do
Post.find_each do |p|
p.save
end
end
end # end oneoff section
end

View File

@@ -15,3 +15,6 @@ rake growstuff:import_crops file=db/seeds/crops-11-tomatoes.csv
echo "2014-10-02 - remove unused photos"
rake growstuff:oneoff:remove_unused_photos
echo "2014-10-05 - generate crops_posts records for existing posts"
rake growstuff:oneoff:generate_crops_posts_records

View File

@@ -12,10 +12,30 @@ describe HarvestsController do
end
describe "GET index" do
before do
@member1 = FactoryGirl.create(:member)
@member2 = FactoryGirl.create(:member)
@tomato = FactoryGirl.create(:tomato)
@maize = FactoryGirl.create(:maize)
@harvest1 = FactoryGirl.create(:harvest, :owner_id => @member1.id, :crop_id => @tomato.id)
@harvest2 = FactoryGirl.create(:harvest, :owner_id => @member2.id, :crop_id => @maize.id)
end
it "assigns all harvests as @harvests" do
harvest = Harvest.create! valid_attributes
get :index, {}
assigns(:harvests).should eq([harvest])
assigns(:harvests).should =~ [@harvest1, @harvest2]
end
it "picks up owner from params and shows owner's harvests only" do
get :index, {:owner => @member1.slug}
assigns(:owner).should eq @member1
assigns(:harvests).should eq [@harvest1]
end
it "picks up crop from params and shows the harvests for the crop only" do
get :index, {:crop => @maize.name}
assigns(:crop).should eq @maize
assigns(:harvests).should eq [@harvest2]
end
end

View File

@@ -12,10 +12,30 @@ describe PlantingsController do
end
describe "GET index" do
it "picks up owner from params" do
owner = FactoryGirl.create(:member)
get :index, {:owner => owner.slug}
assigns(:owner).should eq(owner)
before do
@member1 = FactoryGirl.create(:member)
@member2 = FactoryGirl.create(:member)
@tomato = FactoryGirl.create(:tomato)
@maize = FactoryGirl.create(:maize)
@planting1 = FactoryGirl.create(:planting, :crop => @tomato, :owner => @member1)
@planting2 = FactoryGirl.create(:planting, :crop => @maize, :owner => @member2)
end
it "assigns all plantings as @plantings" do
get :index, {}
assigns(:plantings).should =~ [@planting1, @planting2]
end
it "picks up owner from params and shows owner's plantings only" do
get :index, {:owner => @member1.slug}
assigns(:owner).should eq @member1
assigns(:plantings).should eq [@planting1]
end
it "picks up crop from params and shows the plantings for the crop only" do
get :index, {:crop => @maize.name}
assigns(:crop).should eq @maize
assigns(:plantings).should eq [@planting2]
end
end

View File

@@ -343,4 +343,23 @@ describe Crop do
end
end
context "crop-post association" do
let!(:tomato) { FactoryGirl.create(:tomato) }
let!(:maize) { FactoryGirl.create(:maize) }
let!(:post) { FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") }
describe "destroying a crop" do
before do
tomato.destroy
end
it "should delete the association between post and the crop(tomato)" do
expect(Post.find(post).crops).to eq [maize]
end
it "should not delete the posts" do
expect(Post.find(post)).to_not eq nil
end
end
end
end

View File

@@ -95,4 +95,44 @@ describe Post do
end
end
context "crop-post association" do
let!(:tomato) { FactoryGirl.create(:tomato) }
let!(:maize) { FactoryGirl.create(:maize) }
let!(:chard) { FactoryGirl.create(:chard) }
let!(:post) { FactoryGirl.create(:post, :body => "[maize](crop)[tomato](crop)[tomato](crop)") }
it "should be generated" do
expect(tomato.posts).to eq [post]
expect(maize.posts).to eq [post]
end
it "should not duplicate" do
expect(post.crops) =~ [tomato, maize]
end
it "should be updated when post was modified" do
post.update_attributes(:body => "[chard](crop)")
expect(post.crops).to eq [chard]
expect(chard.posts).to eq [post]
expect(tomato.posts).to eq []
expect(maize.posts).to eq []
end
describe "destroying the post" do
before do
post.destroy
end
it "should delete the association" do
expect(Crop.find(tomato).posts).to eq []
expect(Crop.find(maize).posts).to eq []
end
it "should not delete the crops" do
expect(Crop.find(tomato)).to_not eq nil
expect(Crop.find(maize)).to_not eq nil
end
end
end
end

View File

@@ -7,6 +7,16 @@ describe "crops/show" do
:scientific_names => [ FactoryGirl.create(:zea_mays) ]
)
assign(:crop, @crop)
@author = FactoryGirl.create(:member)
page = 1
per_page = 2
total_entries = 2
@posts = WillPaginate::Collection.create(page, per_page, total_entries) do |pager|
pager.replace([
@post1 = FactoryGirl.create(:post, :author => @author, :body => "Post it!" ),
@post2 = FactoryGirl.create(:post, :author => @author, :body => "Done!" )
])
end
end
context 'photos' do
@@ -125,11 +135,10 @@ describe "crops/show" do
context "has plantings" do
before(:each) do
@owner = FactoryGirl.create(:member)
@garden = FactoryGirl.create(:garden, :owner => @owner)
@owner = FactoryGirl.create(:london_member)
@planting = FactoryGirl.create(:planting,
:garden => @garden,
:crop => @crop
:crop => @crop,
:owner => @owner
)
@crop.reload # to pick up latest plantings_count
end
@@ -137,16 +146,24 @@ describe "crops/show" do
it "links to people who are growing this crop" do
render
rendered.should contain @owner.login_name
rendered.should contain @garden.name
rendered.should contain @owner.location
end
end
it "shows photos where available" do
@photo = FactoryGirl.create(:photo)
@planting.photos << @photo
context "has posts" do
it "links to posts" do
render
assert_select "img", :src => @photo.thumbnail_url
@posts.each do |p|
rendered.should contain p.author.login_name
rendered.should contain p.subject
rendered.should contain p.body
end
end
it "contains two gravatar icons" do
render
assert_select "img", :src => /gravatar\.com\/avatar/, :count => 2
end
end
context 'varieties' do
@@ -189,9 +206,36 @@ describe "crops/show" do
rendered.should contain "Harvest this"
end
it "links to the right crop in the planting link" do
it "links to the right crop in the new planting link" do
assert_select("a[href=#{new_planting_path}?crop_id=#{@crop.id}]")
end
it "links to the right crop in the new harvest link" do
assert_select("a[href=#{new_harvest_path}?crop_id=#{@crop.id}]")
end
it { rendered.should contain "Nobody has planted this crop yet" }
it { rendered.should contain "Nobody has harvested this crop yet" }
context "should have a link to" do
before do
FactoryGirl.create(:planting, :crop => @crop)
FactoryGirl.create(:harvest, :crop => @crop)
@crop.reload
render
end
it "show all plantings by the crop link" do
assert_select("a[href=#{plantings_by_crop_path @crop}]")
end
it "show all harvests by the crop link" do
assert_select("a[href=#{harvests_by_crop_path @crop}]")
end
end
end
context "logged in and crop wrangler" do

View File

@@ -41,4 +41,17 @@ describe "harvests/index" do
assert_select "a", :href => harvests_path(:format => 'csv')
assert_select "a", :href => harvests_path(:format => 'json')
end
it "displays member's name in title" do
assign(:owner, @member)
render
view.content_for(:title).should contain @member.login_name
end
it "displays crop's name in title" do
assign(:crop, @tomato)
render
view.content_for(:title).should contain @tomato.name
end
end

View File

@@ -48,4 +48,15 @@ describe "plantings/index" do
assert_select "a", :href => plantings_path(:format => 'rss')
end
it "displays member's name in title" do
assign(:owner, @member)
render
view.content_for(:title).should contain @member.login_name
end
it "displays crop's name in title" do
assign(:crop, @tomato)
render
view.content_for(:title).should contain @tomato.name
end
end