From 3fa72ee185c2adcae0e83fea2c3ee510996f0de7 Mon Sep 17 00:00:00 2001 From: "SHIOYA, Hiromu" Date: Tue, 20 Jan 2015 13:50:15 +0900 Subject: [PATCH 1/8] add and run migration --- db/migrate/20150120053756_user_resume_histories.rb | 11 +++++++++++ db/schema.rb | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20150120053756_user_resume_histories.rb diff --git a/db/migrate/20150120053756_user_resume_histories.rb b/db/migrate/20150120053756_user_resume_histories.rb new file mode 100644 index 0000000..5fbb265 --- /dev/null +++ b/db/migrate/20150120053756_user_resume_histories.rb @@ -0,0 +1,11 @@ +class UserResumeHistories < ActiveRecord::Migration + def change + create_table :user_resume_histories do |t| + t.integer :user_id, null: false + t.text :diff, null: false, default: '' + t.timestamp :updated_at, null: false + end + + add_index :user_resume_histories, [:user_id, :updated_at] + end +end diff --git a/db/schema.rb b/db/schema.rb index a8edd72..02bed71 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,15 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150114120333) do +ActiveRecord::Schema.define(version: 20150120053756) do + + create_table "user_resume_histories", force: :cascade do |t| + t.integer "user_id", null: false + t.text "diff", default: "", null: false + t.datetime "updated_at", null: false + end + + add_index "user_resume_histories", ["user_id", "updated_at"], name: "index_user_resume_histories_on_user_id_and_updated_at" create_table "user_resumes", force: :cascade do |t| t.integer "user_id", null: false From 277128d729d2ac06a3d5c4e27523cf0edb40a318 Mon Sep 17 00:00:00 2001 From: "SHIOYA, Hiromu" Date: Tue, 20 Jan 2015 13:53:15 +0900 Subject: [PATCH 2/8] generate model UserResumeHistory --- app/models/user_resume_history.rb | 2 ++ spec/factories/user_resume_histories.rb | 6 ++++++ spec/models/user_resume_history_spec.rb | 5 +++++ 3 files changed, 13 insertions(+) create mode 100644 app/models/user_resume_history.rb create mode 100644 spec/factories/user_resume_histories.rb create mode 100644 spec/models/user_resume_history_spec.rb diff --git a/app/models/user_resume_history.rb b/app/models/user_resume_history.rb new file mode 100644 index 0000000..041dbf6 --- /dev/null +++ b/app/models/user_resume_history.rb @@ -0,0 +1,2 @@ +class UserResumeHistory < ActiveRecord::Base +end diff --git a/spec/factories/user_resume_histories.rb b/spec/factories/user_resume_histories.rb new file mode 100644 index 0000000..3e5250a --- /dev/null +++ b/spec/factories/user_resume_histories.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :user_resume_history do + + end + +end diff --git a/spec/models/user_resume_history_spec.rb b/spec/models/user_resume_history_spec.rb new file mode 100644 index 0000000..36218bc --- /dev/null +++ b/spec/models/user_resume_history_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe UserResumeHistory, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end From 42f6cce8dd8145649630c8ccfe7826a1ec7fb9c7 Mon Sep 17 00:00:00 2001 From: "SHIOYA, Hiromu" Date: Tue, 20 Jan 2015 14:18:08 +0900 Subject: [PATCH 3/8] add gem 'diffy' --- Gemfile | 1 + Gemfile.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index eca6259..5a3e21f 100644 --- a/Gemfile +++ b/Gemfile @@ -14,6 +14,7 @@ gem 'haml-rails', '~> 0.7.0' gem 'bcrypt', '~> 3.1.9' gem 'username_not_reserved_validator' gem 'qiita-markdown' +gem 'diffy', '~> 3.0.7' gem 'devise', '~> 3.4.1' gem 'omniauth', '~> 1.2.2' gem 'compass-rails', '~> 2.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 71ae71f..37b48ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,6 +81,7 @@ GEM thread_safe (~> 0.1) warden (~> 1.2.3) diff-lcs (1.2.5) + diffy (3.0.7) docile (1.1.5) erb2haml (0.1.5) html2haml @@ -300,6 +301,7 @@ DEPENDENCIES coveralls database_rewinder devise (~> 3.4.1) + diffy (~> 3.0.7) erb2haml factory_girl_rails font-awesome-rails From b3f86cc7265f13d48dc9514b457b63800f86c6e0 Mon Sep 17 00:00:00 2001 From: "SHIOYA, Hiromu" Date: Tue, 20 Jan 2015 14:34:41 +0900 Subject: [PATCH 4/8] implement to record diff --- app/controllers/user_resumes_controller.rb | 2 +- app/models/user_resume.rb | 15 +++++++++++++++ app/models/user_resume_history.rb | 4 ++++ spec/models/user_resume_spec.rb | 13 ++++++++++--- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/app/controllers/user_resumes_controller.rb b/app/controllers/user_resumes_controller.rb index 6743217..b837217 100644 --- a/app/controllers/user_resumes_controller.rb +++ b/app/controllers/user_resumes_controller.rb @@ -26,7 +26,7 @@ def create end def update - @user_resume.update_attributes(body: user_resume_params[:body]) + @user_resume.update_body(user_resume_params[:body]) flash[:notice] = 'Resume updated.' redirect_to home_path(@user.nick) end diff --git a/app/models/user_resume.rb b/app/models/user_resume.rb index be01484..fedfc93 100644 --- a/app/models/user_resume.rb +++ b/app/models/user_resume.rb @@ -3,9 +3,24 @@ class UserResume < ActiveRecord::Base belongs_to :user + after_create :record_first_diff after_save :touch_user def touch_user user.touch end + + def update_body(content) + old_body = self.body.strip + result = update_attributes(body: content.strip) + + if result + UserResumeHistory.record_diff(self.user_id, old_body, self.body) + end + result + end + + def record_first_diff + UserResumeHistory.record_diff(self.user_id, '', self.body) + end end diff --git a/app/models/user_resume_history.rb b/app/models/user_resume_history.rb index 041dbf6..60adc64 100644 --- a/app/models/user_resume_history.rb +++ b/app/models/user_resume_history.rb @@ -1,2 +1,6 @@ class UserResumeHistory < ActiveRecord::Base + def self.record_diff(user_id, old_body, current_body) + diff = Diffy::Diff.new(old_body + "\n", current_body + "\n", context: 3, include_diff_info: true).to_s + create(user_id: user_id, diff: diff) + end end diff --git a/spec/models/user_resume_spec.rb b/spec/models/user_resume_spec.rb index 80521cf..a3034ac 100644 --- a/spec/models/user_resume_spec.rb +++ b/spec/models/user_resume_spec.rb @@ -1,14 +1,19 @@ require 'rails_helper' RSpec.describe UserResume, type: :model do - let(:old_time) { 1.minute.ago.change(usec: 0) } - let(:current_time) { Time.now.change(usec: 0) } describe 'after save' do + let(:old_time) { 1.minute.ago.change(usec: 0) } + let(:current_time) { Time.now.change(usec: 0) } + shared_examples 'touchs user' do specify { expect { subject }.to change { User.first.updated_at }.from(old_time).to(current_time) } end + shared_examples 'record diff' do |count| + specify { expect { subject }.to change { UserResumeHistory.count }.from(count).to(count + 1) } + end + context 'when create' do subject do Timecop.freeze(current_time) do @@ -21,12 +26,13 @@ end include_examples 'touchs user' + include_examples 'record diff', 0 end context 'when update' do subject do Timecop.freeze(current_time) do - User.first.resume.update_attributes(body: 'hogepiyo') + User.first.resume.update_body('hogepiyo') end end @@ -38,6 +44,7 @@ end include_examples 'touchs user' + include_examples 'record diff', 1 end end end From 888982439b59d421882505512444dfe89de52fae Mon Sep 17 00:00:00 2001 From: "SHIOYA, Hiromu" Date: Tue, 20 Jan 2015 15:54:12 +0900 Subject: [PATCH 5/8] specify relation between user and user_resume_histories --- app/models/user.rb | 1 + app/models/user_resume_history.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 912ec2b..b6ba366 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -10,6 +10,7 @@ class User < ActiveRecord::Base has_one :resume, class_name: 'UserResume' has_many :user_taggings has_many :tags, through: :user_taggings, class_name: 'UserTag' + has_many :resume_histories, class_name: 'UserResumeHistory' devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable, :omniauthable diff --git a/app/models/user_resume_history.rb b/app/models/user_resume_history.rb index 60adc64..b5b7033 100644 --- a/app/models/user_resume_history.rb +++ b/app/models/user_resume_history.rb @@ -1,4 +1,6 @@ class UserResumeHistory < ActiveRecord::Base + belongs_to :user + def self.record_diff(user_id, old_body, current_body) diff = Diffy::Diff.new(old_body + "\n", current_body + "\n", context: 3, include_diff_info: true).to_s create(user_id: user_id, diff: diff) From fed6b8077535b0c175ecb1791061e411f5f318fe Mon Sep 17 00:00:00 2001 From: "SHIOYA, Hiromu" Date: Tue, 20 Jan 2015 18:16:32 +0900 Subject: [PATCH 6/8] unify new line and strip resume body --- app/models/user_resume.rb | 17 ++++++++++++++--- spec/models/user_resume_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/models/user_resume.rb b/app/models/user_resume.rb index fedfc93..f9fb5d6 100644 --- a/app/models/user_resume.rb +++ b/app/models/user_resume.rb @@ -3,6 +3,8 @@ class UserResume < ActiveRecord::Base belongs_to :user + before_create :prepare_body + after_create :record_first_diff after_save :touch_user @@ -11,11 +13,12 @@ def touch_user end def update_body(content) - old_body = self.body.strip - result = update_attributes(body: content.strip) + old_body = self.body + new_body = self.class.strip_and_unify_newline(content) + result = update_attributes(body: new_body) if result - UserResumeHistory.record_diff(self.user_id, old_body, self.body) + UserResumeHistory.record_diff(self.user_id, old_body, new_body) end result end @@ -23,4 +26,12 @@ def update_body(content) def record_first_diff UserResumeHistory.record_diff(self.user_id, '', self.body) end + + def self.strip_and_unify_newline(str) + str.strip.gsub(/\r\n|\r|\n/, "\n") + end + + def prepare_body + self.body = self.class.strip_and_unify_newline(self.body) + end end diff --git a/spec/models/user_resume_spec.rb b/spec/models/user_resume_spec.rb index a3034ac..1ebbc88 100644 --- a/spec/models/user_resume_spec.rb +++ b/spec/models/user_resume_spec.rb @@ -1,6 +1,26 @@ require 'rails_helper' RSpec.describe UserResume, type: :model do + describe 'unify newline and strip' do + let(:user) { create(:user) } + let(:original_body) { " new\r\nline\rwith\nblank\t" } + let(:saved_body) { "new\nline\nwith\nblank" } + + context 'when create' do + before { described_class.create(user_id: user.id, body: original_body) } + it 'strips and unifies new line' do + expect(user.resume.body).to eq saved_body + end + end + + context 'when update' do + let(:old_body) { 'old_body' } + before { described_class.create(user_id: user.id, body: old_body) } + it 'strips and unifies new line' do + expect { user.resume.update_body(original_body) }.to change { user.resume.body }.from(old_body).to(saved_body) + end + end + end describe 'after save' do let(:old_time) { 1.minute.ago.change(usec: 0) } From fb57a8f7575fa7e737f0de1d5f7662549cd3a850 Mon Sep 17 00:00:00 2001 From: "SHIOYA, Hiromu" Date: Wed, 21 Jan 2015 20:16:22 +0900 Subject: [PATCH 7/8] implement to show edit histories of user's resume --- .../javascripts/resume_histories.js.coffee | 10 +++++ app/assets/stylesheets/users/profile.css.scss | 28 ++++++++++++++ app/controllers/users_controller.rb | 24 ++++++++---- app/models/user_resume_history.rb | 10 +++++ app/views/users/resume_histories.html.haml | 38 +++++++++++++++++++ app/views/users/show.html.haml | 4 ++ config/routes.rb | 7 +++- spec/controllers/users_controller_spec.rb | 23 +++++++++++ 8 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 app/assets/javascripts/resume_histories.js.coffee create mode 100644 app/views/users/resume_histories.html.haml diff --git a/app/assets/javascripts/resume_histories.js.coffee b/app/assets/javascripts/resume_histories.js.coffee new file mode 100644 index 0000000..894b39a --- /dev/null +++ b/app/assets/javascripts/resume_histories.js.coffee @@ -0,0 +1,10 @@ +($ -> + showResumeHistoryDiff = (e) -> + e.preventDefault() + history = $(this).closest('tr').find('.history_data').data('history') + $('.edit_history_detail_container').html(history) + + $('.show_resume_history').each((idx, elem) -> + $(elem).bind('click', showResumeHistoryDiff) + ) +) diff --git a/app/assets/stylesheets/users/profile.css.scss b/app/assets/stylesheets/users/profile.css.scss index 6538535..767e2ae 100755 --- a/app/assets/stylesheets/users/profile.css.scss +++ b/app/assets/stylesheets/users/profile.css.scss @@ -50,6 +50,34 @@ table.profile { } } +.edit_histories { + @include clearfix; + width: 100%; + + .edit_history_list { + float: left; + width: 25%; + table { + width: 90%; + margin: auto; + } + } + + .edit_history_detail { + float: left; + width: 75%; + .edit_history_detail_container { + padding-left: 16px; + height: 25em; + overflow-y: auto; + } + } +} + +ul.pure-paginator { + margin-bottom: 32px; +} + form.resume_form { textarea { width: 80%; diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 3d7ab10..5c643cc 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,8 @@ class UsersController < ApplicationController - MEMBERS_PER_PAGE = 10 + MEMBERS_PER_PAGE = 10 + HISTORIES_PER_PAGE = 10 + + before_action :prepare_user, only: [:show, :resume_histories, :resume_history] layout 'users' @@ -10,12 +13,6 @@ def index end def show - if request.path_info.start_with?('/users') - @user = current_user - else - @user = User.find_by(nick: params[:nick]) - end - raise ActiveRecord::RecordNotFound unless @user end def list @@ -28,7 +25,20 @@ def list @members = relation.page(params[:page]).per(MEMBERS_PER_PAGE) end + def resume_histories + @histories = @user.resume_histories.order(:updated_at).reverse_order.page(params[:page]).per(HISTORIES_PER_PAGE) + end + def after_edit redirect_to home_path(current_user.nick) end + + def prepare_user + if request.path_info.start_with?('/users') + @user = current_user + else + @user = User.find_by(nick: params[:nick]) + end + raise ActiveRecord::RecordNotFound unless @user + end end diff --git a/app/models/user_resume_history.rb b/app/models/user_resume_history.rb index b5b7033..d68cf87 100644 --- a/app/models/user_resume_history.rb +++ b/app/models/user_resume_history.rb @@ -1,8 +1,18 @@ class UserResumeHistory < ActiveRecord::Base + include Renderable + belongs_to :user def self.record_diff(user_id, old_body, current_body) diff = Diffy::Diff.new(old_body + "\n", current_body + "\n", context: 3, include_diff_info: true).to_s create(user_id: user_id, diff: diff) end + + def quoted_diff + <<-EOD +```diff +#{self.diff} +``` + EOD + end end diff --git a/app/views/users/resume_histories.html.haml b/app/views/users/resume_histories.html.haml new file mode 100644 index 0000000..de5296c --- /dev/null +++ b/app/views/users/resume_histories.html.haml @@ -0,0 +1,38 @@ += content_for :header do + javascript_include_tag 'resume_histories' + +%div.content.pure-u-1.pure-u-md-3-4 + %div + %div.posts + %h1.content-subhead + %section.post + %header.post-header + %h2 + %i.fa.fa-files-o.fa-lg + Edit Histories + %div.post-description + %div.edit_histories + %div.edit_history_list + %table.pure-table.pure-table-bordered + - @histories.each_with_index do |history, idx| + %tbody + %tr{ class: idx.odd? ? 'pure-table-odd' : '' } + %td.history_data{ data: { history: history.render(:quoted_diff)} } + = history.updated_at + %td + = link_to '#', class: 'show_resume_history' do + %i.fa.fa-file-text-o.fa-lg + %div.edit_history_detail + %div.edit_history_detail_container + = paginate @histories + %div.posts + %h1.content-subhead + %section.post + %header.post-header + %h2 + %i.fa.fa-file-text.fa-lg + Resume + %div.post-description + %div.resume + - if @user.resume.present? + = @user.resume.render(:body).html_safe diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index e53f3d8..084d2e3 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -64,3 +64,7 @@ %div.resume - if @user.resume.present? = @user.resume.render(:body).html_safe + - if @user.resume_histories.present? + = link_to user_resume_histories_path do + %i.fa.fa-files-o.fa-lg + Edit Histories diff --git a/config/routes.rb b/config/routes.rb index dc7bb66..3eefcde 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -8,16 +8,21 @@ namespace :users do get :list - resource :user_resumes, controller: '/user_resumes', as: :resume, path: :resume + resource :user_resumes, controller: '/user_resumes', as: :resume, path: :resume, only: [:new, :edit, :create, :update] end resources :user_tags, only: [:index, :show] scope ':nick' do get '/', controller: :users, action: :show, as: :home + scope :user_tags, controller: :user_tags, as: :user_tag, path: :tag do post :attach delete :detach end + + scope :resume_histories, controller: :users, as: :user_resume_histories, path: :resume_histories do + get '/', action: :resume_histories + end end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index bcb833c..876e034 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -71,4 +71,27 @@ it { expect { get :show, nick: 'NOT_EXISTING_USER' }.to raise_error ActiveRecord::RecordNotFound } end end + + describe '#resume_histories' do + let(:user) { create(:user) } + subject(:resume_histories) { get :resume_histories, nick: user.nick } + + context 'without resume' do + it 'shows blank page' do + expect(resume_histories).to be_ok + expect(user.resume).to_not be_present + expect(user.resume_histories).to_not be_present + end + end + + context 'with resume and histories' do + let!(:resume) { create(:user_resume, user: user) } + before { resume } + it 'shows resume and histories' do + expect(resume_histories).to be_ok + expect(user.resume).to be_present + expect(user.resume_histories).to be_present + end + end + end end From cd102db81d32a432e95ecc45e9c155fe30e308a4 Mon Sep 17 00:00:00 2001 From: "SHIOYA, Hiromu" Date: Wed, 21 Jan 2015 20:26:17 +0900 Subject: [PATCH 8/8] add spec for model of user_resume_history --- spec/models/user_resume_history_spec.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/spec/models/user_resume_history_spec.rb b/spec/models/user_resume_history_spec.rb index 36218bc..ccaf6bc 100644 --- a/spec/models/user_resume_history_spec.rb +++ b/spec/models/user_resume_history_spec.rb @@ -1,5 +1,17 @@ require 'rails_helper' -RSpec.describe UserResumeHistory, :type => :model do - pending "add some examples to (or delete) #{__FILE__}" +RSpec.describe UserResumeHistory, type: :model do + let(:user) { create(:user) } + + describe '.record_diff' do + subject(:record_diff) { described_class.record_diff(user.id, 'old_body', 'new_body') } + specify { expect { record_diff }.to change { described_class.count }.from(0).to(1) } + end + + describe '#quoted_diff' do + let(:diff_body) { 'diff_body' } + let(:quoted_diff_body) { "```diff\n#{diff_body}\n```\n" } + let(:diff) { build(:user_resume_history, diff: diff_body) } + specify { expect(diff.quoted_diff).to eq quoted_diff_body } + end end