active_model_serializers 0.10.0.rc2 → 0.10.0.rc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +82 -0
  4. data/.rubocop_todo.yml +315 -0
  5. data/.simplecov +99 -0
  6. data/.travis.yml +8 -0
  7. data/CHANGELOG.md +9 -3
  8. data/Gemfile +39 -8
  9. data/README.md +55 -31
  10. data/Rakefile +29 -2
  11. data/active_model_serializers.gemspec +37 -13
  12. data/appveyor.yml +25 -0
  13. data/docs/README.md +29 -0
  14. data/docs/general/adapters.md +110 -0
  15. data/docs/general/configuration_options.md +11 -0
  16. data/docs/general/getting_started.md +73 -0
  17. data/docs/howto/add_pagination_links.md +112 -0
  18. data/docs/howto/add_root_key.md +51 -0
  19. data/docs/howto/outside_controller_use.md +42 -0
  20. data/lib/action_controller/serialization.rb +24 -33
  21. data/lib/active_model/serializable_resource.rb +70 -0
  22. data/lib/active_model/serializer.rb +50 -131
  23. data/lib/active_model/serializer/adapter.rb +84 -21
  24. data/lib/active_model/serializer/adapter/flatten_json.rb +9 -9
  25. data/lib/active_model/serializer/adapter/fragment_cache.rb +10 -13
  26. data/lib/active_model/serializer/adapter/json.rb +25 -28
  27. data/lib/active_model/serializer/adapter/json/fragment_cache.rb +2 -12
  28. data/lib/active_model/serializer/adapter/json_api.rb +100 -98
  29. data/lib/active_model/serializer/adapter/json_api/fragment_cache.rb +4 -14
  30. data/lib/active_model/serializer/adapter/json_api/pagination_links.rb +50 -0
  31. data/lib/active_model/serializer/adapter/null.rb +2 -8
  32. data/lib/active_model/serializer/array_serializer.rb +22 -17
  33. data/lib/active_model/serializer/association.rb +20 -0
  34. data/lib/active_model/serializer/associations.rb +97 -0
  35. data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
  36. data/lib/active_model/serializer/collection_reflection.rb +7 -0
  37. data/lib/active_model/serializer/configuration.rb +1 -0
  38. data/lib/active_model/serializer/fieldset.rb +7 -7
  39. data/lib/active_model/serializer/has_many_reflection.rb +10 -0
  40. data/lib/active_model/serializer/has_one_reflection.rb +10 -0
  41. data/lib/active_model/serializer/lint.rb +129 -0
  42. data/lib/active_model/serializer/railtie.rb +7 -0
  43. data/lib/active_model/serializer/reflection.rb +74 -0
  44. data/lib/active_model/serializer/singular_reflection.rb +7 -0
  45. data/lib/active_model/serializer/utils.rb +35 -0
  46. data/lib/active_model/serializer/version.rb +1 -1
  47. data/lib/active_model_serializers.rb +28 -14
  48. data/lib/generators/serializer/serializer_generator.rb +7 -7
  49. data/lib/generators/serializer/templates/{serializer.rb → serializer.rb.erb} +2 -2
  50. data/lib/tasks/rubocop.rake +0 -0
  51. data/test/action_controller/adapter_selector_test.rb +3 -3
  52. data/test/action_controller/explicit_serializer_test.rb +9 -9
  53. data/test/action_controller/json_api/linked_test.rb +179 -0
  54. data/test/action_controller/json_api/pagination_test.rb +116 -0
  55. data/test/action_controller/serialization_scope_name_test.rb +10 -6
  56. data/test/action_controller/serialization_test.rb +149 -112
  57. data/test/active_record_test.rb +9 -0
  58. data/test/adapter/fragment_cache_test.rb +11 -1
  59. data/test/adapter/json/belongs_to_test.rb +4 -5
  60. data/test/adapter/json/collection_test.rb +30 -21
  61. data/test/adapter/json/has_many_test.rb +20 -9
  62. data/test/adapter/json_api/belongs_to_test.rb +38 -38
  63. data/test/adapter/json_api/collection_test.rb +22 -23
  64. data/test/adapter/json_api/has_many_embed_ids_test.rb +2 -2
  65. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +4 -4
  66. data/test/adapter/json_api/has_many_test.rb +54 -19
  67. data/test/adapter/json_api/has_one_test.rb +28 -8
  68. data/test/adapter/json_api/json_api_test.rb +37 -0
  69. data/test/adapter/json_api/linked_test.rb +75 -75
  70. data/test/adapter/json_api/pagination_links_test.rb +115 -0
  71. data/test/adapter/json_api/resource_type_config_test.rb +59 -0
  72. data/test/adapter/json_test.rb +18 -5
  73. data/test/adapter_test.rb +10 -11
  74. data/test/array_serializer_test.rb +63 -5
  75. data/test/capture_warnings.rb +65 -0
  76. data/test/fixtures/active_record.rb +56 -0
  77. data/test/fixtures/poro.rb +60 -29
  78. data/test/generators/scaffold_controller_generator_test.rb +1 -2
  79. data/test/generators/serializer_generator_test.rb +17 -12
  80. data/test/lint_test.rb +37 -0
  81. data/test/logger_test.rb +18 -0
  82. data/test/poro_test.rb +9 -0
  83. data/test/serializable_resource_test.rb +27 -0
  84. data/test/serializers/adapter_for_test.rb +123 -3
  85. data/test/serializers/association_macros_test.rb +36 -0
  86. data/test/serializers/associations_test.rb +70 -47
  87. data/test/serializers/attribute_test.rb +28 -4
  88. data/test/serializers/attributes_test.rb +8 -14
  89. data/test/serializers/cache_test.rb +58 -31
  90. data/test/serializers/fieldset_test.rb +3 -4
  91. data/test/serializers/meta_test.rb +42 -28
  92. data/test/serializers/root_test.rb +21 -0
  93. data/test/serializers/serializer_for_test.rb +1 -1
  94. data/test/support/rails_app.rb +21 -0
  95. data/test/support/serialization_testing.rb +13 -0
  96. data/test/support/simplecov.rb +6 -0
  97. data/test/support/stream_capture.rb +50 -0
  98. data/test/support/test_case.rb +5 -0
  99. data/test/test_helper.rb +41 -29
  100. data/test/utils/include_args_to_hash_test.rb +79 -0
  101. metadata +123 -17
  102. data/test/action_controller/json_api_linked_test.rb +0 -179
  103. data/test/action_controller/rescue_from_test.rb +0 -32
  104. data/test/serializers/urls_test.rb +0 -26
