similar_models 0.2.1 → 0.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b55aa8e2b833092a963b70b211ff10f0ee78ead0
4
- data.tar.gz: 264b408ebdb78c40b31a1c7f37c99cf6a0360c76
2
+ SHA256:
3
+ metadata.gz: 2bf02e51c067023b861f64a73394be810f74ed007a4299a4cc4de18fb7e8ff71
4
+ data.tar.gz: ae0fdceeb1388fa7a247c89d892d812c2554ffb0c7f12c1389da6b313b3b3350
5
5
  SHA512:
6
- metadata.gz: b81bfa56e0a7cfad7ca1c2e3e00d67645dcf18ef2c0e1726d8f23905ffceda65e1ee7d8a617f5e1af136c62a36cb0f20b85f6e9cac5a5792423c06d277e812c6
7
- data.tar.gz: b2a2d671129e2bdc1498296b8ce299d599132f96751bb5227fa5a35458026b4fb6c13e01710a29502e047f0632a8484df506fbef0f29fb21e40cd85d11857652
6
+ metadata.gz: 3ac48f199b64dc571a9f05caf15128429b809988d8d904750870fa30db3b74a3a5bb5a650862b3b97ce331bd5b9c250efe4efb090c96552c2610da8dbed46760
7
+ data.tar.gz: d0bbb042025768241fcc2b487b9767e19fbd8244f79653d213908added72df10a9368daba02f192390954ebf5f96618b035b49ae4dabbeae0d8c156a629d3dca
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.3.3
1
+ 3.4.2
data/README.md CHANGED
@@ -1,80 +1,116 @@
1
1
  # Similar Models
2
2
 
3
- Adds a `similar_{model name plural}` method to an active record model, but can be set to any name using `as: {method name}`. It returns the most similar models of the same class based on associated models in common.
3
+ Adds a `similar_#{model_name.plural}` instance and class method to an active record model, but can be set to any name using `as: {method name}`.
4
4
 
