acts_as_votable 0.10.0 → 0.11.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 +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
|
[](https://travis-ci.org/ryanto/acts_as_votable)
|
4
|
+
[](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
|