@@ -0,0 +1,115 @@
1
+ require 'test_helper'
2
+ require 'will_paginate/array'
3
+ require 'kaminari'
4
+ require 'kaminari/hooks'
5
+ ::Kaminari::Hooks.init
6
+
7
+ module ActiveModel
8
+ class Serializer
9
+ class Adapter
10
+ class JsonApi
11
+ class PaginationLinksTest < Minitest::Test
12
+ URI = 'http://example.com'
13
+
14
+ def setup
15
+ ActionController::Base.cache_store.clear
16
+ @array = [
17
+ Profile.new({ id: 1, name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }),
18
+ Profile.new({ id: 2, name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }),
19
+ Profile.new({ id: 3, name: 'Name 3', description: 'Description 3', comments: 'Comments 3' })
20
+ ]
21
+ end
22
+
23
+ def mock_request(query_parameters = {}, original_url = URI)
24
+ context = Minitest::Mock.new
25
+ context.expect(:original_url, original_url)
26
+ context.expect(:query_parameters, query_parameters)
27
+ @options = {}
28
+ @options[:context] = context
29
+ end
30
+
31
+ def load_adapter(paginated_collection, options = {})
32
+ options = options.merge(adapter: :json_api)
33
+ ActiveModel::SerializableResource.new(paginated_collection, options)
34
+ end
35
+
36
+ def using_kaminari
37
+ Kaminari.paginate_array(@array).page(2).per(1)
38
+ end
39
+
40
+ def using_will_paginate
41
+ @array.paginate(page: 2, per_page: 1)
42
+ end
43
+
44
+ def data
45
+ { data: [
46
+ { id: '1', type: 'profiles', attributes: { name: 'Name 1', description: 'Description 1' } },
47
+ { id: '2', type: 'profiles', attributes: { name: 'Name 2', description: 'Description 2' } },
48
+ { id: '3', type: 'profiles', attributes: { name: 'Name 3', description: 'Description 3' } }
49
+ ]
50
+ }
51
+ end
52
+
53
+ def links
54
+ {
55
+ links: {
56
+ self: "#{URI}?page%5Bnumber%5D=2&page%5Bsize%5D=1",
57
+ first: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1",
58
+ prev: "#{URI}?page%5Bnumber%5D=1&page%5Bsize%5D=1",
59
+ next: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1",
60
+ last: "#{URI}?page%5Bnumber%5D=3&page%5Bsize%5D=1"
61
+ }
62
+ }
63
+ end
64
+
65
+ def expected_response_without_pagination_links
66
+ data
67
+ end
68
+
69
+ def expected_response_with_pagination_links
70
+ {}.tap do |hash|
71
+ hash[:data] = [data.values.flatten.second]
72
+ hash.merge! links
73
+ end
74
+ end
75
+
76
+ def expected_response_with_pagination_links_and_additional_params
77
+ new_links = links[:links].each_with_object({}) { |(key, value), hash| hash[key] = "#{value}&test=test" }
78
+ {}.tap do |hash|
79
+ hash[:data] = [data.values.flatten.second]
80
+ hash.merge! links: new_links
81
+ end
82
+ end
83
+
84
+ def test_pagination_links_using_kaminari
85
+ adapter = load_adapter(using_kaminari)
86
+
87
+ mock_request
88
+ assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options)
89
+ end
90
+
91
+ def test_pagination_links_using_will_paginate
92
+ adapter = load_adapter(using_will_paginate)
93
+
94
+ mock_request
95
+ assert_equal expected_response_with_pagination_links, adapter.serializable_hash(@options)
96
+ end
97
+
98
+ def test_pagination_links_with_additional_params
99
+ adapter = load_adapter(using_will_paginate)
100
+
101
+ mock_request({ test: 'test' })
102
+ assert_equal expected_response_with_pagination_links_and_additional_params,
103
+ adapter.serializable_hash(@options)
104
+ end
105
+
106
+ def test_not_showing_pagination_links
107
+ adapter = load_adapter(@array)
108
+
109
+ assert_equal expected_response_without_pagination_links, adapter.serializable_hash
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,59 @@
1
+ require 'test_helper'
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ class Adapter
6
+ class JsonApi
7
+ class ResourceTypeConfigTest < Minitest::Test
8
+ def setup
9
+ @author = Author.new(id: 1, name: 'Steve K.')
10
+ @author.bio = nil
11
+ @author.roles = []
12
+ @blog = Blog.new(id: 23, name: 'AMS Blog')
13
+ @post = Post.new(id: 42, title: 'New Post', body: 'Body')
14
+ @anonymous_post = Post.new(id: 43, title: 'Hello!!', body: 'Hello, world!!')
15
+ @comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
16
+ @post.comments = [@comment]
17
+ @post.blog = @blog
18
+ @anonymous_post.comments = []
19
+ @anonymous_post.blog = nil
20
+ @comment.post = @post
21
+ @comment.author = nil
22
+ @post.author = @author
23
+ @anonymous_post.author = nil
24
+ @blog = Blog.new(id: 1, name: 'My Blog!!')
25
+ @blog.writer = @author
26
+ @blog.articles = [@post, @anonymous_post]
27
+ @author.posts = []
28
+ end
29
+
30
+ def with_jsonapi_resource_type type
31
+ old_type = ActiveModel::Serializer.config.jsonapi_resource_type
32
+ ActiveModel::Serializer.config.jsonapi_resource_type = type
33
+ yield
34
+ ensure
35
+ ActiveModel::Serializer.config.jsonapi_resource_type = old_type
36
+ end
37
+
38
+ def test_config_plural
39
+ with_adapter :json_api do
40
+ with_jsonapi_resource_type :plural do
41
+ hash = ActiveModel::SerializableResource.new(@comment).serializable_hash
42
+ assert_equal('comments', hash[:data][:type])
43
+ end
44
+ end
45
+ end
46
+
47
+ def test_config_singular
48
+ with_adapter :json_api do
49
+ with_jsonapi_resource_type :singular do
50
+ hash = ActiveModel::SerializableResource.new(@comment).serializable_hash
51
+ assert_equal('comment', hash[:data][:type])
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -7,14 +7,14 @@ module ActiveModel
7
7
  def setup
