acts_as_votable 0.10.0 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +9 -6
- data/.rubocop.yml +121 -0
- data/.travis.yml +12 -23
- data/Appraisals +13 -0
- data/Gemfile +3 -14
- data/README.markdown +80 -13
- data/Rakefile +6 -4
- data/acts_as_votable.gemspec +12 -5
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_4.gemfile.lock +159 -0
- data/gemfiles/rails_5.gemfile +7 -0
- data/gemfiles/rails_5.gemfile.lock +166 -0
- data/gemfiles/rails_5_1.gemfile +7 -0
- data/gemfiles/rails_5_1.gemfile.lock +166 -0
- data/lib/acts_as_votable.rb +9 -9
- data/lib/acts_as_votable/cacheable.rb +174 -0
- data/lib/acts_as_votable/extenders/controller.rb +3 -4
- data/lib/acts_as_votable/extenders/votable.rb +17 -6
- data/lib/acts_as_votable/extenders/voter.rb +4 -6
- data/lib/acts_as_votable/helpers/words.rb +7 -10
- data/lib/acts_as_votable/version.rb +3 -1
- data/lib/acts_as_votable/votable.rb +50 -187
- data/lib/acts_as_votable/vote.rb +10 -12
- data/lib/acts_as_votable/voter.rb +52 -53
- data/lib/generators/acts_as_votable/migration/migration_generator.rb +19 -4
- data/lib/generators/acts_as_votable/migration/templates/active_record/{migration.rb → migration.erb} +1 -6
- data/spec/factories/votable.rb +6 -0
- data/spec/factories/votable_cache.rb +6 -0
- data/spec/factories/votable_cache_update_attributes.rb +6 -0
- data/spec/factories/votable_cache_update_columns.rb +6 -0
- data/spec/factories/votable_child_of_sti_not_votable.rb +6 -0
- data/spec/factories/votable_child_of_sti_votable.rb +6 -0
- data/spec/factories/votable_voter.rb +6 -0
- data/spec/factories/vote.rb +6 -0
- data/spec/factories/voter.rb +6 -0
- data/spec/generators/active_record_generator_spec.rb +13 -0
- data/spec/shared_example/votable_model.rb +506 -0
- data/spec/shared_example/voter_model.rb +280 -0
- data/spec/spec_helper.rb +28 -18
- data/spec/support/factory_girl.rb +10 -0
- data/spec/votable_spec.rb +10 -9
- data/spec/votable_voter_spec.rb +12 -12
- data/spec/voter_spec.rb +9 -10
- data/spec/words_spec.rb +9 -12
- metadata +98 -27
- data/spec/shared_example/votable_model_spec.rb +0 -421
- data/spec/shared_example/voter_model_spec.rb +0 -279
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec62f24bb2a85474f561c476a47007e333dae16f
|
4
|
+
data.tar.gz: ab88ccd51c8d63225b05b7f6443056cd0defe2a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be0133a6030a9c0d83a6753b82360c100ed998a608f98829fd97bc4cbbf27f61990fb1bbc9eac9b9b46b0d4d7e122556e3fce47eb59eb7bf5d42a71a1f895512
|
7
|
+
data.tar.gz: ec1ee10e398a40bb5b20a568265334194e29ca8b05b75a8e8f8c87076e9d187f72225124704af321ffa3b615853906b51daf96a4837074f97bdda4cbe27a28a2
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.2
|
3
|
+
# RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
|
4
|
+
# to ignore them, so only the ones explicitly set in this file are enabled.
|
5
|
+
DisabledByDefault: true
|
6
|
+
|
7
|
+
# Prefer &&/|| over and/or.
|
8
|
+
Style/AndOr:
|
9
|
+
Enabled: true
|
10
|
+
|
11
|
+
# Do not use braces for hash literals when they are the last argument of a
|
12
|
+
# method call.
|
13
|
+
Style/BracesAroundHashParameters:
|
14
|
+
Enabled: true
|
15
|
+
EnforcedStyle: context_dependent
|
16
|
+
|
17
|
+
# Align `when` with `case`.
|
18
|
+
Layout/CaseIndentation:
|
19
|
+
Enabled: true
|
20
|
+
|
21
|
+
# Align comments with method definitions.
|
22
|
+
Layout/CommentIndentation:
|
23
|
+
Enabled: true
|
24
|
+
|
25
|
+
Layout/EmptyLineAfterMagicComment:
|
26
|
+
Enabled: true
|
27
|
+
|
28
|
+
# In a regular class definition, no empty lines around the body.
|
29
|
+
Layout/EmptyLinesAroundClassBody:
|
30
|
+
Enabled: true
|
31
|
+
|
32
|
+
# In a regular method definition, no empty lines around the body.
|
33
|
+
Layout/EmptyLinesAroundMethodBody:
|
34
|
+
Enabled: true
|
35
|
+
|
36
|
+
# In a regular module definition, no empty lines around the body.
|
37
|
+
Layout/EmptyLinesAroundModuleBody:
|
38
|
+
Enabled: true
|
39
|
+
|
40
|
+
Layout/FirstParameterIndentation:
|
41
|
+
Enabled: true
|
42
|
+
|
43
|
+
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
|
44
|
+
Style/HashSyntax:
|
45
|
+
Enabled: true
|
46
|
+
|
47
|
+
# Two spaces, no tabs (for indentation).
|
48
|
+
Layout/IndentationWidth:
|
49
|
+
Enabled: true
|
50
|
+
|
51
|
+
Layout/SpaceAfterColon:
|
52
|
+
Enabled: true
|
53
|
+
|
54
|
+
Layout/SpaceAfterComma:
|
55
|
+
Enabled: true
|
56
|
+
|
57
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
58
|
+
Enabled: true
|
59
|
+
|
60
|
+
Layout/SpaceAroundKeyword:
|
61
|
+
Enabled: true
|
62
|
+
|
63
|
+
Layout/SpaceAroundOperators:
|
64
|
+
Enabled: true
|
65
|
+
|
66
|
+
Layout/SpaceBeforeFirstArg:
|
67
|
+
Enabled: true
|
68
|
+
|
69
|
+
# Defining a method with parameters needs parentheses.
|
70
|
+
Style/MethodDefParentheses:
|
71
|
+
Enabled: true
|
72
|
+
|
73
|
+
Style/FrozenStringLiteralComment:
|
74
|
+
Enabled: true
|
75
|
+
EnforcedStyle: always
|
76
|
+
|
77
|
+
# Use `foo {}` not `foo{}`.
|
78
|
+
Layout/SpaceBeforeBlockBraces:
|
79
|
+
Enabled: true
|
80
|
+
|
81
|
+
# Use `foo { bar }` not `foo {bar}`.
|
82
|
+
Layout/SpaceInsideBlockBraces:
|
83
|
+
Enabled: true
|
84
|
+
|
85
|
+
# Use `{ a: 1 }` not `{a:1}`.
|
86
|
+
Layout/SpaceInsideHashLiteralBraces:
|
87
|
+
Enabled: true
|
88
|
+
|
89
|
+
Layout/SpaceInsideParens:
|
90
|
+
Enabled: true
|
91
|
+
|
92
|
+
# Check quotes usage according to lint rule below.
|
93
|
+
Style/StringLiterals:
|
94
|
+
Enabled: true
|
95
|
+
EnforcedStyle: double_quotes
|
96
|
+
|
97
|
+
# Detect hard tabs, no hard tabs.
|
98
|
+
Layout/Tab:
|
99
|
+
Enabled: true
|
100
|
+
|
101
|
+
# Blank lines should not have any spaces.
|
102
|
+
Layout/TrailingBlankLines:
|
103
|
+
Enabled: true
|
104
|
+
|
105
|
+
# No trailing whitespace.
|
106
|
+
Layout/TrailingWhitespace:
|
107
|
+
Enabled: true
|
108
|
+
|
109
|
+
# Use quotes for string literals when they are enough.
|
110
|
+
Style/UnneededPercentQ:
|
111
|
+
Enabled: true
|
112
|
+
|
113
|
+
# Align `end` with the matching keyword or starting expression except for
|
114
|
+
# assignments, where it should be aligned with the LHS.
|
115
|
+
Lint/EndAlignment:
|
116
|
+
Enabled: true
|
117
|
+
EnforcedStyleAlignWith: variable
|
118
|
+
|
119
|
+
# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
|
120
|
+
Lint/RequireParentheses:
|
121
|
+
Enabled: true
|
data/.travis.yml
CHANGED
@@ -1,25 +1,14 @@
|
|
1
1
|
language: ruby
|
2
|
+
|
3
|
+
cache: bundler
|
4
|
+
|
2
5
|
rvm:
|
3
|
-
-
|
4
|
-
-
|
5
|
-
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
-
|
10
|
-
-
|
11
|
-
|
12
|
-
- "RAILS_VERSION=4.0.0"
|
13
|
-
- "RAILS_VERSION=4.1.0"
|
14
|
-
matrix:
|
15
|
-
exclude:
|
16
|
-
- rvm: 1.8.7
|
17
|
-
env: "RAILS_VERSION=4.0.0"
|
18
|
-
- rvm: 1.9.2
|
19
|
-
env: "RAILS_VERSION=4.0.0"
|
20
|
-
- rvm: 1.8.7
|
21
|
-
env: "RAILS_VERSION=4.1.0"
|
22
|
-
- rvm: 1.9.2
|
23
|
-
env: "RAILS_VERSION=4.1.0"
|
24
|
-
- rvm: 1.9.3
|
25
|
-
env: "RAILS_VERSION=4.1.0"
|
6
|
+
- 2.2.7
|
7
|
+
- 2.3.4
|
8
|
+
- 2.4.1
|
9
|
+
|
10
|
+
gemfile:
|
11
|
+
- gemfiles/rails_4.gemfile
|
12
|
+
- gemfiles/rails_5.gemfile
|
13
|
+
- gemfiles/rails_5_1.gemfile
|
14
|
+
|
data/Appraisals
ADDED
data/Gemfile
CHANGED
@@ -1,17 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
2
4
|
|
3
5
|
# Specify your gem's dependencies in acts_as_votable.gemspec
|
4
6
|
gemspec
|
5
|
-
|
6
|
-
rails_version = ENV['RAILS_VERSION'] || 'default'
|
7
|
-
|
8
|
-
rails = case rails_version
|
9
|
-
when 'master'
|
10
|
-
{ :github => 'rails/rails'}
|
11
|
-
when 'default'
|
12
|
-
'~> 3.2.0'
|
13
|
-
else
|
14
|
-
"~> #{rails_version}"
|
15
|
-
end
|
16
|
-
|
17
|
-
gem 'rails', rails
|
data/README.markdown
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Acts As Votable (aka Acts As Likeable)
|
2
2
|
|
3
3
|
[![Build Status](https://travis-ci.org/ryanto/acts_as_votable.png)](https://travis-ci.org/ryanto/acts_as_votable)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/ryanto/acts_as_votable.png)](https://codeclimate.com/github/ryanto/acts_as_votable)
|
4
5
|
|
5
6
|
Acts As Votable is a Ruby Gem specifically written for Rails/ActiveRecord models.
|
6
7
|
The main goals of this gem are:
|
@@ -15,14 +16,13 @@ The main goals of this gem are:
|
|
15
16
|
|
16
17
|
### Supported Ruby and Rails versions
|
17
18
|
|
18
|
-
* Ruby
|
19
|
-
* Ruby 2.0.0, 2.1.0
|
20
|
-
* Rails 3.0, 3.1, 3.2
|
19
|
+
* Ruby 2.2.0, 2.3.0 and 2.4.0
|
21
20
|
* Rails 4.0, 4.1+
|
21
|
+
* Rails 5.0, 5.1
|
22
22
|
|
23
23
|
### Install
|
24
24
|
|
25
|
-
Just add the following to your Gemfile.
|
25
|
+
Just add the following to your Gemfile to install the latest release.
|
26
26
|
|
27
27
|
```ruby
|
28
28
|
gem 'acts_as_votable', '~> 0.10.0'
|
@@ -96,10 +96,12 @@ Revisiting the previous example of code.
|
|
96
96
|
|
97
97
|
# tally them up!
|
98
98
|
@post.votes_for.size # => 5
|
99
|
+
@post.weighted_total => 5
|
99
100
|
@post.get_likes.size # => 3
|
100
101
|
@post.get_upvotes.size # => 3
|
101
102
|
@post.get_dislikes.size # => 2
|
102
103
|
@post.get_downvotes.size # => 2
|
104
|
+
@post.weighted_score => 1
|
103
105
|
```
|
104
106
|
|
105
107
|
Active Record scopes are provided to make life easier.
|
@@ -109,7 +111,7 @@ Active Record scopes are provided to make life easier.
|
|
109
111
|
@post.votes_for.down
|
110
112
|
@user1.votes.up
|
111
113
|
@user1.votes.down
|
112
|
-
@user1.votes.up.
|
114
|
+
@user1.votes.up.for_type(Post)
|
113
115
|
```
|
114
116
|
|
115
117
|
Once scoping is complete, you can also trigger a get for the
|
@@ -137,7 +139,7 @@ Unvoting works for both positive and negative votes.
|
|
137
139
|
|
138
140
|
### Examples with scopes
|
139
141
|
|
140
|
-
You can add
|
142
|
+
You can add a scope to your vote
|
141
143
|
|
142
144
|
```ruby
|
143
145
|
# positive votes
|
@@ -215,9 +217,9 @@ check how the voter voted by using ``voted_as_when_voted_for``.
|
|
215
217
|
@user.voted_for? @comment2 # => true
|
216
218
|
@user.voted_for? @comment3 # => false
|
217
219
|
|
218
|
-
@user.voted_as_when_voted_for @comment1 # => true,
|
219
|
-
@user.voted_as_when_voted_for @comment2 # => false,
|
220
|
-
@user.voted_as_when_voted_for @comment3 # => nil,
|
220
|
+
@user.voted_as_when_voted_for @comment1 # => true, user liked it
|
221
|
+
@user.voted_as_when_voted_for @comment2 # => false, user didnt like it
|
222
|
+
@user.voted_as_when_voted_for @comment3 # => nil, user has yet to vote
|
221
223
|
```
|
222
224
|
|
223
225
|
You can also check whether the voter has voted up or down.
|
@@ -317,12 +319,14 @@ class AddCachedVotesToPosts < ActiveRecord::Migration
|
|
317
319
|
add_column :posts, :cached_votes_down, :integer, :default => 0
|
318
320
|
add_column :posts, :cached_weighted_score, :integer, :default => 0
|
319
321
|
add_column :posts, :cached_weighted_total, :integer, :default => 0
|
322
|
+
add_column :posts, :cached_weighted_average, :float, :default => 0.0
|
320
323
|
add_index :posts, :cached_votes_total
|
321
324
|
add_index :posts, :cached_votes_score
|
322
325
|
add_index :posts, :cached_votes_up
|
323
326
|
add_index :posts, :cached_votes_down
|
324
327
|
add_index :posts, :cached_weighted_score
|
325
328
|
add_index :posts, :cached_weighted_total
|
329
|
+
add_index :posts, :cached_weighted_average
|
326
330
|
|
327
331
|
# Uncomment this line to force caching of existing votes
|
328
332
|
# Post.find_each(&:update_cached_votes)
|
@@ -335,10 +339,74 @@ class AddCachedVotesToPosts < ActiveRecord::Migration
|
|
335
339
|
remove_column :posts, :cached_votes_down
|
336
340
|
remove_column :posts, :cached_weighted_score
|
337
341
|
remove_column :posts, :cached_weighted_total
|
342
|
+
remove_column :posts, :cached_weighted_average
|
338
343
|
end
|
339
344
|
end
|
340
345
|
```
|
341
346
|
|
347
|
+
If you have a scope for your vote, let's say `subscribe`, your migration will be slightly different like below:
|
348
|
+
|
349
|
+
```ruby
|
350
|
+
class AddCachedVotesToPosts < ActiveRecord::Migration
|
351
|
+
def self.up
|
352
|
+
add_column :posts, :cached_scoped_subscribe_votes_total, :integer, :default => 0
|
353
|
+
add_column :posts, :cached_scoped_subscribe_votes_score, :integer, :default => 0
|
354
|
+
add_column :posts, :cached_scoped_subscribe_votes_up, :integer, :default => 0
|
355
|
+
add_column :posts, :cached_scoped_subscribe_votes_down, :integer, :default => 0
|
356
|
+
add_column :posts, :cached_weighted_subscribe_score, :integer, :default => 0
|
357
|
+
add_column :posts, :cached_weighted_subscribe_total, :integer, :default => 0
|
358
|
+
add_column :posts, :cached_weighted_subscribe_average, :float, :default => 0.0
|
359
|
+
add_index :posts, :cached_scoped_subscribe_votes_total
|
360
|
+
add_index :posts, :cached_scoped_subscribe_votes_score
|
361
|
+
add_index :posts, :cached_scoped_subscribe_votes_up
|
362
|
+
add_index :posts, :cached_scoped_subscribe_votes_down
|
363
|
+
add_index :posts, :cached_weighted_subscribe_score
|
364
|
+
add_index :posts, :cached_weighted_subscribe_total
|
365
|
+
add_index :posts, :cached_weighted_subscribe_average
|
366
|
+
end
|
367
|
+
|
368
|
+
def self.down
|
369
|
+
remove_column :posts, :cached_scoped_subscribe_votes_total
|
370
|
+
remove_column :posts, :cached_scoped_subscribe_votes_score
|
371
|
+
remove_column :posts, :cached_scoped_subscribe_votes_up
|
372
|
+
remove_column :posts, :cached_scoped_subscribe_votes_down
|
373
|
+
remove_column :posts, :cached_weighted_subscribe_score
|
374
|
+
remove_column :posts, :cached_weighted_subscribe_total
|
375
|
+
remove_column :posts, :cached_weighted_subscribe_average
|
376
|
+
end
|
377
|
+
end
|
378
|
+
```
|
379
|
+
|
380
|
+
`cached_weighted_average` can be helpful for a rating system, e.g.:
|
381
|
+
|
382
|
+
Order by average rating:
|
383
|
+
|
384
|
+
```ruby
|
385
|
+
Post.order(:cached_weighted_average => :desc)
|
386
|
+
```
|
387
|
+
|
388
|
+
Display average rating:
|
389
|
+
|
390
|
+
```erb
|
391
|
+
<%= post.weighted_average.round(2) %> / 5
|
392
|
+
<!-- 3.5 / 5 -->
|
393
|
+
```
|
394
|
+
## updated_at at Votable model
|
395
|
+
|
396
|
+
You can control whether `updated_at` column of votable model will be touched or
|
397
|
+
not by passing `cacheable_strategy` option to `acts_as_votable` method.
|
398
|
+
|
399
|
+
By default, `update_attributes` strategy is used. Pass `:update_columns` as
|
400
|
+
`cacheable_strategy` if you don't want to touch model's `updated_at` column.
|
401
|
+
```ruby
|
402
|
+
class Post < ActiveRecord::Base
|
403
|
+
acts_as_votable cacheable_strategy: :update_columns
|
404
|
+
end
|
405
|
+
```
|
406
|
+
|
407
|
+
NOTE: this option does not work for ActiveRecord < 3.1
|
408
|
+
|
409
|
+
|
342
410
|
## Testing
|
343
411
|
|
344
412
|
All tests follow the RSpec format and are located in the spec directory.
|
@@ -352,7 +420,7 @@ rake spec
|
|
352
420
|
|
353
421
|
### Fixes for votable voter model
|
354
422
|
|
355
|
-
In version 0.8.0, there
|
423
|
+
In version 0.8.0, there are bugs for a model that is both votable and voter.
|
356
424
|
Some name-conflicting methods are renamed:
|
357
425
|
+ Renamed Votable.votes to votes_for
|
358
426
|
+ Renamed Votable.vote to vote_by,
|
@@ -370,7 +438,7 @@ Some name-conflicting methods are renamed:
|
|
370
438
|
Acts as votable is released under the [MIT
|
371
439
|
License](http://www.opensource.org/licenses/MIT).
|
372
440
|
|
373
|
-
##
|
441
|
+
## Next steps
|
374
442
|
|
375
443
|
- Pass in a block of options when creating acts_as. Allow for things
|
376
444
|
like disabling the aliasing
|
@@ -379,8 +447,7 @@ License](http://www.opensource.org/licenses/MIT).
|
|
379
447
|
that the user likes, while `@user.likes @model` will cast a vote for @model by
|
380
448
|
@user.
|
381
449
|
|
382
|
-
|
383
450
|
- The aliased methods are referred to by using the terms 'up/down' and/or
|
384
|
-
'true/false'.
|
451
|
+
'true/false'. Need to come up with guidelines for naming these methods.
|
385
452
|
|
386
453
|
- Create more aliases. Specifically for counting votes and finding votes.
|
data/Rakefile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler"
|
2
4
|
Bundler::GemHelper.install_tasks
|
3
5
|
|
4
|
-
require
|
6
|
+
require "rspec/core/rake_task"
|
5
7
|
|
6
8
|
desc "Run specs"
|
7
9
|
RSpec::Core::RakeTask.new(:spec)
|
8
10
|
|
9
|
-
desc
|
10
|
-
task :
|
11
|
+
desc "Default: run specs."
|
12
|
+
task default: :spec
|
data/acts_as_votable.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
$:.push File.expand_path("../lib", __FILE__)
|
3
5
|
require "acts_as_votable/version"
|
4
6
|
|
@@ -9,16 +11,21 @@ Gem::Specification.new do |s|
|
|
9
11
|
s.authors = ["Ryan"]
|
10
12
|
s.email = ["ryanto"]
|
11
13
|
s.homepage = "http://rubygems.org/gems/acts_as_votable"
|
12
|
-
s.summary =
|
13
|
-
s.description =
|
14
|
+
s.summary = "Rails gem to allowing records to be votable"
|
15
|
+
s.description = "Rails gem to allowing records to be votable"
|
16
|
+
s.license = "MIT"
|
14
17
|
|
15
18
|
s.rubyforge_project = "acts_as_votable"
|
16
19
|
|
17
20
|
s.files = `git ls-files`.split("\n")
|
18
21
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
20
23
|
s.require_paths = ["lib"]
|
21
24
|
|
22
|
-
s.add_development_dependency "rspec"
|
23
|
-
s.add_development_dependency "sqlite3",
|
25
|
+
s.add_development_dependency "rspec", "~> 3.6"
|
26
|
+
s.add_development_dependency "sqlite3", "~> 1.3"
|
27
|
+
s.add_development_dependency "rubocop", "~> 0.49.1"
|
28
|
+
s.add_development_dependency "simplecov", "~> 0.15.0"
|
29
|
+
s.add_development_dependency "appraisal", "~> 2.2"
|
30
|
+
s.add_development_dependency "factory_girl", "~> 4.8"
|
24
31
|
end
|