5
- The association(s) have to be many to many, so either [habtm](http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association) or [has_many :through](http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association).
5
+ The instance method returns models that have associated models in common ordered by most in common first.
6
+
7
+ The class method returns models ordered by most associated models in common.
8
+
9
+ If the commonality count is the same then a second order clause of `created_at` if present takes precedence.
10
+
11
+ The association(s) have to be many to many, so either [habtm](https://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many) or [has_many :through](https://guides.rubyonrails.org/association_basics.html#has-many-through).
6
12
 
7
13
  ## Installation
8
14
 
9
15
  Add this line to your application's Gemfile:
10
16
 
11
- gem 'similar_models'
17
+ ```sh
18
+ gem 'similar_models'
19
+ ```
12
20
 
13
21
  And then execute:
14
22
 
15
- $ bundle
16
-
17
- Or install it yourself as:
18
-
19
- $ gem install similar_models
23
+ ```sh
24
+ $ bundle
25
+ ```
20
26
 
21
27
  ## Usage
22
28
 
23
29
  Post example
24
30
 
25
- class Post < ActiveRecord::Base
26
- has_many :author_posts
27
- has_many :authors, through: :author_posts
28
- has_and_belongs_to_many :tags
31
+ ```ruby
32
+ class Post < ApplicationRecord
33
+ has_many :author_posts
34
+ has_many :authors, through: :author_posts
35
+ has_and_belongs_to_many :tags
29
36
 
30
- has_similar_models :authors
31
- has_similar_models :tags, as: :similar_posts_by_tag
32
- has_similar_models :authors, :tags, as: :similar_posts_by_author_and_tag
33
- end
37
+ has_similar_models :authors
38
+ has_similar_models :tags, as: :similar_posts_by_tag
39
+ has_similar_models :authors, :tags, as: :similar_posts_by_author_and_tag
40
+ end
34
41
 
35
- class Tag < ActiveRecord::Base
36
- end
42
+ class Tag < ApplicationRecord
43
+ end
37
44
 
38
- class Author < ActiveRecord::Base
39
- has_many :author_posts
40
- end
45
+ class Author < ApplicationRecord
46
+ has_many :author_posts
47
+ end
41
48
 
42
- class AuthorPosts < ActiveRecord::Base
43
- belongs_to :author
44
- belongs_to :post
45
- end
49
+ class AuthorPosts < ApplicationRecord
50
+ belongs_to :author
51
+ belongs_to :post
52
+ end
53
+ ```
46
54
 
47
- To return the posts with the most authors in common with `post` in descending order:
55
+ To return posts with authors in common with the `post` model by most in common first:
48
56
 
49
- post.similar_posts
57
+ ```ruby
58
+ post.similar_posts
59
+ ```
60
+
61
+ To return posts ordered by most authors in common:
62
+ ```ruby
63
+ Post.similar_posts
64
+ ```
50
65
 
51
66
  The returned object is an ActiveRecord::Relation and so chaining of other query methods is possible:
52
67
 
53
- post.similar_posts.where('posts.created_at > ?', 10.days.ago).limit(5)
68
+ ```ruby
69
+ post.similar_posts.where(created_at: 10.days.ago..).limit(5)
70
+ ```
71
+
72
+ To return posts with tags in common with the `post` model by most in common first:
73
+
74
+ ```ruby
75
+ post.similar_posts_by_tag
76
+ ```
77
+
78
+ To return posts ordered by most tags in common:
79
+ ```ruby
80
+ Post.similar_posts_by_tag
81
+ ```
54
82
 
55
- To return the posts with the most tags in common with `post` in descending order:
83
+ To return posts with the authors and tags in common with the `post` model by most in common first:
56
84
 
57
- post.similar_posts_by_tag
85
+ ```ruby
86
+ post.similar_posts_by_author_and_tag
87
+ ```
58
88
 
59
- To return the posts with the most authors and tags in common with `post` in descending order:
89
+ To return posts ordered by most authors and tags in common:
60
90
 
61
- post.similar_posts_by_author_and_tag
91
+ ```ruby
92
+ Post.similar_posts_by_author_and_tag
93
+ ```
62
94
 
63
95
  The count of the associated models in common is accessible on each returned model:
64
96
 
65
- post.similar_posts_model_count
66
- post.similar_posts_by_tag_model_count
67
- post.similar_posts_by_author_and_tag_model_count
97
+ ```ruby
98
+ post.similar_posts_commonality_count
99
+ post.similar_posts_by_tag_commonality_count
100
+ post.similar_posts_by_author_and_tag_commonality_count
101
+ ```
68
102
 
69
- Note multiple associations do not work with sqlite.
103
+ **Note multiple associations for the instance method do not work with sqlite.**
70
104
 
71
- Because of the use of `group`, pagination is not supported.
105
+ **Pagination is not supported on the instance method due to the use of `group by`.**
72
106
 
73
107
  ## In conjunction with acts-as-taggable-on
74
108
 
75
- If you use https://github.com/mbleigh/acts-as-taggable-on/#usage and want to find related users say across multiple contexts:
109
+ If you use [mbleigh/acts-as-taggable-on](https://github.com/mbleigh/acts-as-taggable-on/#usage) and want to find related users say across multiple contexts:
76
110
 
77
- user.similar_users.where(taggings: { context: %w(skills interests) })
111
+ ```ruby
112
+ user.similar_users.where(taggings: { context: %w(skills interests) })
113
+ ```
78
114
 
79
115
  ## Contributing
80
116
 
@@ -1,3 +1,3 @@
1
1
  module SimilarModels
2
- VERSION = '0.2.1'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -3,9 +3,45 @@ require 'similar_models/version'
3
3
  module SimilarModels
4
4
 
5
5
  def has_similar_models(*many_to_many_associations, as: nil)
6
- as = "similar_#{model_name.plural}" unless as
6
+ as ||= "similar_#{model_name.plural}"
7
7
 
8
- # defaults to 'def similar_{model name}'
8
+ # example sql query for one many to many association:
9
+ #
10
+ # SELECT posts.*,
11
+ # (select count(*) from author_posts where post_id != posts.alt_id and author_id in
12
+ # (select author_id from author_posts where post_id = posts.alt_id)) AS similar_posts_commonality_count
13
+ # FROM "posts"
14
+ # ORDER BY similar_posts_commonality_count DESC, created_at DESC
15
+ #
16
+ define_singleton_method as do
17
+ primary_key_ref = "#{table_name}.#{primary_key}"
18
+ similarity_counts = []
19
+
20
+ many_to_many_associations.each do |many_to_many_association|
21
+ association = reflect_on_association(many_to_many_association)
22
+ join_table, foreign_key, association_foreign_key = join_table_values(association)
23
+
24
+ similarity_counts <<
25
+ "(select count(*) from #{join_table} where #{foreign_key} != #{primary_key_ref} and " \
26
+ "#{association_foreign_key} in " \
27
+ "(select #{association_foreign_key} from #{join_table} where #{foreign_key} = #{primary_key_ref}))"
28
+ end
29
+
30
+ order_clause = "#{as}_commonality_count DESC"
31
+ order_clause += ", created_at DESC" if column_names.include?('created_at')
32
+ select("#{table_name}.*, #{similarity_counts.join(' + ')} AS #{as}_commonality_count").order(order_clause)
33
+ end
34
+
35
+ # example sql query for one many to many association:
36
+ #
37
+ # SELECT posts.*, count(posts.alt_id) AS similar_posts_commonality_count
38
+ # FROM "posts"
39
+ # INNER JOIN author_posts ON author_posts.post_id = posts.alt_id
40
+ # WHERE "posts"."alt_id" != ? AND
41
+ # author_posts.author_id IN (select author_posts.author_id from author_posts where author_posts.post_id = ?)
42
+ # GROUP BY posts.alt_id, posts.created_at, posts.updated_at
43
+ # ORDER BY similar_posts_commonality_count DESC, created_at DESC
44
+ #
9
45
  define_method as do
10
46
  table_name = self.class.table_name
11
47
  primary_key = self.class.primary_key
@@ -13,8 +49,8 @@ module SimilarModels
13
49
  association_scopes = []
14
50
 
15
51
  many_to_many_associations.each do |many_to_many_association|
16
- assocation = self.class.reflect_on_association(many_to_many_association)
17
- join_table, foreign_key, association_foreign_key = self.class.join_table_values(assocation)
52
+ association = self.class.reflect_on_association(many_to_many_association)
53
+ join_table, foreign_key, association_foreign_key = self.class.join_table_values(association)
18
54
 
19
55
  association_scopes << self.class.where(
20
56
  "#{join_table}.#{association_foreign_key} IN \
@@ -23,15 +59,16 @@ module SimilarModels
23
59
  ).joins("INNER JOIN #{join_table} ON #{join_table}.#{foreign_key} = #{primary_key_ref}")
24
60
  end
25
61
 
26
- scope = self.class.select("#{table_name}.*, count(#{primary_key_ref}) AS #{as}_model_count").
27
- where.not(primary_key => self.id).order("#{as}_model_count DESC")
62
+ order_clause = "#{as}_commonality_count DESC"
63
+ order_clause += ", created_at DESC" if self.class.column_names.include?('created_at')
64
+ scope = self.class.select("#{table_name}.*, count(#{primary_key_ref}) AS #{as}_commonality_count").
65
+ where.not(primary_key => self.id).order(order_clause)
28
66
  group_by_clause = self.class.column_names.map { |column| "#{table_name}.#{column}"}.join(', ')
29
67
 
30
68
  # if there is only one many-to-many association no need to use UNION sql syntax
31
69
  if association_scopes.one?
32
70
  scope.merge(association_scopes.first).group(group_by_clause)
33
71
  else
34
- # see http://blog.ubersense.com/2013/09/27/tech-talk-unioning-scoped-queries-in-rails/
35
72
  scope.from("((#{association_scopes.map(&:to_sql).join(') UNION ALL (')})) AS #{table_name}").group(group_by_clause)
36
73
  end
37
74
  end
@@ -8,8 +8,8 @@ Gem::Specification.new do |spec|
8
8
  spec.version = SimilarModels::VERSION
9
9
  spec.authors = ["Jolyon Pawlyn"]
10
10
  spec.email = ["jpawlyn@gmail.com"]
11
- spec.description = %q{Adds an instance method to an active record model that returns the most similar models based on associated models in common}
12
- spec.summary = %q{Returns models that have the most associated models in common}
11
+ spec.description = %q{Adds a `similar_#{model_name.plural}` instance and class method to an active record model and returns models based on associated models in common ordered by most in common first}
12
+ spec.summary = %q{Returns models that have associated models in common ordered by most in common first}
13
13
  spec.homepage = "https://github.com/jpawlyn/similar_models"
14
14
  spec.license = "MIT"
15
15
 
@@ -18,13 +18,12 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.required_ruby_version = '>= 2.1'
22
- spec.add_runtime_dependency 'activerecord', '>= 4.2'
23
- spec.add_development_dependency 'bundler', '~> 1.16'
21
+ spec.required_ruby_version = '>= 3.1'
22
+ spec.add_runtime_dependency 'activerecord', '>= 7.2'
24
23
  spec.add_development_dependency 'rspec', '~> 3.5'
25
24
  spec.add_development_dependency 'database_cleaner', '~>1.5'
26
- spec.add_development_dependency 'sqlite3', '~>1.3'
25
+ spec.add_development_dependency 'sqlite3', '~>2.6'
27
26
  spec.add_development_dependency 'mysql2', '~>0.5'
28
- spec.add_development_dependency 'pg', '~>1.0'
29
- spec.add_development_dependency 'byebug', '~>9.0'
27
+ spec.add_development_dependency 'pg', '~>1.5'
28
+ spec.add_development_dependency 'debug', '~>1.0'
30
29
  end
data/spec/post_spec.rb CHANGED
@@ -5,34 +5,52 @@ describe Post do
5
5
  let(:author2) { Author.create! }
6
6
  let(:author3) { Author.create! }
7
7
  let(:author4) { Author.create! }
8
+ let(:author5) { Author.create! }
8
9
  let(:tag1) { Tag.create! }
9
10
  let(:tag2) { Tag.create! }
10
11
  let(:tag3) { Tag.create! }
11
12
  let(:tag4) { Tag.create! }
12
13
 
13
14
  context 'has_many through:' do
14
- it 'return posts that have the most authors in common with post' do
15
- post = Post.create! authors: [author1, author2, author3]
16
- post1 = Post.create! authors: [author1, author4]
17
- post2 = Post.create! authors: [author1, author2]
18
- post3 = Post.create! authors: [author4]
19
- post4 = Post.create! authors: [author1, author2, author3]
20
-
21
- expect(post.similar_posts.map(&:similar_posts_model_count)).to eq([3, 2, 1])
22
- expect(post.similar_posts).to eq([post4, post2, post1])
15
+ let!(:post) { Post.create! authors: [author1, author2, author3] }
16
+ let!(:post1) { Post.create! authors: [author1] }
17
+ let!(:post2) { Post.create! authors: [author4] }
18
+ let!(:post3) { Post.create! authors: [author1, author2, author3, author4] }
19
+ let!(:post4) { Post.create! authors: [author5] }
20
+
21
+ describe '.similar_posts' do
22
+ it 'return posts ordered by most authors in common' do
23
+ expect(described_class.similar_posts.map(&:similar_posts_commonality_count)).to eq([5, 4, 2, 1, 0])
24
+ expect(described_class.similar_posts).to eq([post3, post, post1, post2, post4])
25
+ end
26
+ end
27
+
28
+ describe '#similar_posts' do
29
+ it 'return posts that have authors in common with `post` ordered by most in common first' do
30
+ expect(post.similar_posts.map(&:similar_posts_commonality_count)).to eq([3, 1])
31
+ expect(post.similar_posts).to eq([post3, post1])
32
+ end
23
33
  end
24
34
  end
25
35
 
26
36
  context 'has_and_belongs_to_many' do
27
- it 'return posts that have the most tags in common with post' do
28
- post = Post.create! tags: [tag1, tag2, tag3]
29
- post1 = Post.create! tags: [tag1, tag4]
30
- post2 = Post.create! tags: [tag1, tag2]
31
- post3 = Post.create! tags: [tag4]
32
- post4 = Post.create! tags: [tag1, tag2, tag3]
33
-
34
- expect(post.similar_posts_by_tag.map(&:similar_posts_by_tag_model_count)).to eq([3, 2, 1])
35
- expect(post.similar_posts_by_tag).to eq([post4, post2, post1])
37
+ let!(:post) { Post.create! tags: [tag1, tag2, tag3] }
38
+ let!(:post1) { Post.create! tags: [tag1, tag4] }
39
+ let!(:post2) { Post.create! tags: [tag2] }
40
+ let!(:post3) { Post.create! tags: [tag2, tag3] }
41
+
42
+ describe '.similar_posts_by_tag' do
43
+ it 'return posts ordered by most tags in common' do
44
+ expect(described_class.similar_posts_by_tag.map(&:similar_posts_by_tag_commonality_count)).to eq([4, 3, 2, 1])
45
+ expect(described_class.similar_posts_by_tag).to eq([post, post3, post2, post1])
46
+ end
47
+ end
48
+
49
+ describe '#similar_posts_by_tag' do
50
+ it 'return posts that have tags in common with `post` ordered by most in common first' do
51
+ expect(post.similar_posts_by_tag.map(&:similar_posts_by_tag_commonality_count)).to eq([2, 1, 1])
52
+ expect(post.similar_posts_by_tag).to eq([post3, post2, post1])
53
+ end
36
54
  end
37
55
  end
38
56
 
@@ -43,9 +61,19 @@ describe Post do
43
61
  let!(:post3) { Post.create! tags: [tag4] }
44
62
  let!(:post4) { Post.create! authors: [author1], tags: [tag1, tag2, tag3] }
45
63
 
46
- it 'return posts that have the most authors and tags in common with post' do
47
- expect(post.similar_posts_by_author_and_tag.map(&:similar_posts_by_author_and_tag_model_count)).to eq([5, 4, 1])
48
- expect(post.similar_posts_by_author_and_tag).to eq([post1, post4, post2])
64
+ describe '.similar_posts_by_author_and_tag' do
65
+ it 'return posts ordered by most authors and tags in common' do
66
+ expect(described_class.similar_posts_by_author_and_tag.map(&:similar_posts_by_author_and_tag_commonality_count))
67
+ .to eq([10, 10, 8, 3, 1])
68
+ expect(described_class.similar_posts_by_author_and_tag).to eq([post1, post, post4, post2, post3])
69
+ end
70
+ end
71
+
72
+ describe '#similar_posts_by_author_and_tag' do
73
+ it 'return posts that have authors and tags in common with `post` ordered by most in common first' do
74
+ expect(post.similar_posts_by_author_and_tag.map(&:similar_posts_by_author_and_tag_commonality_count)).to eq([5, 4, 1])
75
+ expect(post.similar_posts_by_author_and_tag).to eq([post1, post4, post2])
76
+ end
49
77
  end
50
78
  end
51
79
  end
data/spec/spec_helper.rb CHANGED
@@ -15,7 +15,7 @@ load 'support/schema.rb'
15
15
  require 'similar_models'
16
16
  require 'support/models'
17
17
  require 'database_cleaner'
18
- require 'byebug'
18
+ require 'debug'
19
19
 
20
20
  DatabaseCleaner.strategy = :transaction
21
21
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: similar_models
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jolyon Pawlyn
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2018-05-10 00:00:00.000000000 Z
10
+ date: 2025-02-25 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activerecord
@@ -16,28 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '4.2'
18
+ version: '7.2'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
- version: '4.2'
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1.16'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.16'
25
+ version: '7.2'
41
26
  - !ruby/object:Gem::Dependency
42
27
  name: rspec
43
28
  requirement: !ruby/object:Gem::Requirement
@@ -72,14 +57,14 @@ dependencies:
72
57
  requirements:
73
58
  - - "~>"
74
59
  - !ruby/object:Gem::Version
75
- version: '1.3'
60
+ version: '2.6'
76
61
  type: :development
77
62
  prerelease: false
78
63
  version_requirements: !ruby/object:Gem::Requirement
79
64
  requirements:
80
65
  - - "~>"
81
66
  - !ruby/object:Gem::Version
82
- version: '1.3'
67
+ version: '2.6'
83
68
  - !ruby/object:Gem::Dependency
84
69
  name: mysql2
85
70
  requirement: !ruby/object:Gem::Requirement
@@ -100,30 +85,31 @@ dependencies:
100
85
  requirements:
101
86
  - - "~>"
102
87
  - !ruby/object:Gem::Version
103
- version: '1.0'
88
+ version: '1.5'
104
89
  type: :development
105
90
  prerelease: false
106
91
  version_requirements: !ruby/object:Gem::Requirement
107
92
  requirements:
108
93
  - - "~>"
109
94
  - !ruby/object:Gem::Version
110
- version: '1.0'
95
+ version: '1.5'
111
96
  - !ruby/object:Gem::Dependency
112
- name: byebug
97
+ name: debug
113
98
  requirement: !ruby/object:Gem::Requirement
114
99
  requirements:
115
100
  - - "~>"
116
101
  - !ruby/object:Gem::Version
117
- version: '9.0'
102
+ version: '1.0'
118
103
  type: :development
119
104
  prerelease: false
120
105
  version_requirements: !ruby/object:Gem::Requirement
121
106
  requirements:
122
107
  - - "~>"
123
108
  - !ruby/object:Gem::Version
124
- version: '9.0'
125
- description: Adds an instance method to an active record model that returns the most
126
- similar models based on associated models in common
109
+ version: '1.0'
110
+ description: Adds a `similar_#{model_name.plural}` instance and class method to an
111
+ active record model and returns models based on associated models in common ordered
112
+ by most in common first
127
113
  email:
128
114
  - jpawlyn@gmail.com
129
115
  executables: []
@@ -147,7 +133,6 @@ homepage: https://github.com/jpawlyn/similar_models
147
133
  licenses:
148
134
  - MIT
149
135
  metadata: {}
150
- post_install_message:
151
136
  rdoc_options: []
152
137
  require_paths:
153
138
  - lib
@@ -155,18 +140,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
155
140
  requirements:
156
141
  - - ">="
157
142
  - !ruby/object:Gem::Version
158
- version: '2.1'
143
+ version: '3.1'
159
144
  required_rubygems_version: !ruby/object:Gem::Requirement
160
145
  requirements:
161
146
  - - ">="
162
147
  - !ruby/object:Gem::Version
163
148
  version: '0'
164
149
  requirements: []
165
- rubyforge_project:
166
- rubygems_version: 2.5.2
167
- signing_key:
150
+ rubygems_version: 3.6.5
168
151
  specification_version: 4
169
- summary: Returns models that have the most associated models in common
152
+ summary: Returns models that have associated models in common ordered by most in common
153
+ first
170
154
  test_files:
171
155
  - spec/post_spec.rb
172
156
  - spec/spec_helper.rb