8
8
  ActionController::Base.cache_store.clear
9
9
  @author = Author.new(id: 1, name: 'Steve K.')
10
- @post = Post.new(title: 'New Post', body: 'Body')
10
+ @post = Post.new(id: 1, title: 'New Post', body: 'Body')
11
11
  @first_comment = Comment.new(id: 1, body: 'ZOMG A COMMENT')
12
12
  @second_comment = Comment.new(id: 2, body: 'ZOMG ANOTHER COMMENT')
13
13
  @post.comments = [@first_comment, @second_comment]
14
14
  @first_comment.post = @post
15
15
  @second_comment.post = @post
16
16
  @post.author = @author
17
- @blog = Blog.new(id: 1, name: "My Blog!!")
17
+ @blog = Blog.new(id: 1, name: 'My Blog!!')
18
18
  @post.blog = @blog
19
19
 
20
20
  @serializer = PostSerializer.new(@post)
@@ -23,12 +23,25 @@ module ActiveModel
23
23
 
24
24
  def test_has_many
25
25
  assert_equal([
26
- {id: 1, body: 'ZOMG A COMMENT'},
27
- {id: 2, body: 'ZOMG ANOTHER COMMENT'}
26
+ { id: 1, body: 'ZOMG A COMMENT' },
27
+ { id: 2, body: 'ZOMG ANOTHER COMMENT' }
28
28
  ], @adapter.serializable_hash[:post][:comments])
