ポリモーフィック関連付け（Railsガイド写経）

Railsガイド「Active Record の関連付け (アソシエーション)」の章、[2.9 ポリモーフィック関連付け]
(https://railsguides.jp/association_basics.html#%E3%83%9D%E3%83%AA%E3%83%A2%E3%83%BC%E3%83%95%E3%82%A3%E3%83%83%E3%82%AF%E9%96%A2%E9%80%A3%E4%BB%98%E3%81%91) の写経です。

# 目的
写経により、ポリモーしてるぜ感を味わう。

# 準備

#### railsプロジェクト testapp　を作ってあります。

```shell-session
[ykt68@macbook testapp]$ ruby -v
ruby 2.2.6p396 (2016-11-15 revision 56800) [x86_64-darwin15]
[ykt68@macbook testapp]$ rails -v
Rails 5.0.1
[ykt68@macbook testapp]$ ls
Gemfile		README.md	app		config		db		log		test		vendor
Gemfile.lock	Rakefile	bin		config.ru	lib		public		tmp
[ykt68@macbook testapp]$ rails s
=> Booting Puma
=> Rails 5.0.1 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.7.0 (ruby 2.2.6-p396), codename: Snowy Sagebrush
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop
^CExiting
[ykt68@macbook testapp]$
```

#### データベースはMySQLです。

```shell-session
[ykt68@macbook testapp]$ cat config/database.yml 
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: root
  password: root
  socket: /Applications/MAMP/tmp/mysql/mysql.sock

development:
  <<: *default
  database: testapp_dev
```

# モデル作成
###(1) ```rails g model``` にて以下の３つのモデルを作成します。
#### Picture
```shell-session
[ykt68@macbook testapp]$ rails g model picture name:string imageable:references
Running via Spring preloader in process 5156
Expected string default value for '--jbuilder'; got true (boolean)
      invoke  active_record
      create    db/migrate/20170219012701_create_pictures.rb
      create    app/models/picture.rb
      invoke    test_unit
      create      test/models/picture_test.rb
      create      test/fixtures/pictures.yml
[ykt68@macbook testapp]$
```

#### Employee

```shell-session
[ykt68@macbook testapp]$ rails g model employee name:string
Running via Spring preloader in process 5261
Expected string default value for '--jbuilder'; got true (boolean)
      invoke  active_record
      create    db/migrate/20170219012813_create_employees.rb
      create    app/models/employee.rb
      invoke    test_unit
      create      test/models/employee_test.rb
      create      test/fixtures/employees.yml
[ykt68@macbook testapp]$
```

#### Product

```shell-session
[ykt68@macbook testapp]$ rails g model product name:string
Running via Spring preloader in process 5331
Expected string default value for '--jbuilder'; got true (boolean)
      invoke  active_record
      create    db/migrate/20170219012838_create_products.rb
      create    app/models/product.rb
      invoke    test_unit
      create      test/models/product_test.rb
      create      test/fixtures/products.yml
[ykt68@macbook testapp]$
```

### (2) 作成されたモデルを以下のように修正していきます。
#### Picture
修正前：

```ruby
class Picture < ApplicationRecord
  belongs_to :imageable
end
```
```polymorphic: true```を追加。

修正後：

```ruby
class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end
```

#### Employee
修正前:

```ruby
class Employee < ApplicationRecord
end
```
```has_many :pictures, as: :imageable```を追加します。
修正後:

```ruby
class Employee < ApplicationRecord
  has_many :pictures, as: :imageable
end
```

#### Product
修正前:

```ruby
class Product < ApplicationRecord
end
```
```has_many :pictures, as: :imageable```を追加します。
修正後:

```ruby
class Product < ApplicationRecord
  has_many :pictures, as: :imageable
end
```


# マイグレーション
### CreatePictures
修正前：

```ruby
class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string :name
      t.references :imageable, foreign_key: true

      t.timestamps
    end
  end
end
```

```foreign_key```　を ```polymorphic```に修正して以下とします。
修正後：

```ruby
class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string :name
      t.references :imageable, polymorphic: true

      t.timestamps
    end
  end
end
```
```CreatePictures```および```CreateEmployees```は修正不要で、migration を実行

```shell-session

[ykt68@macbook testapp]$ rails db:migrate
== 20170219102701 CreatePictures: migrating ===================================
-- create_table(:pictures)
   -> 0.0274s
== 20170219102701 CreatePictures: migrated (0.0275s) ==========================

== 20170219102813 CreateEmployees: migrating ==================================
-- create_table(:employees)
   -> 0.0246s
== 20170219102813 CreateEmployees: migrated (0.0247s) =========================

== 20170219102838 CreateProducts: migrating ===================================
-- create_table(:products)
   -> 0.0186s
== 20170219102838 CreateProducts: migrated (0.0187s) ==========================

[ykt68@macbook testapp]$
```

# picturesテーブルの確認

MySQLコンソールから以下で確認



```shell-session
mysql> SHOW CREATE TABLE pictures;
+----------+-----------------------------------------------------------------------------+
| Table    | Create Table                                                                                                                                                                                                                                                                                                                                                                                                          |
+----------+-----------------------------------------------------------------------------+
| pictures | 
```
```sql
CREATE TABLE `pictures` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `imageable_type` varchar(255) DEFAULT NULL,
  `imageable_id` int(11) DEFAULT NULL,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_pictures_on_imageable_type_and_imageable_id` (`imageable_type`,`imageable_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
```
```
+----------+-----------------------------------------------------------------------------+```
1 row in set (0.00 sec)
```
上記のDDLを確認すると、pictures テーブルを作成するマイグレーションの

```ruby
t.references :imageable, polymorphic: true
```
が、

```sql
  `imageable_type` varchar(255) DEFAULT NULL,
  `imageable_id` int(11) DEFAULT NULL,
```
として反映されている。

# サンプルデータの投入

&nbsp;&nbsp;後の確認を分かりやすくするため、employeesテーブルのidを201から、
productsのidを9001から付番するようにして、以下でサンプルデータ作成

```ruby:db/seed.rb
ActiveRecord::Base.connection_pool.with_connection { |con|
  con.execute('ALTER TABLE employees AUTO_INCREMENT=201')
  con.execute('ALTER TABLE products AUTO_INCREMENT=9001')
}

%w[野比 剛田 骨川 出木杉].each do |e|
  Employee.create name:e
end

%w[どこでもドア タケコプター もしもボックス].each do |e|
  Product.create name:e
end
```

db:seed タスクを実行

```shell-session
[ykt68@macbook testapp]$ rails db:seed
```

# 動作確認

ここからは、rails console で確認

```irb
[ykt68@macbook testapp]$ rails c
Running via Spring preloader in process 8313
Loading development environment (Rails 5.0.1)
```

<b>1.Employee とProductのレコードを全件表示</b>

```irb
irb(main):001:0> Employee.all
  Employee Load (0.4ms)  SELECT `employees`.* FROM `employees`
=> #<ActiveRecord::Relation [#<Employee id: 201, name: "野比", created_at: "2017-02-19 02:21:47", updated_at: "2017-02-19 02:21:47">, #<Employee id: 202, name: "剛田", created_at: "2017-02-19 02:21:47", updated_at: "2017-02-19 02:21:47">, #<Employee id: 203, name: "骨川", created_at: "2017-02-19 02:21:47", updated_at: "2017-02-19 02:21:47">, #<Employee id: 204, name: "出木杉", created_at: "2017-02-19 02:21:47", updated_at: "2017-02-19 02:21:47">]>
irb(main):002:0> Product.all
  Product Load (0.5ms)  SELECT `products`.* FROM `products`
=> #<ActiveRecord::Relation [#<Product id: 9001, name: "どこでもドア", created_at: "2017-02-19 02:21:47", updated_at: "2017-02-19 02:21:47">, #<Product id: 9002, name: "タケコプター", created_at: "2017-02-19 02:21:47", updated_at: "2017-02-19 02:21:47">, #<Product id: 9003, name: "もしもボックス", created_at: "2017-02-19 02:21:47", updated_at: "2017-02-19 02:21:47">]>
```

<b>2.Picture レコードを2件保存</b>

```irb
irb(main):003:0> pic1 = Picture.create name:'ある日ののび太', imageable: Employee.find(201)
  Employee Load (0.6ms)  SELECT  `employees`.* FROM `employees` WHERE `employees`.`id` = 201 LIMIT 1
   (0.2ms)  BEGIN
  SQL (0.4ms)  INSERT INTO `pictures` (`name`, `imageable_type`, `imageable_id`, `created_at`, `updated_at`) VALUES ('ある日ののび太', 'Employee', 201, '2017-02-19 02:26:49', '2017-02-19 02:26:49')
   (0.5ms)  COMMIT
=> #<Picture id: 1, name: "ある日ののび太", imageable_type: "Employee", imageable_id: 201, created_at: "2017-02-19 02:26:49", updated_at: "2017-02-19 02:26:49">
```

```irb
irb(main):004:0> pic2 = Picture.create name:'どらえもん、もしもボックスを取り出す', imageable: Product.find(9003)
  Product Load (0.6ms)  SELECT  `products`.* FROM `products` WHERE `products`.`id` = 9003 LIMIT 1
   (0.2ms)  BEGIN
  SQL (0.4ms)  INSERT INTO `pictures` (`name`, `imageable_type`, `imageable_id`, `created_at`, `updated_at`) VALUES ('どらえもん、もしもボックスを取り出す', 'Product', 9003, '2017-02-19 02:27:35', '2017-02-19 02:27:35')
   (3.3ms)  COMMIT
=> #<Picture id: 2, name: "どらえもん、もしもボックスを取り出す", imageable_type: "Product", imageable_id: 9003, created_at: "2017-02-19 02:27:35", updated_at: "2017-02-19 02:27:35">

```

<b>3.Pictureのimageableプロパティに異なるモデルのオブジェクトが入ることを確認</b>

```irb
irb(main):005:0> pic1.imageable
=> #<Employee id: 201, name: "野比", created_at: "2017-02-19 02:21:47", updated_at: "2017-02-19 02:21:47">
```
```irb
irb(main):006:0> pic2.imageable
=> #<Product id: 9003, name: "もしもボックス", created_at: "2017-02-19 02:21:47", updated_at: "2017-02-19 02:21:47">
```

うぉぉ、ポリモーしてるぜ。ActiveRecord便利杉ワロタ。

&nbsp;&nbsp;ちなみに、pictures テーブルの 

* imageable_type カラムには、モデルクラス名
* imageable_id カラムには、それぞれのクラスのid

が入っていることを以下で確認

```
mysql> select * from pictures;
+----+---------------------------------+----------------+--------------+---------------------+---------------------+
| id | name                            | imageable_type | imageable_id | created_at          | updated_at          |
+----+---------------------------------+----------------+--------------+---------------------+---------------------+
|  1 | ある日ののび太                    | Employee       |          201 | 2017-02-19 02:26:49 | 2017-02-19 02:26:49 |
|  2 | どらえもん、もしもボックスを取り出す  | Product        |         9003 | 2017-02-19 02:27:35 | 2017-02-19 02:27:35 |
+----+---------------------------------+----------------+--------------+---------------------+---------------------+
2 rows in set (0.01 sec)

```

<b>4.imageable_typeを、存在しないクラス名に変えてみると</b>

以下のように強引にクラス名を変えてみます。

```
mysql> update pictures set imageable_type='Hoge' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from pictures where id=1;
+----+------------------+----------------+--------------+---------------------+---------------------+
| id | name             | imageable_type | imageable_id | created_at          | updated_at          |
+----+------------------+----------------+--------------+---------------------+---------------------+
|  1 | ある日ののび太     | Hoge           |          201 | 2017-02-19 02:26:49 | 2017-02-19 02:26:49 |
+----+------------------+----------------+--------------+---------------------+---------------------+
1 row in set (0.00 sec)
```

その後、このPictureレコードを取り出そうとしてみます。

```shell-session
[ykt68@macbook testapp]$ rails c
Running via Spring preloader in process 10855
Loading development environment (Rails 5.0.1)
```

```Picture.find 1```でエラーになるかと思いきや、

```irb
irb(main):001:0> pict1 = Picture.find 1
  Picture Load (0.6ms)  SELECT  `pictures`.* FROM `pictures` WHERE `pictures`.`id` = 1 LIMIT 1
=> #<Picture id: 1, name: "ある日ののび太", imageable_type: "Hoge", imageable_id: 201, created_at: "2017-02-19 02:26:49", updated_at: "2017-02-19 02:26:49">
```

上記のとおりエラーになりませんでした。
が、```pict1```の```imageable```を取りだそうとすると、以下のように```NameError```　が発生しました。

```irb
irb(main):002:0> pict1.imageable
NameError: uninitialized constant Hoge
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activesupport-5.0.1/lib/active_support/inflector/methods.rb:268:in `const_get'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activesupport-5.0.1/lib/active_support/inflector/methods.rb:268:in `block in constantize'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activesupport-5.0.1/lib/active_support/inflector/methods.rb:266:in `each'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activesupport-5.0.1/lib/active_support/inflector/methods.rb:266:in `inject'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activesupport-5.0.1/lib/active_support/inflector/methods.rb:266:in `constantize'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activesupport-5.0.1/lib/active_support/core_ext/string/inflections.rb:66:in `constantize'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activerecord-5.0.1/lib/active_record/associations/belongs_to_polymorphic_association.rb:7:in `klass'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activerecord-5.0.1/lib/active_record/associations/belongs_to_association.rb:55:in `find_target?'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activerecord-5.0.1/lib/active_record/associations/association.rb:138:in `load_target'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activerecord-5.0.1/lib/active_record/associations/association.rb:53:in `reload'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activerecord-5.0.1/lib/active_record/associations/singular_association.rb:14:in `reader'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/activerecord-5.0.1/lib/active_record/associations/builder/association.rb:111:in `imageable'
	from (irb):2
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/railties-5.0.1/lib/rails/commands/console.rb:65:in `start'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/railties-5.0.1/lib/rails/commands/console_helper.rb:9:in `start'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/railties-5.0.1/lib/rails/commands/commands_tasks.rb:78:in `console'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/railties-5.0.1/lib/rails/commands/commands_tasks.rb:49:in `run_command!'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/gems/2.2.0/gems/railties-5.0.1/lib/rails/commands.rb:18:in `<top (required)>'
	from /Users/ykt68/MyProjects/testapp/bin/rails:9:in `<top (required)>'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
	from /Users/ykt68/.rbenv/versions/2.2.6/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'
	from -e:1:in `<main>'
irb(main):003:0>
```

以上です。<br>
&nbsp;&nbsp;```Picture```の```imageable```プロパティで取得できるクラスを、```Employee```と```Product```だけではなく
後から増やしていけますね。便利。