|
|
|
必見!Forkwell松田明が
出題するRails課題の正解発表 |
||
2012年12月19日、「Forkwell」と「CodeIQ」は、Railsコミッターである松田明さん監修の下、「実践 Rails コードチャレンジ」というイベントを開催した。そこで披露された松田さんの模範解答を紹介する。
(取材・文/中村仁美 総研スタッフ/宮みゆき)作成日:13.02.01
|
Railsのスキルアップに加え、オープンソースの作法も学べる
エンジニアやクリエイターが情報を発信、収集、議論するためのネットワークサイト「Forkwell」とITエンジニア向けの実務スキル評価サービス「CodeIQ」。2012年12月19日、このエンジニアのためのサービスを提供する両社が手を組み、初めて開催したのが「実践 Rails コードチャレンジ」である。
Railsのコミッターである松田明さんを擁するForkwellの開発チームが、実際のForkwellコードを使用したRailsの課題に関する問題を作成。これらの問題をイベントの1週間前からCodeIQで公開し、参加者を募集した。課題問題は3問あり、Railsの基本編から応用編、4.0先取り編で構成されていたため、参加者も初心者からベテランまで、幅広いスキルの人が集まった。 当日は参加者が一緒にコーディングした後、コードレビューし合ったり、松田さんからの解答解説やフィードバックが行われた。正解を出した人にはTシャツやパーカーなどのグッズのプレゼントがもらえるなど、楽しみながらスキルアップができ、かつGitHubを使ってパッチを送るなど、オープンソースの作法も学べるとあり、有意義な時間が過ごせたようだ。 |
松田 明さん
|
<問1.Railsの基本編> Forkwellの「読んだ・書いた」の一覧ページを高速化しましょう
基本編はRails定番のN+1問題。RailsのActiveRecordは便利だが、非効率なデータベースクエリを発生させることもある。そうならないようコーディングするにはどのようにすればいいかを考えるのがこの問題の趣旨。問題は以下のとおりだ。
○設問
Forkwellの「読んだ・書いた」の一覧ページを高速化しましょう。
読んだ・書いたのページで、それぞれの記事の「読んだよ!」の総数や、記事についたコメントの総数も一緒に表示する、という仕様だったとします。
https://forkwell.com/publications
何も考えずにコードを書いたところ、以下のようになりました。
https://github.com/CodeIQ/forkwell/tree/master/question_1
しかし、このコードだとレコード数が増えれば増えるほど、SQLの発行数が増える、いわゆる『N+1問題』になってしまいます。このコードを、Ruby on Railsが持つ機能を利用してそれを解決することになりました。
このコードを、SQLの発行回数ができるだけ少なくなるようにリファクタリングしてください。
※解答は、git format-patchで書き出したテキストファイルを解答画面からアップロードしてください。
○松田さんの模範解答
[PATCH 1/4] [A1] r g migration add_counter_caches_to_publications
concerns_count:integer publication_comments_count:integer
---
.../db/migrate/20121219064244_add_counter_caches_to_publications.rb | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 question_1/db/migrate/20121219064244_add_counter_caches_to_publications.rb
diff --git a/question_1/db/migrate/20121219064244_add_counter_caches_to_publications.rb b/question_1/db/migrate/20121219064244_add_counter_caches_to_publications.rb
new file mode 100644
index 0000000..ea38057
--- /dev/null
+++ b/question_1/db/migrate/20121219064244_add_counter_caches_to_publications.rb
@@ -0,0 +1,6 @@
+class AddCounterCachesToPublications < ActiveRecord::Migration
+ def change
+ add_column :publications, :concerns_count, :integer
+ add_column :publications, :publication_comments_count, :integer
+ end
+end
--
1.8.0.2
[PATCH 2/4] [A1] enable counter_caches && set default counter_cache
values
---
question_1/app/models/concern.rb | 2 +-
question_1/app/models/publication_comment.rb | 2 +-
.../migrate/20121219064244_add_counter_caches_to_publications.rb | 8 ++++++--
3 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/question_1/app/models/concern.rb b/question_1/app/models/concern.rb
index 3bd4237..3c0af16 100644
--- a/question_1/app/models/concern.rb
+++ b/question_1/app/models/concern.rb
@@ -1,4 +1,4 @@
class Concern < ActiveRecord::Base
- belongs_to :publication
+ belongs_to :publication, counter_cache: true
# attr_accessible :title, :body
end
diff --git a/question_1/app/models/publication_comment.rb b/question_1/app/models/publication_comment.rb
index 5f3d5f8..46ed8da 100644
--- a/question_1/app/models/publication_comment.rb
+++ b/question_1/app/models/publication_comment.rb
@@ -1,4 +1,4 @@
class PublicationComment < ActiveRecord::Base
- belongs_to :publication
+ belongs_to :publication, counter_cache: true
attr_accessible :body
end
diff --git a/question_1/db/migrate/20121219064244_add_counter_caches_to_publications.rb b/question_1/db/migrate/20121219064244_add_counter_caches_to_publications.rb
index ea38057..7fedd2b 100644
--- a/question_1/db/migrate/20121219064244_add_counter_caches_to_publications.rb
+++ b/question_1/db/migrate/20121219064244_add_counter_caches_to_publications.rb
@@ -1,6 +1,10 @@
class AddCounterCachesToPublications < ActiveRecord::Migration
def change
- add_column :publications, :concerns_count, :integer
- add_column :publications, :publication_comments_count, :integer
+ add_column :publications, :concerns_count, :integer, default: 0
+ add_column :publications, :publication_comments_count, :integer, default: 0
+
+ Publication.find_each do |pub|
+ Publication.reset_counters pub.id, :concerns, :publication_comments
+ end
end
end
--
1.8.0.2
[PATCH 3/4] [A1] rake db:migrate
---
question_1/db/schema.rb | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/question_1/db/schema.rb b/question_1/db/schema.rb
index 5fce505..56372b6 100644
--- a/question_1/db/schema.rb
+++ b/question_1/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20121207103317) do
+ActiveRecord::Schema.define(:version => 20121219064244) do
create_table "concerns", :force => true do |t|
t.integer "publication_id"
@@ -33,8 +33,10 @@ ActiveRecord::Schema.define(:version => 20121207103317) do
create_table "publications", :force => true do |t|
t.string "title"
t.string "url"
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.integer "concerns_count", :default => 0
+ t.integer "publication_comments_count", :default => 0
end
end
--
1.8.0.2
[PATCH 2/4] [A1] enable counter_caches && set default counter_cache
values
---
question_1/app/views/publications/index.html.haml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/question_1/app/views/publications/index.html.haml b/question_1/app/views/publications/index.html.haml
index 28d2aeb..59b64fb 100644
--- a/question_1/app/views/publications/index.html.haml
+++ b/question_1/app/views/publications/index.html.haml
@@ -1,3 +1,3 @@
- @publications.each do |pub|
- = pub.concerns.count
- = pub.publication_comments.count
+ = pub.concerns.size
+ = pub.publication_comments.size
--
1.8.0.2
○解説
ポイントは関連名_カウントというinteger型カラムを作ること、デフォルト値を0にする、Publicationの中でちゃんといまのカウントの値を入れておくこと。こうすればSQLのクエリは1回しか発生しない。
この問題は参加者から模範解答が出された。また参加者の中には「(:concerns, :publication_comments)をincludesし、countメソッドをlengthメソッドに変えるとクエリが減る」という解答もあった。これに対して松田さんは「クエリを減らすという問題からすると正解ともいえるが、それだとconcernsのインスタンス全部とpublication_commentsインスタンスを全部取ってくるので、これが1000件紐づいていると1000個インスタンスが作られてしまい、すごくメモリを使う可能性がある。この方法は実アプリでは使えない可能性がある」と指摘した。
<問2.Railsの応用編> Forkwellをレールに乗せましょう
Forkwellでも実際に発生した、Webアプリケーション開発の現場でありがちな生々しい問題を散りばめた問題。複数のわながあり、それをいかに見つけ、Railsらしいプログラムに仕上げるかが試された。問題は以下のとおりだ。
○設問
Ruby on Railsは開発効率がとても良いフレームワークですが、一度そのレールから外れると途端にメンテナンス性の悪いものになってしまいます。そのため世のRailsエンジニアは、なるべくRuby on Railsが敷いたレールに沿って開発を進めようとします。
しかし、開発を進めていく上で、どうしてもコードに負債を残していってしまうことはよくあります。今回問題になっているRailsアプリも、コードに大量の負債をかかえてしまいました。
https://github.com/CodeIQ/forkwell/tree/master/question_2
メンテナンス性を高めるために、この脱線したコードをRuby on Rails「らしい」コードにリファクタリングしましょう。
※解答は、git format-patchで書き出したテキストファイルを解答画面からアップロードしてください。
○松田さんの模範解答
[PATCH 01/24] [A2] rspec-rails should be in :development, :test group
---
question_2/Gemfile | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/question_2/Gemfile b/question_2/Gemfile
index 93df0c6..699d8f4 100644
--- a/question_2/Gemfile
+++ b/question_2/Gemfile
@@ -41,7 +41,10 @@ gem 'kaminari'
# gem 'debugger'
group :test do
- gem 'rspec-rails'
gem 'capybara'
gem 'rr'
end
+
+group :development, :test do
+ gem 'rspec-rails'
+end
--
1.8.0.2
[PATCH 02/24] [A2] spec/requests => spec/features
---
.../user_can_add_himself_as_author_spec.rb | 54 ++++++++++++++++++++++
.../user_can_add_skilltag_to_himself_spec.rb | 48 +++++++++++++++++++
.../user_can_add_skilltag_to_publication_spec.rb | 36 +++++++++++++++
.../features/user_can_register_publication_spec.rb | 31 +++++++++++++
.../user_can_see_list_of_publications_spec.rb | 31 +++++++++++++
.../features/user_can_see_skilltag_detail_spec.rb | 33 +++++++++++++
.../user_can_add_himself_as_author_spec.rb | 54 ----------------------
.../user_can_add_skilltag_to_himself_spec.rb | 48 -------------------
.../user_can_add_skilltag_to_publication_spec.rb | 36 ---------------
.../requests/user_can_register_publication_spec.rb | 31 -------------
.../user_can_see_list_of_publications_spec.rb | 31 -------------
.../requests/user_can_see_skilltag_detail_spec.rb | 33 -------------
12 files changed, 233 insertions(+), 233 deletions(-)
create mode 100644 question_2/spec/features/user_can_add_himself_as_author_spec.rb
create mode 100644 question_2/spec/features/user_can_add_skilltag_to_himself_spec.rb
create mode 100644 question_2/spec/features/user_can_add_skilltag_to_publication_spec.rb
create mode 100644 question_2/spec/features/user_can_register_publication_spec.rb
create mode 100644 question_2/spec/features/user_can_see_list_of_publications_spec.rb
create mode 100644 question_2/spec/features/user_can_see_skilltag_detail_spec.rb
delete mode 100644 question_2/spec/requests/user_can_add_himself_as_author_spec.rb
delete mode 100644 question_2/spec/requests/user_can_add_skilltag_to_himself_spec.rb
delete mode 100644 question_2/spec/requests/user_can_add_skilltag_to_publication_spec.rb
delete mode 100644 question_2/spec/requests/user_can_register_publication_spec.rb
delete mode 100644 question_2/spec/requests/user_can_see_list_of_publications_spec.rb
delete mode 100644 question_2/spec/requests/user_can_see_skilltag_detail_spec.rb
diff --git a/question_2/spec/features/user_can_add_himself_as_author_spec.rb b/question_2/spec/features/user_can_add_himself_as_author_spec.rb
new file mode 100644
index 0000000..3144733
--- /dev/null
+++ b/question_2/spec/features/user_can_add_himself_as_author_spec.rb
@@ -0,0 +1,54 @@
+# coding: utf-8
+require 'spec_helper'
+
+feature 'ユーザーとして、自分を書いたものの著者として登録できる' do
+ let(:user) { User.create! username: 'johndoe', first_name: 'John', last_name: 'Doe', email: 'john@example.com' }
+ let!(:publication) { Publication.create! title: 'テスト投稿', url: 'http://example.com' }
+
+ context '非ログイン時' do
+ scenario '書いたものの詳細画面で、著者登録用のリンクが表示されないこと' do
+ visit root_path
+ click_link '一覧へ'
+
+ click_link publication.title
+
+ page.should have_no_link '自分を著者に登録'
+ end
+ end
+
+ context 'ログイン時' do
+ background do
+ any_instance_of(ApplicationController) do |controller|
+ stub(controller).current_user { user }
+ end
+ end
+
+ context '登録していない場合' do
+ scenario '書いたものの詳細画面で、著者登録用のリンクが表示され、自分を著者に登録できること' do
+ visit root_path
+ click_link '一覧へ'
+
+ click_link publication.title
+
+ click_link '自分を著者に登録'
+
+ page.should have_content user.username
+ end
+ end
+
+ context '登録済みの場合' do
+ background do
+ publication.users << user
+ end
+
+ scenario '書いたものの詳細画面で、著者登録用のリンクが表示されないこと' do
+ visit root_path
+ click_link '一覧へ'
+
+ click_link publication.title
+
+ page.should have_no_link '自分を著者に登録'
+ end
+ end
+ end
+end
diff --git a/question_2/spec/features/user_can_add_skilltag_to_himself_spec.rb b/question_2/spec/features/user_can_add_skilltag_to_himself_spec.rb
new file mode 100644
index 0000000..3e7dc3c
--- /dev/null
+++ b/question_2/spec/features/user_can_add_skilltag_to_himself_spec.rb
@@ -0,0 +1,48 @@
+# coding: utf-8
+require 'spec_helper'
+
+feature 'ユーザーとして自分にスキルタグを追加できる' do
+ let!(:user) { User.create! username: 'johndoe', first_name: 'John', last_name: 'Doe', email: 'john@example.com' }
+
+ context '非ログイン時' do
+ scenario '自分のページでスキルタグの追加フォームが表示されないこと' do
+ visit user_path user
+ page.should have_no_button '登録する'
+ end
+ end
+
+ context 'ログイン時' do
+ background do
+ any_instance_of(ApplicationController) do |controller|
+ stub(controller).current_user { user }
+ end
+ end
+
+ context 'すでに登録済みのスキルタグの場合' do
+ background do
+ user.skilltags << Skilltag.create!(name: 'ruby')
+ end
+
+ scenario '登録しようとするとエラーメッセージとともにリダイレクトされること' do
+ visit user_path user
+ fill_in 'skilltag_name', with: 'ruby'
+ click_button '登録する'
+
+ current_path.should eq user_path(user)
+ page.should have_content 'ruby'
+ page.should have_content 'すでに登録済みです'
+ end
+ end
+
+ context 'まだ登録していないスキルタグの場合' do
+ scenario '自分のページでスキルタグの追加フォームにスキルタグを入力し、追加できること' do
+ visit user_path user
+ fill_in 'skilltag_name', with: 'ruby'
+ click_button '登録する'
+
+ current_path.should eq user_path(user)
+ page.should have_content 'ruby'
+ end
+ end
+ end
+end
diff --git a/question_2/spec/features/user_can_add_skilltag_to_publication_spec.rb b/question_2/spec/features/user_can_add_skilltag_to_publication_spec.rb
new file mode 100644
index 0000000..0d09ca5
--- /dev/null
+++ b/question_2/spec/features/user_can_add_skilltag_to_publication_spec.rb
@@ -0,0 +1,36 @@
+# coding: utf-8
+require 'spec_helper'
+
+feature 'ユーザーとして、書いたものにスキルタグを登録できる' do
+ let(:user) { User.create! username: 'johndoe', first_name: 'John', last_name: 'Doe', email: 'john@example.com' }
+ let!(:publication) { Publication.create! title: 'テスト投稿', url: 'http://example.com' }
+
+ context '非ログイン時' do
+ scenario '登録フォームが表示されないこと' do
+ visit root_path
+ click_link '一覧へ'
+ click_link publication.title
+
+ page.should have_no_button 'スキルタグ登録する'
+ end
+ end
+
+ context 'ログイン時' do
+ background do
+ any_instance_of(ApplicationController) do |controller|
+ stub(controller).current_user { user }
+ end
+ end
+
+ scenario '登録フォームが表示され、スキルタグを登録できること' do
+ visit root_path
+ click_link '一覧へ'
+ click_link publication.title
+
+ fill_in 'skilltag', with: 'ruby'
+ click_button 'スキルタグ登録する'
+
+ page.should have_content 'ruby'
+ end
+ end
+end
diff --git a/question_2/spec/features/user_can_register_publication_spec.rb b/question_2/spec/features/user_can_register_publication_spec.rb
new file mode 100644
index 0000000..9960b42
--- /dev/null
+++ b/question_2/spec/features/user_can_register_publication_spec.rb
@@ -0,0 +1,31 @@
+# coding: utf-8
+require 'spec_helper'
+
+feature 'ユーザーとして、書いたものを登録できる' do
+ let(:user) { User.create! username: 'johndoe', first_name: 'John', last_name: 'Doe', email: 'john@example.com' }
+
+ background do
+ any_instance_of(ApplicationController) do |controller|
+ stub(controller).current_user { user }
+ end
+ end
+
+ scenario '書いたもののタイトルとURLを入力して、登録できる' do
+ title = 'タイトル'
+ scheme = 'http://'
+ url = 'example.com'
+
+ visit new_publication_path
+
+ fill_in 'Title', with: title
+ fill_in 'Url', with: "#{scheme}#{url}"
+
+ click_button 'Save'
+
+ current_path.should eq publication_path(Publication.last)
+
+ page.should have_content title
+ page.should have_link url
+ page.should_not have_content scheme
+ end
+end
diff --git a/question_2/spec/features/user_can_see_list_of_publications_spec.rb b/question_2/spec/features/user_can_see_list_of_publications_spec.rb
new file mode 100644
index 0000000..eac4c13
--- /dev/null
+++ b/question_2/spec/features/user_can_see_list_of_publications_spec.rb
@@ -0,0 +1,31 @@
+# coding: utf-8
+require 'spec_helper'
+
+feature 'ユーザーとして、「書いたもの」の一覧を閲覧できる' do
+ background do
+ 10.times.map.with_index(1) { |i| Publication.create! title: "テスト #{i+1}", url: "http://example.com/test#{i+1}" }
+ end
+
+ scenario 'トップページから「書いたもの」の一覧ページに遷移することができる' do
+ visit root_path
+ click_link '一覧へ'
+
+ all('.publication').count.should == 10
+
+ Publication.all.each do |pub|
+ page.should have_content pub.title
+ end
+ end
+
+ scenario 'トップページから「書いたもの」の一覧ページに遷移し、次のページに遷移することができる' do
+ 2.times.map.with_index(11) { |i| Publication.create! title: "テスト #{i+1}", url: "http://example.com/test#{i+1}" }
+ visit root_path
+ click_link '一覧へ'
+
+ all('.publication').count.should == 10
+
+ click_link 'Next'
+
+ all('.publication').count.should == 2
+ end
+end
diff --git a/question_2/spec/features/user_can_see_skilltag_detail_spec.rb b/question_2/spec/features/user_can_see_skilltag_detail_spec.rb
new file mode 100644
index 0000000..a730725
--- /dev/null
+++ b/question_2/spec/features/user_can_see_skilltag_detail_spec.rb
@@ -0,0 +1,33 @@
+# coding: utf-8
+require 'spec_helper'
+
+feature 'ユーザーとして、スキルタグの詳細情報を閲覧できる' do
+ let(:ruby) { Skilltag.create! name: 'ruby' }
+ let(:users) { 10.times.map.with_index(1) { |i| User.create! username: "johndoe_#{i}", first_name: "John#{i}", last_name: "Doe#{i}", email: "john_#{i}@example.com" } }
+ let(:publications) { 10.times.map.with_index(1) { |i| Publication.create! title: "テスト投稿 その#{i}", url: "http://example.com/test_#{i}" } }
+
+ background do
+ users[0..2].map {|u| u.skilltags << ruby }
+ publications[0..2].map {|pub| pub.skilltags << ruby }
+ end
+
+ scenario 'スキルタグの詳細ページで、スキルタグに関連する書いたものの一覧を閲覧できること' do
+ visit skilltag_path(name: ruby.name)
+
+ ruby.publications.each do |pub|
+ page.should have_content pub.title
+ end
+ page.should have_no_link '書いたもの'
+ end
+
+ scenario 'スキルタグの詳細ページで、スキルタグを自分に登録しているユーザーの一覧を閲覧できること' do
+ visit skilltag_path(name: ruby.name)
+ click_link 'ユーザー'
+
+ ruby.users.each do |user|
+ page.should have_content user.first_name
+ page.should have_content user.last_name
+ end
+ page.should have_no_link 'ユーザー'
+ end
+end
diff --git a/question_2/spec/requests/user_can_add_himself_as_author_spec.rb b/question_2/spec/requests/user_can_add_himself_as_author_spec.rb
deleted file mode 100644
index 3144733..0000000
--- a/question_2/spec/requests/user_can_add_himself_as_author_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# coding: utf-8
-require 'spec_helper'
-
-feature 'ユーザーとして、自分を書いたものの著者として登録できる' do
- let(:user) { User.create! username: 'johndoe', first_name: 'John', last_name: 'Doe', email: 'john@example.com' }
- let!(:publication) { Publication.create! title: 'テスト投稿', url: 'http://example.com' }
-
- context '非ログイン時' do
- scenario '書いたものの詳細画面で、著者登録用のリンクが表示されないこと' do
- visit root_path
- click_link '一覧へ'
-
- click_link publication.title
-
- page.should have_no_link '自分を著者に登録'
- end
- end
-
- context 'ログイン時' do
- background do
- any_instance_of(ApplicationController) do |controller|
- stub(controller).current_user { user }
- end
- end
-
- context '登録していない場合' do
- scenario '書いたものの詳細画面で、著者登録用のリンクが表示され、自分を著者に登録できること' do
- visit root_path
- click_link '一覧へ'
-
- click_link publication.title
-
- click_link '自分を著者に登録'
-
- page.should have_content user.username
- end
- end
-
- context '登録済みの場合' do
- background do
- publication.users << user
- end
-
- scenario '書いたものの詳細画面で、著者登録用のリンクが表示されないこと' do
- visit root_path
- click_link '一覧へ'
-
- click_link publication.title
-
- page.should have_no_link '自分を著者に登録'
- end
- end
- end
-end
diff --git a/question_2/spec/requests/user_can_add_skilltag_to_himself_spec.rb b/question_2/spec/requests/user_can_add_skilltag_to_himself_spec.rb
deleted file mode 100644
index 3e7dc3c..0000000
--- a/question_2/spec/requests/user_can_add_skilltag_to_himself_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# coding: utf-8
-require 'spec_helper'
-
-feature 'ユーザーとして自分にスキルタグを追加できる' do
- let!(:user) { User.create! username: 'johndoe', first_name: 'John', last_name: 'Doe', email: 'john@example.com' }
-
- context '非ログイン時' do
- scenario '自分のページでスキルタグの追加フォームが表示されないこと' do
- visit user_path user
- page.should have_no_button '登録する'
- end
- end
-
- context 'ログイン時' do
- background do
- any_instance_of(ApplicationController) do |controller|
- stub(controller).current_user { user }
- end
- end
-
- context 'すでに登録済みのスキルタグの場合' do
- background do
- user.skilltags << Skilltag.create!(name: 'ruby')
- end
-
- scenario '登録しようとするとエラーメッセージとともにリダイレクトされること' do
- visit user_path user
- fill_in 'skilltag_name', with: 'ruby'
- click_button '登録する'
-
- current_path.should eq user_path(user)
- page.should have_content 'ruby'
- page.should have_content 'すでに登録済みです'
- end
- end
-
- context 'まだ登録していないスキルタグの場合' do
- scenario '自分のページでスキルタグの追加フォームにスキルタグを入力し、追加できること' do
- visit user_path user
- fill_in 'skilltag_name', with: 'ruby'
- click_button '登録する'
-
- current_path.should eq user_path(user)
- page.should have_content 'ruby'
- end
- end
- end
-end
diff --git a/question_2/spec/requests/user_can_add_skilltag_to_publication_spec.rb b/question_2/spec/requests/user_can_add_skilltag_to_publication_spec.rb
deleted file mode 100644
index 0d09ca5..0000000
--- a/question_2/spec/requests/user_can_add_skilltag_to_publication_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# coding: utf-8
-require 'spec_helper'
-
-feature 'ユーザーとして、書いたものにスキルタグを登録できる' do
- let(:user) { User.create! username: 'johndoe', first_name: 'John', last_name: 'Doe', email: 'john@example.com' }
- let!(:publication) { Publication.create! title: 'テスト投稿', url: 'http://example.com' }
-
- context '非ログイン時' do
- scenario '登録フォームが表示されないこと' do
- visit root_path
- click_link '一覧へ'
- click_link publication.title
-
- page.should have_no_button 'スキルタグ登録する'
- end
- end
-
- context 'ログイン時' do
- background do
- any_instance_of(ApplicationController) do |controller|
- stub(controller).current_user { user }
- end
- end
-
- scenario '登録フォームが表示され、スキルタグを登録できること' do
- visit root_path
- click_link '一覧へ'
- click_link publication.title
-
- fill_in 'skilltag', with: 'ruby'
- click_button 'スキルタグ登録する'
-
- page.should have_content 'ruby'
- end
- end
-end
diff --git a/question_2/spec/requests/user_can_register_publication_spec.rb b/question_2/spec/requests/user_can_register_publication_spec.rb
deleted file mode 100644
index 9960b42..0000000
--- a/question_2/spec/requests/user_can_register_publication_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# coding: utf-8
-require 'spec_helper'
-
-feature 'ユーザーとして、書いたものを登録できる' do
- let(:user) { User.create! username: 'johndoe', first_name: 'John', last_name: 'Doe', email: 'john@example.com' }
-
- background do
- any_instance_of(ApplicationController) do |controller|
- stub(controller).current_user { user }
- end
- end
-
- scenario '書いたもののタイトルとURLを入力して、登録できる' do
- title = 'タイトル'
- scheme = 'http://'
- url = 'example.com'
-
- visit new_publication_path
-
- fill_in 'Title', with: title
- fill_in 'Url', with: "#{scheme}#{url}"
-
- click_button 'Save'
-
- current_path.should eq publication_path(Publication.last)
-
- page.should have_content title
- page.should have_link url
- page.should_not have_content scheme
- end
-end
diff --git a/question_2/spec/requests/user_can_see_list_of_publications_spec.rb b/question_2/spec/requests/user_can_see_list_of_publications_spec.rb
deleted file mode 100644
index eac4c13..0000000
--- a/question_2/spec/requests/user_can_see_list_of_publications_spec.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# coding: utf-8
-require 'spec_helper'
-
-feature 'ユーザーとして、「書いたもの」の一覧を閲覧できる' do
- background do
- 10.times.map.with_index(1) { |i| Publication.create! title: "テスト #{i+1}", url: "http://example.com/test#{i+1}" }
- end
-
- scenario 'トップページから「書いたもの」の一覧ページに遷移することができる' do
- visit root_path
- click_link '一覧へ'
-
- all('.publication').count.should == 10
-
- Publication.all.each do |pub|
- page.should have_content pub.title
- end
- end
-
- scenario 'トップページから「書いたもの」の一覧ページに遷移し、次のページに遷移することができる' do
- 2.times.map.with_index(11) { |i| Publication.create! title: "テスト #{i+1}", url: "http://example.com/test#{i+1}" }
- visit root_path
- click_link '一覧へ'
-
- all('.publication').count.should == 10
-
- click_link 'Next'
-
- all('.publication').count.should == 2
- end
-end
diff --git a/question_2/spec/requests/user_can_see_skilltag_detail_spec.rb b/question_2/spec/requests/user_can_see_skilltag_detail_spec.rb
deleted file mode 100644
index a730725..0000000
--- a/question_2/spec/requests/user_can_see_skilltag_detail_spec.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# coding: utf-8
-require 'spec_helper'
-
-feature 'ユーザーとして、スキルタグの詳細情報を閲覧できる' do
- let(:ruby) { Skilltag.create! name: 'ruby' }
- let(:users) { 10.times.map.with_index(1) { |i| User.create! username: "johndoe_#{i}", first_name: "John#{i}", last_name: "Doe#{i}", email: "john_#{i}@example.com" } }
- let(:publications) { 10.times.map.with_index(1) { |i| Publication.create! title: "テスト投稿 その#{i}", url: "http://example.com/test_#{i}" } }
-
- background do
- users[0..2].map {|u| u.skilltags << ruby }
- publications[0..2].map {|pub| pub.skilltags << ruby }
- end
-
- scenario 'スキルタグの詳細ページで、スキルタグに関連する書いたものの一覧を閲覧できること' do
- visit skilltag_path(name: ruby.name)
-
- ruby.publications.each do |pub|
- page.should have_content pub.title
- end
- page.should have_no_link '書いたもの'
- end
-
- scenario 'スキルタグの詳細ページで、スキルタグを自分に登録しているユーザーの一覧を閲覧できること' do
- visit skilltag_path(name: ruby.name)
- click_link 'ユーザー'
-
- ruby.users.each do |user|
- page.should have_content user.first_name
- page.should have_content user.last_name
- end
- page.should have_no_link 'ユーザー'
- end
-end
--
1.8.0.2
[PATCH 03/24] [A2] use transaction
---
question_2/app/controllers/publications_controller.rb | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/question_2/app/controllers/publications_controller.rb b/question_2/app/controllers/publications_controller.rb
index d464387..c9954ed 100644
--- a/question_2/app/controllers/publications_controller.rb
+++ b/question_2/app/controllers/publications_controller.rb
@@ -13,14 +13,15 @@ class PublicationsController < ApplicationController
end
def create
- @publication = Publication.new params[:publication]
- @publication.users << current_user
-
- if @publication.save
- redirect_to @publication, notice: "#{@publication.title}を登録しました"
- else
- render 'new'
+ begin
+ ActiveRecord::Base.transaction do
+ @publication = Publication.create! params[:publication]
+ @publication.users << current_user
+ end
+ rescue ActiveRecord::RecordInvalid
+ return render 'new'
end
+ redirect_to @publication, notice: "#{@publication.title}を登録しました"
end
def add_author
--
1.8.0.2
[PATCH 04/24] [A2] r g controller authorships create
---
question_2/app/assets/javascripts/authorships.js.coffee | 3 +++
question_2/app/assets/stylesheets/authorships.css.scss | 3 +++
question_2/app/controllers/authorships_controller.rb | 4 ++++
question_2/app/helpers/authorships_helper.rb | 2 ++
question_2/app/views/authorships/create.html.haml | 2 ++
question_2/config/routes.rb | 2 ++
.../spec/controllers/authorships_controller_spec.rb | 12 ++++++++++++
question_2/spec/helpers/authorships_helper_spec.rb | 15 +++++++++++++++
.../spec/views/authorships/create.html.haml_spec.rb | 5 +++++
9 files changed, 48 insertions(+)
create mode 100644 question_2/app/assets/javascripts/authorships.js.coffee
create mode 100644 question_2/app/assets/stylesheets/authorships.css.scss
create mode 100644 question_2/app/controllers/authorships_controller.rb
create mode 100644 question_2/app/helpers/authorships_helper.rb
create mode 100644 question_2/app/views/authorships/create.html.haml
create mode 100644 question_2/spec/controllers/authorships_controller_spec.rb
create mode 100644 question_2/spec/helpers/authorships_helper_spec.rb
create mode 100644 question_2/spec/views/authorships/create.html.haml_spec.rb
diff --git a/question_2/app/assets/javascripts/authorships.js.coffee b/question_2/app/assets/javascripts/authorships.js.coffee
new file mode 100644
index 0000000..7615679
--- /dev/null
+++ b/question_2/app/assets/javascripts/authorships.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/question_2/app/assets/stylesheets/authorships.css.scss b/question_2/app/assets/stylesheets/authorships.css.scss
new file mode 100644
index 0000000..5eb0028
--- /dev/null
+++ b/question_2/app/assets/stylesheets/authorships.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the authorships controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/question_2/app/controllers/authorships_controller.rb b/question_2/app/controllers/authorships_controller.rb
new file mode 100644
index 0000000..c0f9051
--- /dev/null
+++ b/question_2/app/controllers/authorships_controller.rb
@@ -0,0 +1,4 @@
+class AuthorshipsController < ApplicationController
+ def create
+ end
+end
diff --git a/question_2/app/helpers/authorships_helper.rb b/question_2/app/helpers/authorships_helper.rb
new file mode 100644
index 0000000..45325c6
--- /dev/null
+++ b/question_2/app/helpers/authorships_helper.rb
@@ -0,0 +1,2 @@
+module AuthorshipsHelper
+end
diff --git a/question_2/app/views/authorships/create.html.haml b/question_2/app/views/authorships/create.html.haml
new file mode 100644
index 0000000..34307ce
--- /dev/null
+++ b/question_2/app/views/authorships/create.html.haml
@@ -0,0 +1,2 @@
+%h1 Authorships#create
+%p Find me in app/views/authorships/create.html.haml
\ No newline at end of file
diff --git a/question_2/config/routes.rb b/question_2/config/routes.rb
index bbf6868..9b5a232 100644
--- a/question_2/config/routes.rb
+++ b/question_2/config/routes.rb
@@ -1,4 +1,6 @@
Question2::Application.routes.draw do
+ get "authorships/create"
+
post '/login' => 'sessions#login', as: 'login'
delete '/logout' => 'sessions#logout', as: 'logout'
diff --git a/question_2/spec/controllers/authorships_controller_spec.rb b/question_2/spec/controllers/authorships_controller_spec.rb
new file mode 100644
index 0000000..82ab111
--- /dev/null
+++ b/question_2/spec/controllers/authorships_controller_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe AuthorshipsController do
+
+ describe "GET 'create'" do
+ it "returns http success" do
+ get 'create'
+ response.should be_success
+ end
+ end
+
+end
diff --git a/question_2/spec/helpers/authorships_helper_spec.rb b/question_2/spec/helpers/authorships_helper_spec.rb
new file mode 100644
index 0000000..4df33f1
--- /dev/null
+++ b/question_2/spec/helpers/authorships_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+# Specs in this file have access to a helper object that includes
+# the AuthorshipsHelper. For example:
+#
+# describe AuthorshipsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# helper.concat_strings("this","that").should == "this that"
+# end
+# end
+# end
+describe AuthorshipsHelper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/question_2/spec/views/authorships/create.html.haml_spec.rb b/question_2/spec/views/authorships/create.html.haml_spec.rb
new file mode 100644
index 0000000..11de763
--- /dev/null
+++ b/question_2/spec/views/authorships/create.html.haml_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe "authorships/create.html.haml" do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
--
1.8.0.2
[PATCH 05/24] [A2] gomi
---
question_2/spec/controllers/authorships_controller_spec.rb | 12 ------------
1 file changed, 12 deletions(-)
delete mode 100644 question_2/spec/controllers/authorships_controller_spec.rb
diff --git a/question_2/spec/controllers/authorships_controller_spec.rb b/question_2/spec/controllers/authorships_controller_spec.rb
deleted file mode 100644
index 82ab111..0000000
--- a/question_2/spec/controllers/authorships_controller_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-
-describe AuthorshipsController do
-
- describe "GET 'create'" do
- it "returns http success" do
- get 'create'
- response.should be_success
- end
- end
-
-end
--
1.8.0.2
[PATCH 06/24] [A2] OMG
---
question_2/config/routes.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/question_2/config/routes.rb b/question_2/config/routes.rb
index 9b5a232..416fb41 100644
--- a/question_2/config/routes.rb
+++ b/question_2/config/routes.rb
@@ -6,7 +6,7 @@ Question2::Application.routes.draw do
root to: 'top#index'
- resources :publications, except: [:edit, :udpate, :destroy] do
+ resources :publications, except: [:edit, :update, :destroy] do
member do
put :add_author
put :add_skilltag
--
1.8.0.2
[PATCH 07/24] [A2] publications#add_author => authorships#create
---
question_2/app/controllers/authorships_controller.rb | 4 ++++
question_2/app/controllers/publications_controller.rb | 10 ----------
question_2/app/views/publications/show.html.haml | 2 +-
question_2/config/routes.rb | 5 ++---
4 files changed, 7 insertions(+), 14 deletions(-)
diff --git a/question_2/app/controllers/authorships_controller.rb b/question_2/app/controllers/authorships_controller.rb
index c0f9051..7810f21 100644
--- a/question_2/app/controllers/authorships_controller.rb
+++ b/question_2/app/controllers/authorships_controller.rb
@@ -1,4 +1,8 @@
+# coding: utf-8
class AuthorshipsController < ApplicationController
def create
+ publication = Publication.find params[:publication_id]
+ publication.users << current_user
+ redirect_to publication, notice: "#{publication.title}の著者にあなたを追加しました"
end
end
diff --git a/question_2/app/controllers/publications_controller.rb b/question_2/app/controllers/publications_controller.rb
index c9954ed..53510b2 100644
--- a/question_2/app/controllers/publications_controller.rb
+++ b/question_2/app/controllers/publications_controller.rb
@@ -24,16 +24,6 @@ class PublicationsController < ApplicationController
redirect_to @publication, notice: "#{@publication.title}を登録しました"
end
- def add_author
- @publication = Publication.find params[:id]
-
- if @publication.users << current_user
- redirect_to @publication, notice: "#{@publication.title}の著者にあなたを追加しました"
- else
- redirect_to @publication, notice: "#{@publication.title}の著者にあなたを追加できませんでした。"
- end
- end
-
def add_skilltag
@publication = Publication.find params[:id]
@skilltag = Skilltag.where(name: params[:skilltag]).first_or_create!
diff --git a/question_2/app/views/publications/show.html.haml b/question_2/app/views/publications/show.html.haml
index 24e5e11..7bb5d75 100644
--- a/question_2/app/views/publications/show.html.haml
+++ b/question_2/app/views/publications/show.html.haml
@@ -17,7 +17,7 @@
- @publication.users.each do |user|
%li= user.username
- if user_signed_in? && !@publication.users.include?(current_user)
- %li= link_to '自分を著者に登録', add_author_publication_path(@publication), method: :put
+ %li= link_to '自分を著者に登録', [@publication, :authorships], method: :post
%nav
= link_to 'Back', publications_path
diff --git a/question_2/config/routes.rb b/question_2/config/routes.rb
index 416fb41..f0040f3 100644
--- a/question_2/config/routes.rb
+++ b/question_2/config/routes.rb
@@ -1,14 +1,13 @@
Question2::Application.routes.draw do
- get "authorships/create"
-
post '/login' => 'sessions#login', as: 'login'
delete '/logout' => 'sessions#logout', as: 'logout'
root to: 'top#index'
resources :publications, except: [:edit, :update, :destroy] do
+ resources :authorships, only: :create
+
member do
- put :add_author
put :add_skilltag
end
end
--
1.8.0.2
[PATCH 08/24] [A2] r g controller skills create
---
question_2/app/assets/javascripts/skills.js.coffee | 3 +++
question_2/app/assets/stylesheets/skills.css.scss | 3 +++
question_2/app/controllers/skills_controller.rb | 4 ++++
question_2/app/helpers/skills_helper.rb | 2 ++
question_2/app/views/skills/create.html.haml | 2 ++
question_2/config/routes.rb | 2 ++
question_2/spec/controllers/skills_controller_spec.rb | 12 ++++++++++++
question_2/spec/helpers/skills_helper_spec.rb | 15 +++++++++++++++
question_2/spec/views/skills/create.html.haml_spec.rb | 5 +++++
9 files changed, 48 insertions(+)
create mode 100644 question_2/app/assets/javascripts/skills.js.coffee
create mode 100644 question_2/app/assets/stylesheets/skills.css.scss
create mode 100644 question_2/app/controllers/skills_controller.rb
create mode 100644 question_2/app/helpers/skills_helper.rb
create mode 100644 question_2/app/views/skills/create.html.haml
create mode 100644 question_2/spec/controllers/skills_controller_spec.rb
create mode 100644 question_2/spec/helpers/skills_helper_spec.rb
create mode 100644 question_2/spec/views/skills/create.html.haml_spec.rb
diff --git a/question_2/app/assets/javascripts/skills.js.coffee b/question_2/app/assets/javascripts/skills.js.coffee
new file mode 100644
index 0000000..7615679
--- /dev/null
+++ b/question_2/app/assets/javascripts/skills.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/question_2/app/assets/stylesheets/skills.css.scss b/question_2/app/assets/stylesheets/skills.css.scss
new file mode 100644
index 0000000..b6c781d
--- /dev/null
+++ b/question_2/app/assets/stylesheets/skills.css.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the skills controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/question_2/app/controllers/skills_controller.rb b/question_2/app/controllers/skills_controller.rb
new file mode 100644
index 0000000..ae6240c
--- /dev/null
+++ b/question_2/app/controllers/skills_controller.rb
@@ -0,0 +1,4 @@
+class SkillsController < ApplicationController
+ def create
+ end
+end
diff --git a/question_2/app/helpers/skills_helper.rb b/question_2/app/helpers/skills_helper.rb
new file mode 100644
index 0000000..5a66444
--- /dev/null
+++ b/question_2/app/helpers/skills_helper.rb
@@ -0,0 +1,2 @@
+module SkillsHelper
+end
diff --git a/question_2/app/views/skills/create.html.haml b/question_2/app/views/skills/create.html.haml
new file mode 100644
index 0000000..9fb7307
--- /dev/null
+++ b/question_2/app/views/skills/create.html.haml
@@ -0,0 +1,2 @@
+%h1 Skills#create
+%p Find me in app/views/skills/create.html.haml
\ No newline at end of file
diff --git a/question_2/config/routes.rb b/question_2/config/routes.rb
index f0040f3..3cafded 100644
--- a/question_2/config/routes.rb
+++ b/question_2/config/routes.rb
@@ -1,4 +1,6 @@
Question2::Application.routes.draw do
+ get "skills/create"
+
post '/login' => 'sessions#login', as: 'login'
delete '/logout' => 'sessions#logout', as: 'logout'
diff --git a/question_2/spec/controllers/skills_controller_spec.rb b/question_2/spec/controllers/skills_controller_spec.rb
new file mode 100644
index 0000000..3095d1c
--- /dev/null
+++ b/question_2/spec/controllers/skills_controller_spec.rb
@@ -0,0 +1,12 @@
+require 'spec_helper'
+
+describe SkillsController do
+
+ describe "GET 'create'" do
+ it "returns http success" do
+ get 'create'
+ response.should be_success
+ end
+ end
+
+end
diff --git a/question_2/spec/helpers/skills_helper_spec.rb b/question_2/spec/helpers/skills_helper_spec.rb
new file mode 100644
index 0000000..f353bf0
--- /dev/null
+++ b/question_2/spec/helpers/skills_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+# Specs in this file have access to a helper object that includes
+# the SkillsHelper. For example:
+#
+# describe SkillsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# helper.concat_strings("this","that").should == "this that"
+# end
+# end
+# end
+describe SkillsHelper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/question_2/spec/views/skills/create.html.haml_spec.rb b/question_2/spec/views/skills/create.html.haml_spec.rb
new file mode 100644
index 0000000..e757eca
--- /dev/null
+++ b/question_2/spec/views/skills/create.html.haml_spec.rb
@@ -0,0 +1,5 @@
+require 'spec_helper'
+
+describe "skills/create.html.haml" do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
--
1.8.0.2
[PATCH 09/24] [A2] gomi
---
question_2/spec/controllers/skills_controller_spec.rb | 12 ------------
1 file changed, 12 deletions(-)
delete mode 100644 question_2/spec/controllers/skills_controller_spec.rb
diff --git a/question_2/spec/controllers/skills_controller_spec.rb b/question_2/spec/controllers/skills_controller_spec.rb
deleted file mode 100644
index 3095d1c..0000000
--- a/question_2/spec/controllers/skills_controller_spec.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'spec_helper'
-
-describe SkillsController do
-
- describe "GET 'create'" do
- it "returns http success" do
- get 'create'
- response.should be_success
- end
- end
-
-end
--
1.8.0.2
[PATCH 10/24] [A2] publications#add_skilltag => skills#create
---
question_2/app/controllers/publications_controller.rb | 11 -----------
question_2/app/controllers/skills_controller.rb | 9 +++++++++
question_2/app/views/publications/show.html.haml | 2 +-
question_2/config/routes.rb | 7 +------
4 files changed, 11 insertions(+), 18 deletions(-)
diff --git a/question_2/app/controllers/publications_controller.rb b/question_2/app/controllers/publications_controller.rb
index 53510b2..3bd7d0d 100644
--- a/question_2/app/controllers/publications_controller.rb
+++ b/question_2/app/controllers/publications_controller.rb
@@ -23,15 +23,4 @@ class PublicationsController < ApplicationController
end
redirect_to @publication, notice: "#{@publication.title}を登録しました"
end
-
- def add_skilltag
- @publication = Publication.find params[:id]
- @skilltag = Skilltag.where(name: params[:skilltag]).first_or_create!
-
- if @publication.skilltags << @skilltag
- redirect_to @publication, notice: "#{@publication.title}のスキルに#{@skilltag.name}を追加しました"
- else
- redirect_to @publication, notice: "#{@publication.title}のスキルに#{@skilltag.name}を追加できませんでした"
- end
- end
end
diff --git a/question_2/app/controllers/skills_controller.rb b/question_2/app/controllers/skills_controller.rb
index ae6240c..db6a10a 100644
--- a/question_2/app/controllers/skills_controller.rb
+++ b/question_2/app/controllers/skills_controller.rb
@@ -1,4 +1,13 @@
+# coding: utf-8
class SkillsController < ApplicationController
def create
+ @publication = Publication.find params[:publication_id]
+ @skilltag = Skilltag.where(name: params[:skilltag]).first_or_create!
+
+ if @publication.skilltags << @skilltag
+ redirect_to @publication, notice: "#{@publication.title}のスキルに#{@skilltag.name}を追加しました"
+ else
+ redirect_to @publication, notice: "#{@publication.title}のスキルに#{@skilltag.name}を追加できませんでした"
+ end
end
end
diff --git a/question_2/app/views/publications/show.html.haml b/question_2/app/views/publications/show.html.haml
index 7bb5d75..6192b5f 100644
--- a/question_2/app/views/publications/show.html.haml
+++ b/question_2/app/views/publications/show.html.haml
@@ -9,7 +9,7 @@
%li= skilltag.name
- if user_signed_in?
%li
- = form_tag add_skilltag_publication_path(@publication), method: :put do
+ = form_tag [@publication, :skills], method: :post do
= text_field_tag :skilltag
= submit_tag 'スキルタグ登録する'
diff --git a/question_2/config/routes.rb b/question_2/config/routes.rb
index 3cafded..af5c417 100644
--- a/question_2/config/routes.rb
+++ b/question_2/config/routes.rb
@@ -1,6 +1,4 @@
Question2::Application.routes.draw do
- get "skills/create"
-
post '/login' => 'sessions#login', as: 'login'
delete '/logout' => 'sessions#logout', as: 'logout'
@@ -8,10 +6,7 @@ Question2::Application.routes.draw do
resources :publications, except: [:edit, :update, :destroy] do
resources :authorships, only: :create
-
- member do
- put :add_skilltag
- end
+ resources :skills, only: :create
end
resources :users, only: :show do
--
1.8.0.2
[PATCH 11/24] [A2] treat irregular cases as system errors
---
question_2/app/controllers/skills_controller.rb | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/question_2/app/controllers/skills_controller.rb b/question_2/app/controllers/skills_controller.rb
index db6a10a..e66dfda 100644
--- a/question_2/app/controllers/skills_controller.rb
+++ b/question_2/app/controllers/skills_controller.rb
@@ -3,11 +3,7 @@ class SkillsController < ApplicationController
def create
@publication = Publication.find params[:publication_id]
@skilltag = Skilltag.where(name: params[:skilltag]).first_or_create!
-
- if @publication.skilltags << @skilltag
- redirect_to @publication, notice: "#{@publication.title}のスキルに#{@skilltag.name}を追加しました"
- else
- redirect_to @publication, notice: "#{@publication.title}のスキルに#{@skilltag.name}を追加できませんでした"
- end
+ @publication.skilltags << @skilltag
+ redirect_to @publication, notice: "#{@publication.title}のスキルに#{@skilltag.name}を追加しました"
end
end
--
1.8.0.2
[PATCH 12/24] [A2] be aware of transaction
---
question_2/app/controllers/skills_controller.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/question_2/app/controllers/skills_controller.rb b/question_2/app/controllers/skills_controller.rb
index e66dfda..e2a3c08 100644
--- a/question_2/app/controllers/skills_controller.rb
+++ b/question_2/app/controllers/skills_controller.rb
@@ -2,7 +2,7 @@
class SkillsController < ApplicationController
def create
@publication = Publication.find params[:publication_id]
- @skilltag = Skilltag.where(name: params[:skilltag]).first_or_create!
+ @skilltag = Skilltag.where(name: params[:skilltag]).first_or_initialize
@publication.skilltags << @skilltag
redirect_to @publication, notice: "#{@publication.title}のスキルに#{@skilltag.name}を追加しました"
end
--
1.8.0.2
[PATCH 13/24] [A2] no need to use ivars
---
question_2/app/controllers/skills_controller.rb | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/question_2/app/controllers/skills_controller.rb b/question_2/app/controllers/skills_controller.rb
index e2a3c08..4960d91 100644
--- a/question_2/app/controllers/skills_controller.rb
+++ b/question_2/app/controllers/skills_controller.rb
@@ -1,9 +1,9 @@
# coding: utf-8
class SkillsController < ApplicationController
def create
- @publication = Publication.find params[:publication_id]
- @skilltag = Skilltag.where(name: params[:skilltag]).first_or_initialize
- @publication.skilltags << @skilltag
- redirect_to @publication, notice: "#{@publication.title}のスキルに#{@skilltag.name}を追加しました"
+ publication = Publication.find params[:publication_id]
+ skilltag = Skilltag.where(name: params[:skilltag]).first_or_initialize
+ publication.skilltags << skilltag
+ redirect_to publication, notice: "#{publication.title}のスキルに#{skilltag.name}を追加しました"
end
end
--
1.8.0.2
[PATCH 14/24] [A2] !
---
question_2/app/controllers/skilltags_controller.rb | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/question_2/app/controllers/skilltags_controller.rb b/question_2/app/controllers/skilltags_controller.rb
index 9b7cfdc..5f9c8ad 100644
--- a/question_2/app/controllers/skilltags_controller.rb
+++ b/question_2/app/controllers/skilltags_controller.rb
@@ -1,8 +1,7 @@
# coding: utf-8
class SkilltagsController < ApplicationController
def show
- @skilltag = Skilltag.find_by_name params[:name]
- raise ActiveRecord::RecordNotFound if @skilltag.nil?
+ @skilltag = Skilltag.find_by_name! params[:name]
end
# POST /users/:user_id/skilltags
--
1.8.0.2
[PATCH 15/24] [A2] !
---
question_2/app/controllers/skilltags/users_controller.rb | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/question_2/app/controllers/skilltags/users_controller.rb b/question_2/app/controllers/skilltags/users_controller.rb
index 28212dc..323bc85 100644
--- a/question_2/app/controllers/skilltags/users_controller.rb
+++ b/question_2/app/controllers/skilltags/users_controller.rb
@@ -1,8 +1,7 @@
# coding: utf-8
class Skilltags::UsersController < ApplicationController
def index
- @skilltag = Skilltag.find_by_name params[:name]
- raise ActiveRecord::RecordNotFound if @skilltag.nil?
+ @skilltag = Skilltag.find_by_name! params[:name]
@users = @skilltag.users.all
end
--
1.8.0.2
[PATCH 16/24] [A2] validate in model
---
question_2/app/controllers/skilltags_controller.rb | 2 --
question_2/app/models/user_skilltag.rb | 3 +++
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/question_2/app/controllers/skilltags_controller.rb b/question_2/app/controllers/skilltags_controller.rb
index 5f9c8ad..06e177d 100644
--- a/question_2/app/controllers/skilltags_controller.rb
+++ b/question_2/app/controllers/skilltags_controller.rb
@@ -9,8 +9,6 @@ class SkilltagsController < ApplicationController
@user = User.find params[:user_id]
@skilltag = Skilltag.where(name: params[:skilltag][:name]).first_or_initialize
- return redirect_to @user, notice: "すでに登録済みです" if @user.skilltags.include? @skilltag
-
if @user.skilltags << @skilltag
redirect_to @user, notice: "#{@skilltag.name}を登録しました"
else
diff --git a/question_2/app/models/user_skilltag.rb b/question_2/app/models/user_skilltag.rb
index ca8d102..12672c3 100644
--- a/question_2/app/models/user_skilltag.rb
+++ b/question_2/app/models/user_skilltag.rb
@@ -1,6 +1,9 @@
+# coding: utf-8
class UserSkilltag < ActiveRecord::Base
attr_accessible :skilltag_id, :user_id
belongs_to :user
belongs_to :skilltag
+
+ validates_uniqueness_of :skilltag_id, scope: :user_id, message: 'は、すでに登録済みです'
end
--
1.8.0.2
[PATCH 17/24] [A2] r g migration add_index_to_user_publications
---
.../db/migrate/20121219101347_add_index_to_user_publications.rb | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 question_2/db/migrate/20121219101347_add_index_to_user_publications.rb
diff --git a/question_2/db/migrate/20121219101347_add_index_to_user_publications.rb b/question_2/db/migrate/20121219101347_add_index_to_user_publications.rb
new file mode 100644
index 0000000..558f49d
--- /dev/null
+++ b/question_2/db/migrate/20121219101347_add_index_to_user_publications.rb
@@ -0,0 +1,4 @@
+class AddIndexToUserPublications < ActiveRecord::Migration
+ def change
+ end
+end
--
1.8.0.2
[PATCH 18/24] [A2] add_index :user_publications, [:user_id,
:publication_id]
---
question_2/db/migrate/20121219101347_add_index_to_user_publications.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/question_2/db/migrate/20121219101347_add_index_to_user_publications.rb b/question_2/db/migrate/20121219101347_add_index_to_user_publications.rb
index 558f49d..a7a1ba7 100644
--- a/question_2/db/migrate/20121219101347_add_index_to_user_publications.rb
+++ b/question_2/db/migrate/20121219101347_add_index_to_user_publications.rb
@@ -1,4 +1,5 @@
class AddIndexToUserPublications < ActiveRecord::Migration
def change
+ add_index :user_publications, [:user_id, :publication_id]
end
end
--
1.8.0.2
[PATCH 19/24] [A2] r g migration add_index_to_publication_skilltags
---
.../db/migrate/20121219102932_add_index_to_publication_skilltags.rb | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 question_2/db/migrate/20121219102932_add_index_to_publication_skilltags.rb
diff --git a/question_2/db/migrate/20121219102932_add_index_to_publication_skilltags.rb b/question_2/db/migrate/20121219102932_add_index_to_publication_skilltags.rb
new file mode 100644
index 0000000..cd1e581
--- /dev/null
+++ b/question_2/db/migrate/20121219102932_add_index_to_publication_skilltags.rb
@@ -0,0 +1,4 @@
+class AddIndexToPublicationSkilltags < ActiveRecord::Migration
+ def change
+ end
+end
--
1.8.0.2
[PATCH 20/24] [A2] add_index :publication_skilltags,
[:publication_id, :skilltag_id]
---
.../db/migrate/20121219102932_add_index_to_publication_skilltags.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/question_2/db/migrate/20121219102932_add_index_to_publication_skilltags.rb b/question_2/db/migrate/20121219102932_add_index_to_publication_skilltags.rb
index cd1e581..a52fb72 100644
--- a/question_2/db/migrate/20121219102932_add_index_to_publication_skilltags.rb
+++ b/question_2/db/migrate/20121219102932_add_index_to_publication_skilltags.rb
@@ -1,4 +1,5 @@
class AddIndexToPublicationSkilltags < ActiveRecord::Migration
def change
+ add_index :publication_skilltags, [:publication_id, :skilltag_id]
end
end
--
1.8.0.2
[PATCH 21/24] [A2] r g migration add_index_to_user_skilltags
---
question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb
diff --git a/question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb b/question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb
new file mode 100644
index 0000000..5f298b3
--- /dev/null
+++ b/question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb
@@ -0,0 +1,4 @@
+class AddIndexToUserSkilltags < ActiveRecord::Migration
+ def change
+ end
+end
--
1.8.0.2
[PATCH 22/24] [A2] add_index :user_skilltags, [:user_id,
:skilltag_id]
---
question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb | 1 +
1 file changed, 1 insertion(+)
diff --git a/question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb b/question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb
index 5f298b3..80c1c81 100644
--- a/question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb
+++ b/question_2/db/migrate/20121219103138_add_index_to_user_skilltags.rb
@@ -1,4 +1,5 @@
class AddIndexToUserSkilltags < ActiveRecord::Migration
def change
+ add_index :user_skilltags, [:user_id, :skilltag_id]
end
end
--
1.8.0.2
[PATCH 23/24] [A2] rake db:migrate
---
question_2/db/schema.rb | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/question_2/db/schema.rb b/question_2/db/schema.rb
index 901fe40..627781d 100644
--- a/question_2/db/schema.rb
+++ b/question_2/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20121209150353) do
+ActiveRecord::Schema.define(:version => 20121219103138) do
create_table "publication_skilltags", :force => true do |t|
t.integer "publication_id"
@@ -20,6 +20,8 @@ ActiveRecord::Schema.define(:version => 20121209150353) do
t.datetime "updated_at", :null => false
end
+ add_index "publication_skilltags", ["publication_id", "skilltag_id"], :name => "index_publication_skilltags_on_publication_id_and_skilltag_id"
+
create_table "publications", :force => true do |t|
t.string "title"
t.string "url"
@@ -41,6 +43,8 @@ ActiveRecord::Schema.define(:version => 20121209150353) do
t.datetime "updated_at", :null => false
end
+ add_index "user_publications", ["user_id", "publication_id"], :name => "index_user_publications_on_user_id_and_publication_id"
+
create_table "user_skilltags", :force => true do |t|
t.integer "user_id"
t.integer "skilltag_id"
@@ -48,6 +52,8 @@ ActiveRecord::Schema.define(:version => 20121209150353) do
t.datetime "updated_at", :null => false
end
+ add_index "user_skilltags", ["user_id", "skilltag_id"], :name => "index_user_skilltags_on_user_id_and_skilltag_id"
+
create_table "users", :force => true do |t|
t.string "username"
t.string "first_name"
--
1.8.0.2
[PATCH 24/24] [A2] AR.find_by_id won't raise when record not found
---
question_2/app/controllers/application_controller.rb | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/question_2/app/controllers/application_controller.rb b/question_2/app/controllers/application_controller.rb
index 93a344b..53c8d15 100644
--- a/question_2/app/controllers/application_controller.rb
+++ b/question_2/app/controllers/application_controller.rb
@@ -4,9 +4,7 @@ class ApplicationController < ActionController::Base
helper_method :current_user, :user_signed_in?
def current_user
- @current_user ||= User.find(session[:current_user])
- rescue ActiveRecord::RecordNotFound
- nil
+ @current_user ||= User.find_by_id(session[:current_user])
end
def user_signed_in?
--
1.8.0.2
○解説
user.skilltags.include?で重要なのは、バリデーション処理をコントローラーからモデルに移した点である。またpublications#add_skilltagのRESTfulじゃないメソッドは、skillコントローラーをつくってそこに移動させた。なぜskilltagではないかというと、中間の見えないリソースに対するCRUD操作と解釈したため。またUsersControllerのところでfind_by_nameの後に「!」マークをつける、AuthorshipsControllerを作成したなど、変更する箇所はいろいろある。
ちなみに、なぜauthorshipsというネーミングにしたか。David Heinemeier Hansson氏(DHH)が日本Rubyカンファレンス2006で行った講演を聴いた人ならわかると思うが、同講演でDHHはユーザーとユーザーが紐付く見えない関連を表すコントローラーをfriendshipと名付けていたと思うのだが、それをもじってみた。つまりauthorshipsというネーミングにするのが、37signalsが考えるRails的なネーミングというイメージを抱いたから。これが正しいとはいえないが、好ましく思っている。
この問題の回答を披露した参加者は3人。その参加者の中には「add_skilltagのコントローラーでの登録処理をまとめ、モデルで実行するようにした」と回答した人も。この「ビジネスロジックをモデルに書く」という発想に、松田さんは「考えたこともなく、目からうろこの思い。しかしここはビジネスロジックと言うほどでもないので、コントローラーでいいのでは」と解説していた。
<問3.Rails4.0先取り編> ForkwellをRails 4に対応させましょう
最後は将来、Rails4に移行する際に誰もが陥るであろう、Rails4で一番面倒くさい変更点への対処法を予習するための問題だ。
○設問
Rails 4では、mass assignment脆弱性への新しい対応策として、strong_parametersが導入されます。以下のリポジトリにあるモデルを、strong_parametersを使って書きなおしてください。
https://github.com/CodeIQ/forkwell/tree/master/question_3
※解答は、git format-patchで書き出したテキストファイルを解答画面からアップロードしてください。
○松田さんの模範解答
[PATCH 1/2] [A3] bundle strong_parameters
---
question_3/Gemfile | 1 +
question_3/Gemfile.lock | 5 +++++
2 files changed, 6 insertions(+)
diff --git a/question_3/Gemfile b/question_3/Gemfile
index 7f85274..a7fd44c 100644
--- a/question_3/Gemfile
+++ b/question_3/Gemfile
@@ -22,6 +22,7 @@ end
gem 'jquery-rails'
gem 'action_args'
+gem 'strong_parameters'
group :development, :test do
gem 'rspec-rails'
diff --git a/question_3/Gemfile.lock b/question_3/Gemfile.lock
index 8d32f82..65a71f0 100644
--- a/question_3/Gemfile.lock
+++ b/question_3/Gemfile.lock
@@ -103,6 +103,10 @@ GEM
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
sqlite3 (1.3.6)
+ strong_parameters (0.1.5)
+ actionpack (~> 3.1)
+ activemodel (~> 3.1)
+ railties (~> 3.1)
thor (0.16.0)
tilt (1.3.3)
treetop (1.4.12)
@@ -124,4 +128,5 @@ DEPENDENCIES
rspec-rails
sass-rails (~> 3.2.3)
sqlite3
+ strong_parameters
uglifier (>= 1.0.3)
--
1.8.0.2
[PATCH 2/2] [A3] from MassAssignmentSecurity to StrongParameters
---
question_3/app/controllers/company_users_controller.rb | 2 ++
question_3/app/models/company_user.rb | 5 ++---
question_3/config/application.rb | 2 +-
question_3/spec/controllers/company_users_controller_spec.rb | 12 ++++++------
4 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/question_3/app/controllers/company_users_controller.rb b/question_3/app/controllers/company_users_controller.rb
index 3dc81c5..612e42d 100644
--- a/question_3/app/controllers/company_users_controller.rb
+++ b/question_3/app/controllers/company_users_controller.rb
@@ -1,4 +1,6 @@
class CompanyUsersController < ApplicationController
+ permits :position
+
def create(company_user)
CompanyUser.create! company_user
end
diff --git a/question_3/app/models/company_user.rb b/question_3/app/models/company_user.rb
index 52bed34..158d7c5 100644
--- a/question_3/app/models/company_user.rb
+++ b/question_3/app/models/company_user.rb
@@ -1,7 +1,6 @@
class CompanyUser < ActiveRecord::Base
+ include ActiveModel::ForbiddenAttributesProtection
+
belongs_to :user
belongs_to :company
-
- attr_accessible :position
- attr_accessible :is_admin, :position, as: :admin
end
diff --git a/question_3/config/application.rb b/question_3/config/application.rb
index c3ba3c0..f566e25 100644
--- a/question_3/config/application.rb
+++ b/question_3/config/application.rb
@@ -57,7 +57,7 @@ module Question3
# This will create an empty whitelist of attributes available for mass-assignment for all models
# in your app. As such, your models will need to explicitly whitelist or blacklist accessible
# parameters by using an attr_accessible or attr_protected declaration.
- config.active_record.whitelist_attributes = true
+ config.active_record.whitelist_attributes = false
# Enable the asset pipeline
config.assets.enabled = true
diff --git a/question_3/spec/controllers/company_users_controller_spec.rb b/question_3/spec/controllers/company_users_controller_spec.rb
index cf3d305..3b53a4a 100644
--- a/question_3/spec/controllers/company_users_controller_spec.rb
+++ b/question_3/spec/controllers/company_users_controller_spec.rb
@@ -1,23 +1,23 @@
require 'spec_helper'
describe CompanyUsersController do
+ subject { CompanyUser.last }
+
context 'without is_admin' do
describe "POST 'create'" do
before do
controller.create position: 'programmer'
end
- subject { CompanyUser.last }
it { should_not be_is_admin }
end
end
context 'with is_admin' do
describe "POST 'create'" do
- it {
- expect {
- controller.create position: 'programmer', is_admin: 'true'
- }.to raise_error ActiveModel::MassAssignmentSecurity::Error
- }
+ before do
+ controller.create position: 'programmer'
+ end
+ it { should_not be_is_admin }
end
end
end
--
1.8.0.2
○解説
4.0ではMassAssignmentSecurityが使えない。その部分を4で新たに採用されたStrongParametersというライブラリを使って、コントローラーに書き換えることになる。GitHub上にあるRailsのドキュメントを見ればわかるが、(person_params)でその下に宣言しているプライベートメソッドを呼び出すのだが、それがあまり洗練されていない。そこで試して欲しいのが、GitHubのasakusarbで公開している「action_args」というプラグイン。模範解答例でもこのプラグインを使用している。また実際、Forkwellの開発でも使っており、運用実績もあるので、安心して活用して欲しい。
このパラメータを使ったとしてもStrongParametersの対応は非常に大変な作業である。protected_attributesというプラグイン使うという手もあるが、将来的に4への移行を考えている場合は、今後3で開発するアプリについてはStrongParametersを使うことをお勧めする。
今後もエンジニアにとって面白いイベントの開催を約束
一見、競合関係にも見える「Forkwell」と「CodeIQ」。しかし両サービスともエンジニアに役立つものを提供したいという思いは同じ。今回のイベントはその第一歩。Forkwellの担当者からも「これからもエンジニアのためになる面白いイベントを共に開催していく話になっています。そのときはみなさん、ぜひ参加してくださいね」と語り、同イベントは盛況のうちに閉幕した。
|
このレポートを読んだあなたにオススメします
Rubyユーザやインフラエンジニアも必見、mrubyのススメ
mrubyを知るとRubyがもっと楽しくなる!
mruby、名前だけは聞いたことがあるけれど組込みエンジニアではないから自分には関係ないと思っているRubyプログラマ…
「おもしろすぎる」を目指す、対談系エンターテインメント!
白石俊平氏とカッコいいやつらが語る電子書籍の未来
「html5j.org」管理人で、HTML5のエバンジェリストとして知られる白石俊平氏が、また新しい試みを始めた。エン…
最新技術を学ぶ「GREE Tech Talk#02」に300人超の技術者が参加
グリー主催の勉強会「GitHub:E Casual Talk」に潜入
企業内でGitHubを使うためのサービス「GitHub:Enterprise(GHE)」。その導入ユーザーであるグリー…
CodeIQ“コードで応募”エンジニアの新しい転職スタイル
コード転職やってはいけない6ヶ条とキラリ光る4ヶ条
企業の第一線で働いているエンジニアに、直接実力を評価してもらい転職する。そういった「コードを書いて応募する」転職スタイ…
100個の玉の中から貴重な石を一つだけ選び出せ!
「機械学習基礎」簡単な問題を解いて理解しよう!後篇
こんにちは、@naoya_tです!ITエンジニア向け実務スキル評価サービス「CodeIQ」で僕が出題している問題の解答…
広める?深める?…あなたが目指したいのはどっちだ
自分に合った「キャリアアップ」2つの登り方
「キャリアアップしたい」と考えるのは、ビジネスパーソンとして当然のこと。しかし、どんなふうにキャリアアップしたいのか、…
あなたのメッセージがTech総研に載るかも