29
29
  end
30
+
31
+ def test_custom_keys
32
+ serializer = PostWithCustomKeysSerializer.new(@post)
33
+ adapter = ActiveModel::Serializer::Adapter::Json.new(serializer)
34
+
35
+ assert_equal({
36
+ id: 1,
37
+ reviews: [{ id: 1, body: 'ZOMG A COMMENT' },
38
+ { id: 2, body: 'ZOMG ANOTHER COMMENT' }
39
+ ],
40
+ writer: { id: 1, name: 'Steve K.' },
41
+ site: { id: 1, name: 'My Blog!!' }
42
+ }, adapter.serializable_hash[:post])
43
+ end
30
44
  end
31
45
  end
32
46
  end
33
47
  end
34
-
data/test/adapter_test.rb CHANGED
@@ -19,25 +19,24 @@ module ActiveModel
19
19
  assert_equal @serializer, @adapter.serializer
20
20
  end
21
21
 
22
- def test_adapter_class_for_known_adapter
23
- klass = ActiveModel::Serializer::Adapter.adapter_class(:json_api)
24
- assert_equal ActiveModel::Serializer::Adapter::JsonApi, klass
25
- end
26
-
27
- def test_adapter_class_for_unknown_adapter
28
- klass = ActiveModel::Serializer::Adapter.adapter_class(:json_simple)
29
- assert_nil klass
30
- end
31
-
32
22
  def test_create_adapter
33
23
  adapter = ActiveModel::Serializer::Adapter.create(@serializer)
34
24
  assert_equal ActiveModel::Serializer::Adapter::FlattenJson, adapter.class
35
25
  end
36
26
 
37
27
  def test_create_adapter_with_override
38
- adapter = ActiveModel::Serializer::Adapter.create(@serializer, { adapter: :json_api})
28
+ adapter = ActiveModel::Serializer::Adapter.create(@serializer, { adapter: :json_api })
39
29
  assert_equal ActiveModel::Serializer::Adapter::JsonApi, adapter.class
40
30
  end
