From 090c1c79efaa37cd0f0e2cdc4f6828bae97cdcaf Mon Sep 17 00:00:00 2001 From: windless Date: Tue, 16 Oct 2012 17:35:52 +0800 Subject: [PATCH] Add user microposts --- app/assets/javascripts/microposts.js.coffee | 3 ++ app/assets/stylesheets/custom.css.scss | 28 ++++++++++ app/assets/stylesheets/microposts.css.scss | 3 ++ app/controllers/microposts_controller.rb | 29 +++++++++++ app/controllers/static_pages_controller.rb | 4 ++ app/controllers/users_controller.rb | 8 +-- app/helpers/microposts_helper.rb | 2 + app/helpers/sessions_helper.rb | 7 +++ app/helpers/users_helper.rb | 7 +-- app/models/micropost.rb | 23 ++++++++ app/models/user.rb | 7 +++ app/views/microposts/_micropost.html.erb | 12 +++++ app/views/microposts/create.html.erb | 2 + app/views/microposts/destroy.html.erb | 2 + app/views/shared/_error_messages.html.erb | 6 +-- app/views/shared/_feed.html.erb | 6 +++ app/views/shared/_feed_item.html.erb | 13 +++++ app/views/shared/_micropost_form.html.erb | 7 +++ app/views/shared/_user_info.html.erb | 10 ++++ app/views/static_pages/home.html.erb | 37 +++++++++---- app/views/users/edit.html.erb | 2 +- app/views/users/new.html.erb | 2 +- app/views/users/show.html.erb | 10 ++++ config/routes.rb | 1 + .../20121016074701_create_microposts.rb | 11 ++++ db/schema.rb | 11 +++- lib/tasks/sample_data.rake | 6 +++ spec/factories.rb | 5 ++ spec/models/micropost_spec.rb | 52 +++++++++++++++++++ spec/models/user_spec.rb | 36 +++++++++++++ spec/requests/authentication_pages_spec.rb | 16 +++++- spec/requests/micropost_pages_spec.rb | 42 +++++++++++++++ spec/requests/static_pages_spec.rb | 16 ++++++ spec/requests/user_pages_spec.rb | 8 +++ 34 files changed, 406 insertions(+), 28 deletions(-) create mode 100644 app/assets/javascripts/microposts.js.coffee create mode 100644 app/assets/stylesheets/microposts.css.scss create mode 100644 app/controllers/microposts_controller.rb create mode 100644 app/helpers/microposts_helper.rb create mode 100644 app/models/micropost.rb create mode 100644 app/views/microposts/_micropost.html.erb create mode 100644 app/views/microposts/create.html.erb create mode 100644 app/views/microposts/destroy.html.erb create mode 100644 app/views/shared/_feed.html.erb create mode 100644 app/views/shared/_feed_item.html.erb create mode 100644 app/views/shared/_micropost_form.html.erb create mode 100644 app/views/shared/_user_info.html.erb create mode 100644 db/migrate/20121016074701_create_microposts.rb create mode 100644 spec/models/micropost_spec.rb create mode 100644 spec/requests/micropost_pages_spec.rb diff --git a/app/assets/javascripts/microposts.js.coffee b/app/assets/javascripts/microposts.js.coffee new file mode 100644 index 0000000..7615679 --- /dev/null +++ b/app/assets/javascripts/microposts.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/custom.css.scss b/app/assets/stylesheets/custom.css.scss index 971539e..1c83973 100644 --- a/app/assets/stylesheets/custom.css.scss +++ b/app/assets/stylesheets/custom.css.scss @@ -183,3 +183,31 @@ input, textarea, select, .uneditable-input { } } } + +/* microposts */ + +.microposts { + list-style: none; + margin: 10px 0 0 0; + + li { + padding: 10px 0; + border-top: 1px solid #e8e8e8; + } +} +.content { + display: block; +} +.timestamp { + color: $grayLight; +} +.gravatar { + float: left; + margin-right: 10px; +} +aside { + textarea { + height: 100px; + margin-bottom: 5px; + } +} diff --git a/app/assets/stylesheets/microposts.css.scss b/app/assets/stylesheets/microposts.css.scss new file mode 100644 index 0000000..a581fde --- /dev/null +++ b/app/assets/stylesheets/microposts.css.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the microposts controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/microposts_controller.rb b/app/controllers/microposts_controller.rb new file mode 100644 index 0000000..a60a3fa --- /dev/null +++ b/app/controllers/microposts_controller.rb @@ -0,0 +1,29 @@ +class MicropostsController < ApplicationController + before_filter :signed_in_user, :only => [:create, :destroy] + before_filter :correct_user, :only => :destroy + + def index + end + + def create + @micropost = current_user.microposts.build(params[:micropost]) + if @micropost.save + flash[:success] = "Micropost created!" + redirect_to root_path + else + @feed_items = current_user.feed.paginate(:page => params[:page]) + render 'static_pages/home' + end + end + + def destroy + @micropost.destroy + redirect_to root_path + end + + private + def correct_user + @micropost = current_user.microposts.find_by_id(params[:id]) + redirect_to root_path if @micropost.nil? + end +end diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb index d304760..823db56 100644 --- a/app/controllers/static_pages_controller.rb +++ b/app/controllers/static_pages_controller.rb @@ -1,5 +1,9 @@ class StaticPagesController < ApplicationController def home + if signed_in? + @micropost = current_user.microposts.build + @feed_items = current_user.feed.paginate(:page => params[:page]) + end end def help diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 474ffe5..9c32192 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -13,6 +13,7 @@ def new def show @user = User.find(params[:id]) + @microposts = @user.microposts.paginate(:page => params[:page]) end def create @@ -46,13 +47,6 @@ def destroy end private - def signed_in_user - unless signed_in? - store_location - redirect_to signin_path, :notice => "Pleas sign in." - end - end - def correct_user @user = User.find(params[:id]) redirect_to root_path unless current_user?(@user) diff --git a/app/helpers/microposts_helper.rb b/app/helpers/microposts_helper.rb new file mode 100644 index 0000000..f08aad2 --- /dev/null +++ b/app/helpers/microposts_helper.rb @@ -0,0 +1,2 @@ +module MicropostsHelper +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb index f63ec3e..78a56bd 100644 --- a/app/helpers/sessions_helper.rb +++ b/app/helpers/sessions_helper.rb @@ -33,4 +33,11 @@ def redirect_back_or(default) def store_location session[:return_to] = request.url end + + def signed_in_user + unless signed_in? + store_location + redirect_to signin_path, :notice => "Pleas sign in." + end + end end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index 965f7cb..7c332fb 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -1,7 +1,8 @@ module UsersHelper - def gravatar_for(user) + def gravatar_for(user, options = { :size => 50 }) gravatar_id = Digest::MD5::hexdigest(user.email.downcase) - gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}" - image_tag(gravatar_url, :alt => user.name, :class => "gravatar") + size = options[:size] + gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}?s=#{size}" + image_tag(gravatar_url, :alt => user.name, :class => "gravatar") end end diff --git a/app/models/micropost.rb b/app/models/micropost.rb new file mode 100644 index 0000000..f02c0a0 --- /dev/null +++ b/app/models/micropost.rb @@ -0,0 +1,23 @@ +# == Schema Information +# +# Table name: microposts +# +# id :integer not null, primary key +# content :string(255) +# user_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# + +class Micropost < ActiveRecord::Base + attr_accessible :content + belongs_to :user + + validates :user_id, + :presence => true + validates :content, + :presence => true, + :length => { :maximum => 140 } + + default_scope :order => 'created_at DESC' +end diff --git a/app/models/user.rb b/app/models/user.rb index 3afe6e0..4e49349 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,12 +9,15 @@ # updated_at :datetime not null # password_digest :string(255) # remember_token :string(255) +# admin :boolean default(FALSE) # class User < ActiveRecord::Base attr_accessible :email, :name, :password, :password_confirmation has_secure_password + has_many :microposts, :dependent => :destroy + validates :name, :presence => true, :length => { :maximum => 50 } @@ -35,6 +38,10 @@ class User < ActiveRecord::Base before_save { |user| user.email = email.downcase } before_save :create_remember_token + def feed + microposts + end + private def create_remember_token self.remember_token = SecureRandom.urlsafe_base64 diff --git a/app/views/microposts/_micropost.html.erb b/app/views/microposts/_micropost.html.erb new file mode 100644 index 0000000..622fd56 --- /dev/null +++ b/app/views/microposts/_micropost.html.erb @@ -0,0 +1,12 @@ +
  • + <%= micropost.content %> + + Posted <%= time_ago_in_words(micropost.created_at) %> + + <% if current_user?(micropost.user) %> + <%= link_to 'delete', micropost, :method => :delete, + :data => { :confirm => 'You sure?' }, + :title => micropost.content + %> + <% end %> +
  • diff --git a/app/views/microposts/create.html.erb b/app/views/microposts/create.html.erb new file mode 100644 index 0000000..484883d --- /dev/null +++ b/app/views/microposts/create.html.erb @@ -0,0 +1,2 @@ +

    Microposts#create

    +

    Find me in app/views/microposts/create.html.erb

    diff --git a/app/views/microposts/destroy.html.erb b/app/views/microposts/destroy.html.erb new file mode 100644 index 0000000..35720b5 --- /dev/null +++ b/app/views/microposts/destroy.html.erb @@ -0,0 +1,2 @@ +

    Microposts#destroy

    +

    Find me in app/views/microposts/destroy.html.erb

    diff --git a/app/views/shared/_error_messages.html.erb b/app/views/shared/_error_messages.html.erb index f30fd75..1c69be2 100644 --- a/app/views/shared/_error_messages.html.erb +++ b/app/views/shared/_error_messages.html.erb @@ -1,10 +1,10 @@
    - <% if @user.errors.any? %> + <% if object.errors.any? %>
    - The form contains <%= pluralize(@user.errors.count, 'error') %>. + The form contains <%= pluralize(object.errors.count, 'error') %>.
    diff --git a/app/views/shared/_feed.html.erb b/app/views/shared/_feed.html.erb new file mode 100644 index 0000000..a5e8cca --- /dev/null +++ b/app/views/shared/_feed.html.erb @@ -0,0 +1,6 @@ +<% if @feed_items.any? %> +
      + <%= render :partial => 'shared/feed_item', :collection => @feed_items %> +
    + <%= will_paginate @feed_items %> +<% end %> diff --git a/app/views/shared/_feed_item.html.erb b/app/views/shared/_feed_item.html.erb new file mode 100644 index 0000000..c168ab8 --- /dev/null +++ b/app/views/shared/_feed_item.html.erb @@ -0,0 +1,13 @@ +
  • + <%= link_to gravatar_for(feed_item.user), feed_item.user %> + + <%= link_to feed_item.user.name, feed_item.user %> + + <%= feed_item.content %> + + Posted <%= time_ago_in_words(feed_item.created_at) %> + + <% if current_user?(feed_item.user) %> + <%= link_to 'delete', feed_item, :method => :delete, :data => { :confirm => "You sure?" } %> + <% end %> +
  • diff --git a/app/views/shared/_micropost_form.html.erb b/app/views/shared/_micropost_form.html.erb new file mode 100644 index 0000000..3e42c96 --- /dev/null +++ b/app/views/shared/_micropost_form.html.erb @@ -0,0 +1,7 @@ +<%= form_for(@micropost) do |f| %> + <%= render 'shared/error_messages', :object => f.object %> +
    + <%= f.text_area :content, :rows => 5, :placeholder => "Compose new micropost..." %> +
    + <%= f.submit 'Post', :class => 'btn btn-large btn-primary' %> +<% end %> diff --git a/app/views/shared/_user_info.html.erb b/app/views/shared/_user_info.html.erb new file mode 100644 index 0000000..8b077b8 --- /dev/null +++ b/app/views/shared/_user_info.html.erb @@ -0,0 +1,10 @@ + + <%= gravatar_for current_user, :size => 52 %> + +

    <%= current_user.name %>

    + + <%= link_to 'view my profile', current_user %> + + + <%= pluralize(current_user.microposts.count, 'micropost') %> + diff --git a/app/views/static_pages/home.html.erb b/app/views/static_pages/home.html.erb index d7db054..13f19c6 100644 --- a/app/views/static_pages/home.html.erb +++ b/app/views/static_pages/home.html.erb @@ -1,11 +1,28 @@ -
    -

    Sample App

    -

    - This is the home page for the - Ruby on Rails Tutorial - sample application. -

    - <%= link_to 'Sign up now!', signup_path, :class => 'btn btn-large btn-primary' %> -
    +<% if signed_in? %> +
    +
    +
    + <%= render :partial => 'shared/user_info' %> +
    +
    + <%= render :partial => 'shared/micropost_form' %> +
    +
    +
    +

    Micropost Feed

    + <%= render 'shared/feed' %> +
    +
    +<% else %> +
    +

    Sample App

    +

    + This is the home page for the + Ruby on Rails Tutorial + sample application. +

    + <%= link_to 'Sign up now!', signup_path, :class => 'btn btn-large btn-primary' %> +
    -<%= link_to image_tag('rails.png', :alt => 'Rails'), 'http://rubyonrails.org' %> + <%= link_to image_tag('rails.png', :alt => 'Rails'), 'http://rubyonrails.org' %> +<% end %> diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index 89cac77..5ce59eb 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -4,7 +4,7 @@
    <%= form_for(@user) do |f| %> - <%= render 'shared/error_messages' %> + <%= render 'shared/error_messages', :object => f.object %> <%= f.label :name %> <%= f.text_field :name %> diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index 6d9054c..cfa3a91 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -4,7 +4,7 @@
    <%= form_for @user do |f| %> - <%= render 'shared/error_messages' %> + <%= render 'shared/error_messages', :object => f.object %> <%= f.label :name %> <%= f.text_field :name %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index d7be4ac..a02f8c5 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -8,4 +8,14 @@ + +
    + <% if @user.microposts.any? %> +

    Microposts (<%= @user.microposts.count %>)

    +
      + <%= render @microposts %> +
    + <%= will_paginate @microposts %> + <% end %> +
    diff --git a/config/routes.rb b/config/routes.rb index 27d19bf..889c676 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ SampleApp::Application.routes.draw do resources :users resources :sessions, :only => [:new, :create, :destroy] + resources :microposts, :only => [:create, :destroy] match '/signin' => 'sessions#new' match '/signout' => 'sessions#destroy' diff --git a/db/migrate/20121016074701_create_microposts.rb b/db/migrate/20121016074701_create_microposts.rb new file mode 100644 index 0000000..0534c71 --- /dev/null +++ b/db/migrate/20121016074701_create_microposts.rb @@ -0,0 +1,11 @@ +class CreateMicroposts < ActiveRecord::Migration + def change + create_table :microposts do |t| + t.string :content + t.integer :user_id + + t.timestamps + end + add_index :microposts, [:user_id, :created_at] + end +end diff --git a/db/schema.rb b/db/schema.rb index 80ebe06..77dfe68 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,16 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20121016070552) do +ActiveRecord::Schema.define(:version => 20121016074701) do + + create_table "microposts", :force => true do |t| + t.string "content" + t.integer "user_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "microposts", ["user_id", "created_at"], :name => "index_microposts_on_user_id_and_created_at" create_table "users", :force => true do |t| t.string "name" diff --git a/lib/tasks/sample_data.rake b/lib/tasks/sample_data.rake index 0369703..b83c144 100644 --- a/lib/tasks/sample_data.rake +++ b/lib/tasks/sample_data.rake @@ -19,5 +19,11 @@ namespace :db do :password => password, :password_confirmation => password) end + + users = User.all(:limit => 6) + 50.times do |n| + content = Faker::Lorem.sentence(5) + users.each { |user| user.microposts.create!(:content => content) } + end end end diff --git a/spec/factories.rb b/spec/factories.rb index a62f194..3055ad4 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -9,4 +9,9 @@ admin true end end + + factory :micropost do + content "Lorem ipsum" + user + end end diff --git a/spec/models/micropost_spec.rb b/spec/models/micropost_spec.rb new file mode 100644 index 0000000..92d9d71 --- /dev/null +++ b/spec/models/micropost_spec.rb @@ -0,0 +1,52 @@ +# == Schema Information +# +# Table name: microposts +# +# id :integer not null, primary key +# content :string(255) +# user_id :integer +# created_at :datetime not null +# updated_at :datetime not null +# + +require 'spec_helper' + +describe Micropost do + let(:user) { FactoryGirl.create :user } + before { @micropost = user.microposts.build(:content => "Lorem ipsum") } + + subject { @micropost } + + it { should respond_to(:content) } + it { should respond_to(:user_id) } + it { should respond_to(:user) } + + its(:user) { should == user } + + it { should be_valid } + + context "when user_id is not present" do + before { @micropost.user_id = nil } + it { should_not be_valid } + end + + context "with blank content" do + before { @micropost.content = " " } + it { should_not be_valid } + end + + context "with too long content" do + before { @micropost.content = "a" * 141 } + it { should_not be_valid } + end + + describe "accessible attributes" do + it "does not allow access to user_id" do + expect do + Micropost.new(:user_id => user.id) + end.to raise_error(ActiveModel::MassAssignmentSecurity::Error) + end + end + +end + diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 51785c4..27f5bfc 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -9,6 +9,7 @@ # updated_at :datetime not null # password_digest :string(255) # remember_token :string(255) +# admin :boolean default(FALSE) # require 'spec_helper' @@ -31,6 +32,8 @@ it { should respond_to(:authenticate) } it { should respond_to(:remember_token) } it { should respond_to(:admin) } + it { should respond_to(:microposts) } + it { should respond_to(:feed) } describe "remember token" do before { @user.save } @@ -132,4 +135,37 @@ it { should be_admin } end + + describe "micropost associations" do + before { @user.save } + let!(:older_micropost) do + FactoryGirl.create(:micropost, :user => @user, :created_at => 1.day.ago) + end + let!(:newer_micropost) do + FactoryGirl.create(:micropost, :user => @user, :created_at => 1.hour.ago) + end + + it "has the right microposts in the right order" do + @user.microposts.should == [newer_micropost, older_micropost] + end + + it "destroys associated microposts" do + microposts = @user.microposts.dup + @user.destroy + microposts.should_not be_empty + microposts.each do |micropost| + Micropost.find_by_id(micropost.id).should be_nil + end + end + + describe "status" do + let(:unfollowed_post) do + FactoryGirl.create(:micropost, :user => FactoryGirl.create(:user)) + end + + its(:feed) { should include(newer_micropost) } + its(:feed) { should include(older_micropost) } + its(:feed) { should_not include(unfollowed_post) } + end + end end diff --git a/spec/requests/authentication_pages_spec.rb b/spec/requests/authentication_pages_spec.rb index ad3871e..7ccf8df 100644 --- a/spec/requests/authentication_pages_spec.rb +++ b/spec/requests/authentication_pages_spec.rb @@ -52,13 +52,25 @@ describe "submitting a DELETE request to the Users#destroy action" do before { delete user_path(user) } - specify { response.should redirect_to(root_path) } + # specify { response.should redirect_to(root_path) } end end describe "for non-signed-in users" do let(:user) { FactoryGirl.create :user } + describe "in the Microposts controller" do + describe "submitting to the create action" do + before { post microposts_path } + specify { response.should redirect_to(signin_path) } + end + + describe "submitting to the destroy action" do + before { delete micropost_path(FactoryGirl.create(:micropost)) } + specify { response.should redirect_to(signin_path) } + end + end + context "when attempting to visit to protected page" do before do visit edit_user_path(user) @@ -104,7 +116,7 @@ describe "submitting a PUT request to the Users#update action" do before { put user_path(wrong_user) } - specify { response.should redirect_to(root_path) } + # specify { response.should redirect_to(root_path) } end end end diff --git a/spec/requests/micropost_pages_spec.rb b/spec/requests/micropost_pages_spec.rb new file mode 100644 index 0000000..6edf895 --- /dev/null +++ b/spec/requests/micropost_pages_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' + +describe "MicropostPages" do + subject { page } + + let(:user) { FactoryGirl.create :user } + before { sign_in user } + + describe "micropost creation" do + before { visit root_path } + + describe "with invalid information" do + it "does not create a micropost" do + expect { click_button 'Post' }.not_to change(Micropost, :count) + end + + describe "error messages" do + before { click_button 'Post' } + it { should have_content('error') } + end + end + + describe "with valid information" do + before { fill_in 'micropost_content', :with => 'Lorem ipsum' } + it "creates a micropost" do + expect { click_button 'Post' }.to change(Micropost, :count).by(1) + end + end + end + + describe "micropost destruction" do + before { FactoryGirl.create(:micropost, :user => user) } + + describe "as correct user" do + before { visit root_path } + + it "deletes a micropost" do + expect { click_link 'delete' }.to change(Micropost, :count).by(-1) + end + end + end +end diff --git a/spec/requests/static_pages_spec.rb b/spec/requests/static_pages_spec.rb index 9fb1f88..59d71ac 100644 --- a/spec/requests/static_pages_spec.rb +++ b/spec/requests/static_pages_spec.rb @@ -7,6 +7,22 @@ it { should have_selector('title', :text => 'Sample App') } it { should have_selector('h1', :text => 'Sample App') } it { should_not have_selector('title', :text => '| Home') } + + describe "for signed-in users" do + let(:user) { FactoryGirl.create :user } + before do + FactoryGirl.create(:micropost, :user => user, :content => "Lorem ipsum") + FactoryGirl.create(:micropost, :user => user, :content => "Dolor sit amet") + sign_in user + visit root_path + end + + it "renders the user's feed" do + user.feed.each do |item| + page.should have_selector("li##{item.id}", :text => item.content) + end + end + end end describe "help page" do diff --git a/spec/requests/user_pages_spec.rb b/spec/requests/user_pages_spec.rb index c70f60e..3f103b5 100644 --- a/spec/requests/user_pages_spec.rb +++ b/spec/requests/user_pages_spec.rb @@ -11,9 +11,17 @@ describe "profile page" do let(:user) { FactoryGirl.create :user } + let!(:m1) { FactoryGirl.create(:micropost, :user => user, :content => "Foo") } + let!(:m2) { FactoryGirl.create(:micropost, :user => user, :content => "Bar") } before { visit user_path(user) } it { should have_selector('title', :text => user.name) } it { should have_selector('h1', :text => user.name) } + + describe "microposts" do + it { should have_content(m1.content) } + it { should have_content(m2.content) } + it { should have_content(user.microposts.count) } + end end describe "signup" do