31
+
32
+ def test_inflected_adapter_class_for_known_adapter
33
+ ActiveSupport::Inflector.inflections(:en) { |inflect| inflect.acronym 'API' }
34
+ klass = ActiveModel::Serializer::Adapter.adapter_class(:json_api)
35
+
36
+ ActiveSupport::Inflector.inflections.acronyms.clear
37
+
38
+ assert_equal ActiveModel::Serializer::Adapter::JsonApi, klass
39
+ end
41
40
  end
42
41
  end
43
42
  end
@@ -6,7 +6,17 @@ module ActiveModel
6
6
  def setup
7
7
  @comment = Comment.new
8
8
  @post = Post.new
9
- @serializer = ArraySerializer.new([@comment, @post], {some: :options})
9
+ @resource = build_named_collection @comment, @post
10
+ @serializer = ArraySerializer.new(@resource, { some: :options })
11
+ end
12
+
13
+ def build_named_collection(*resource)
14
+ resource.define_singleton_method(:name) { 'MeResource' }
15
+ resource
16
+ end
17
+
18
+ def test_has_object_reader_serializer_interface
19
+ assert_equal @serializer.object, @resource
10
20
  end
11
21
 
12
22
  def test_respond_to_each
@@ -26,17 +36,65 @@ module ActiveModel
26
36
  end
27
37
 
28
38
  def test_serializer_option_not_passed_to_each_serializer
29
- serializers = ArraySerializer.new([@post], {serializer: PostSerializer}).to_a
39
+ serializers = ArraySerializer.new([@post], { serializer: PostSerializer }).to_a
30
40
 
31
41
  refute serializers.first.custom_options.key?(:serializer)
32
42
  end
33
43
 
34
44
  def test_meta_and_meta_key_attr_readers
35
- meta_content = {meta: "the meta", meta_key: "the meta key"}
45
+ meta_content = { meta: 'the meta', meta_key: 'the meta key' }
36
46
  @serializer = ArraySerializer.new([@comment, @post], meta_content)
37
47
 
38
- assert_equal @serializer.meta, "the meta"
39
- assert_equal @serializer.meta_key, "the meta key"
48
+ assert_equal @serializer.meta, 'the meta'
49
+ assert_equal @serializer.meta_key, 'the meta key'
50
+ end
51
+
52
+ def test_root_default
53
+ @serializer = ArraySerializer.new([@comment, @post])
54
+ assert_equal @serializer.root, nil
55
+ end
56
+
57
+ def test_root
58
+ expected = 'custom_root'
59
+ @serializer = ArraySerializer.new([@comment, @post], root: expected)
60
+ assert_equal @serializer.root, expected
61
+ end
62
+
63
+ def test_root_with_no_serializers
64
+ expected = 'custom_root'
65
+ @serializer = ArraySerializer.new([], root: expected)
66
+ assert_equal @serializer.root, expected
67
+ end
68
+
69
+ def test_json_key
70
+ assert_equal @serializer.json_key, 'comments'
71
+ end
72
+
73
+ def test_json_key_with_resource_with_name_and_no_serializers
74
+ serializer = ArraySerializer.new(build_named_collection)
75
+ assert_equal serializer.json_key, 'me_resources'
76
+ end
77
+
78
+ def test_json_key_with_resource_with_nil_name_and_no_serializers
79
+ resource = []
80
+ resource.define_singleton_method(:name) { nil }
81
+ serializer = ArraySerializer.new(resource)
82
+ assert_equal serializer.json_key, nil
83
+ end
84
+
85
+ def test_json_key_with_resource_without_name_and_no_serializers
86
+ serializer = ArraySerializer.new([])
87
+ assert_equal serializer.json_key, nil
88
+ end
89
+
90
+ def test_json_key_with_root
91
+ serializer = ArraySerializer.new(@resource, root: 'custom_root')
92
+ assert_equal serializer.json_key, 'custom_roots'
93
+ end
94
+
95
+ def test_json_key_with_root_and_no_serializers
96
+ serializer = ArraySerializer.new(build_named_collection, root: 'custom_root')
97
+ assert_equal serializer.json_key, 'custom_roots'
40
98
  end
41
99
  end
42
100
  end
@@ -0,0 +1,65 @@
1
+ # https://raw.githubusercontent.com/metric_fu/metric_fu/master/spec/capture_warnings.rb
2
+ require 'tempfile'
3
+ require 'fileutils'
4
+
5
+ class CaptureWarnings
6
+ def initialize(fail_on_warnings = true)
7
+ @fail_on_warnings = fail_on_warnings
8
+ @stderr_file = Tempfile.new('app.stderr')
9
+ @app_root ||= Dir.pwd
10
+ @output_dir = File.join(app_root, 'tmp')
11
+ FileUtils.mkdir_p(output_dir)
12
+ @bundle_dir = File.join(app_root, 'bundle')
13
+ @output = STDOUT
14
+ end
15
+
16
+ def execute!
17
+ $VERBOSE = true
18
+ $stderr.reopen(stderr_file.path)
19
+
20
+ Minitest.after_run do
21
+ stderr_file.rewind
22
+ lines = stderr_file.read.split("\n")
23
+ stderr_file.close!
24
+ $stderr.reopen(STDERR)
25
+ after_tests(lines)
26
+ end
27
+ end
28
+
29
+ # rubocop:disable Metrics/AbcSize
30
+ def after_tests(lines)
31
+ app_warnings, other_warnings = lines.partition { |line|
32
+ line.include?(app_root) && !line.include?(bundle_dir)
33
+ }
34
+
35
+ header = "#{'-' * 22} app warnings: #{'-' * 22}"
36
+ output.puts
37
+ output.puts header
38
+
39
+ if app_warnings.any?
40
+ output.puts app_warnings.join("\n")
41
+ else
42
+ output.puts 'None. Yay!'
43
+ end
44
+
45
+ if other_warnings.any?
46
+ File.write(File.join(output_dir, 'warnings.txt'), other_warnings.join("\n") << "\n")
47
+ output.puts
48
+ output.puts 'Non-app warnings written to tmp/warnings.txt'
49
+ output.puts
50
+ end
51
+
52
+ output.puts
53
+ output.puts '-' * header.size
54
+
55
+ # fail the build...
56
+ if fail_on_warnings && app_warnings.any?
57
+ abort "Failing build due to app warnings: #{app_warnings.inspect}"
58
+ end
59
+ end
60
+ # rubocop:enable Metrics/AbcSize
61
+
62
+ private
63
+
64
+ attr_reader :stderr_file, :app_root, :output_dir, :bundle_dir, :fail_on_warnings, :output
65
+ end
@@ -0,0 +1,56 @@
1
+ require 'active_record'
2
+
3
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
4
+ ActiveRecord::Schema.define do
5
+ create_table :posts, force: true do |t|
6
+ t.string :title
7
+ t.text :body
8
+ t.references :author
9
+ t.timestamps null: false
10
+ end
11
+ create_table :authors, force: true do |t|
12
+ t.string :name
13
+ t.timestamps null: false
14
+ end
15
+ create_table :comments, force: true do |t|
16
+ t.text :contents
17
+ t.references :author
18
+ t.references :post
19
+ t.timestamp null: false
20
+ end
21
+ end
22
+
23
+ module ARModels
24
+ class Post < ActiveRecord::Base
25
+ has_many :comments
26
+ belongs_to :author
27
+ end
28
+
29
+ class Comment < ActiveRecord::Base
30
+ belongs_to :post
31
+ belongs_to :author
32
+ end
33
+
34
+ class Author < ActiveRecord::Base
35
+ has_many :posts
36
+ end
37
+
38
+ class PostSerializer < ActiveModel::Serializer
39
+ attributes :id, :title, :body
40
+
41
+ has_many :comments
42
+ belongs_to :author
43
+ end
44
+
45
+ class CommentSerializer < ActiveModel::Serializer
46
+ attributes :id, :contents
47
+
48
+ belongs_to :author
49
+ end
50
+
51
+ class AuthorSerializer < ActiveModel::Serializer
52
+ attributes :id, :name
53
+
54
+ has_many :posts
55
+ end
56
+ end