acts_as_saveable 0.10.1
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 +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +25 -0
- data/Gemfile +17 -0
- data/README.markdown +399 -0
- data/Rakefile +10 -0
- data/acts_as_saveable.gemspec +24 -0
- data/lib/acts_as_saveable.rb +21 -0
- data/lib/acts_as_saveable/extenders/controller.rb +19 -0
- data/lib/acts_as_saveable/extenders/saveable.rb +25 -0
- data/lib/acts_as_saveable/extenders/saver.rb +24 -0
- data/lib/acts_as_saveable/helpers/words.rb +36 -0
- data/lib/acts_as_saveable/save.rb +28 -0
- data/lib/acts_as_saveable/saveable.rb +330 -0
- data/lib/acts_as_saveable/saver.rb +131 -0
- data/lib/acts_as_saveable/version.rb +3 -0
- data/lib/generators/acts_as_saveable/migration/migration_generator.rb +31 -0
- data/lib/generators/acts_as_saveable/migration/templates/active_record/migration.rb +27 -0
- data/spec/saveable_saver_spec.rb +21 -0
- data/spec/saveable_spec.rb +21 -0
- data/spec/saver_spec.rb +21 -0
- data/spec/shared_example/saveable_model_spec.rb +484 -0
- data/spec/shared_example/saver_model_spec.rb +275 -0
- data/spec/spec_helper.rb +132 -0
- data/spec/words_spec.rb +30 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 81a949caf1bba42241830a88be68d00886de121d
|
4
|
+
data.tar.gz: ab7296ff429963a29162f78f2f80fc7e66cea096
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5f257d6d0cb5cee42b9d2c336f1fb1a3c3b64074b14bcead7a71ac71fa6abdc7a4d4d6f3779bdc47de246e957a3ccce9c860d897d18f23c69ce740970d4292b4
|
7
|
+
data.tar.gz: 5c908749e2adbe8e55176ba9f2c435345b20a073adb699d959cf9b8581f825f34439760a444d39e5665aa1ed09d9d83ba307be705fd249300c15f64af9078af2
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 1.8.7
|
4
|
+
- 1.9.2
|
5
|
+
- 1.9.3
|
6
|
+
- 2.0.0
|
7
|
+
- 2.1.0
|
8
|
+
env:
|
9
|
+
- "RAILS_VERSION=3.0.0"
|
10
|
+
- "RAILS_VERSION=3.1.0"
|
11
|
+
- "RAILS_VERSION=3.2.0"
|
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"
|
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in acts_as_saveable.gemspec
|
4
|
+
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
ADDED
@@ -0,0 +1,399 @@
|
|
1
|
+
# Acts As Saveable (aka Acts As Read-Later)
|
2
|
+
|
3
|
+
Acts As Saveable is a Ruby Gem specifically written for Rails/ActiveRecord models.
|
4
|
+
The main goals of this gem are:
|
5
|
+
|
6
|
+
- Allow any model to be saved on, like/dislike, upsaved/downsaved, for later reading and viewing etc.
|
7
|
+
- Allow any model to be saved under arbitrary scopes.
|
8
|
+
- Allow any model to saved. In other words, saves do not have to come from a user,
|
9
|
+
they can come from any model (such as a Group or Team).
|
10
|
+
- Provide an easy to write/read syntax.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
### Supported Ruby and Rails versions
|
15
|
+
|
16
|
+
* Ruby 1.8.7, 1.9.2, 1.9.3
|
17
|
+
* Ruby 2.0.0, 2.1.0
|
18
|
+
* Rails 3.0, 3.1, 3.2
|
19
|
+
* Rails 4.0, 4.1+
|
20
|
+
|
21
|
+
### Install
|
22
|
+
|
23
|
+
Just add the following to your Gemfile.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
gem 'acts_as_saveable', '~> 0.10.0'
|
27
|
+
```
|
28
|
+
|
29
|
+
And follow that up with a ``bundle install``.
|
30
|
+
|
31
|
+
### Database Migrations
|
32
|
+
|
33
|
+
Acts As Saveable uses a saves table to store all saving information. To
|
34
|
+
generate and run the migration just use.
|
35
|
+
|
36
|
+
rails generate acts_as_saveable:migration
|
37
|
+
rake db:migrate
|
38
|
+
|
39
|
+
You will get a performance increase by adding in cached columns to your model's
|
40
|
+
tables. You will have to do this manually through your own migrations. See the
|
41
|
+
caching section of this document for more information.
|
42
|
+
|
43
|
+
## Usage
|
44
|
+
|
45
|
+
### Saveable Models
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
class Post < ActiveRecord::Base
|
49
|
+
acts_as_saveable
|
50
|
+
end
|
51
|
+
|
52
|
+
@post = Post.new(:name => 'my post!')
|
53
|
+
@post.save
|
54
|
+
|
55
|
+
@post.upsaved_by @user
|
56
|
+
@post.saves_for.size # => 1
|
57
|
+
```
|
58
|
+
|
59
|
+
### Like/Dislike Yes/No Up/Down
|
60
|
+
|
61
|
+
Here are some saving examples. All of these calls are valid and acceptable. The
|
62
|
+
more natural calls are the first few examples.
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
@post.upsaved_by @user1
|
66
|
+
@post.downsave_from @user2
|
67
|
+
@post.save_by :saver => @user3
|
68
|
+
@post.save_by :saver => @user4, :saved => 'bad'
|
69
|
+
@post.save_by :saver => @user5, :saved => 'like'
|
70
|
+
```
|
71
|
+
|
72
|
+
By default all saves are positive, so `@user3` has cast a 'good' saved for `@post`.
|
73
|
+
|
74
|
+
`@user1`, `@user3`, and `@user5` all saved in favor of `@post`.
|
75
|
+
|
76
|
+
`@user2` and `@user4` on the other had has saved against `@post`.
|
77
|
+
|
78
|
+
|
79
|
+
Just about any word works for casting a saved in favor or against post. Up/Down,
|
80
|
+
Like/Dislike, Positive/Negative... the list goes on-and-on. Boolean flags `true` and
|
81
|
+
`false` are also applicable.
|
82
|
+
|
83
|
+
Revisiting the previous example of code.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
# positive saves
|
87
|
+
@post.upsaved_by @user1
|
88
|
+
@post.save_by :saver => @user3
|
89
|
+
@post.save_by :saver => @user5, :saved => 'like'
|
90
|
+
|
91
|
+
# negative saves
|
92
|
+
@post.downsave_from @user2
|
93
|
+
@post.save_by :saver => @user2, :saved => 'bad'
|
94
|
+
|
95
|
+
# tally them up!
|
96
|
+
@post.saves_for.size # => 5
|
97
|
+
@post.get_likes.size # => 3
|
98
|
+
@post.get_upsaves.size # => 3
|
99
|
+
@post.get_dislikes.size # => 2
|
100
|
+
@post.get_downsaves.size # => 2
|
101
|
+
```
|
102
|
+
|
103
|
+
Active Record scopes are provided to make life easier.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
@post.saves_for.up.by_type(User)
|
107
|
+
@post.saves_for.down
|
108
|
+
@user1.saves.up
|
109
|
+
@user1.saves.down
|
110
|
+
@user1.saves.up.by_type(Post)
|
111
|
+
```
|
112
|
+
|
113
|
+
Once scoping is complete, you can also trigger a get for the
|
114
|
+
saver/saveable
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
@post.saves_for.up.by_type(User).savers
|
118
|
+
@post.saves_for.down.by_type(User).savers
|
119
|
+
|
120
|
+
@user.saves.up.for_type(Post).saveables
|
121
|
+
@user.saves.up.saveables
|
122
|
+
```
|
123
|
+
|
124
|
+
You can also 'unsaved' a model to remove a previous saved.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
@post.upsaved_by @user1
|
128
|
+
@post.unsaved_by @user1
|
129
|
+
```
|
130
|
+
|
131
|
+
Unsaving works for both positive and negative saves.
|
132
|
+
|
133
|
+
### Examples with scopes
|
134
|
+
|
135
|
+
You can add a scope to your saved
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# positive saves
|
139
|
+
@post.upsaved_by @user1, :save_scope => 'rank'
|
140
|
+
@post.save_by :saver => @user3, :save_scope => 'rank'
|
141
|
+
@post.save_by :saver => @user5, :saved => 'like', :save_scope => 'rank'
|
142
|
+
|
143
|
+
# negative saves
|
144
|
+
@post.downsave_from @user2, :save_scope => 'rank'
|
145
|
+
@post.save_by :saver => @user2, :saved => 'bad', :save_scope => 'rank'
|
146
|
+
|
147
|
+
# tally them up!
|
148
|
+
@post.find_saves_for(:save_scope => 'rank').size # => 5
|
149
|
+
@post.get_likes(:save_scope => 'rank').size # => 3
|
150
|
+
@post.get_upsaves(:save_scope => 'rank').size # => 3
|
151
|
+
@post.get_dislikes(:save_scope => 'rank').size # => 2
|
152
|
+
@post.get_downsaves(:save_scope => 'rank').size # => 2
|
153
|
+
|
154
|
+
# saveable model can be saved under different scopes
|
155
|
+
# by the same user
|
156
|
+
@post.save_by :saver => @user1, :save_scope => 'week'
|
157
|
+
@post.save_by :saver => @user1, :save_scope => 'month'
|
158
|
+
|
159
|
+
@post.saves_for.size # => 2
|
160
|
+
@post.find_saves_for(:save_scope => 'week').size # => 1
|
161
|
+
@post.find_saves_for(:save_scope => 'month').size # => 1
|
162
|
+
```
|
163
|
+
### Adding weights to your saves
|
164
|
+
|
165
|
+
You can add weight to your saved. The default value is 1.
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
# positive saves
|
169
|
+
@post.upsaved_by @user1, :save_weight => 1
|
170
|
+
@post.save_by :saver => @user3, :save_weight => 2
|
171
|
+
@post.save_by :saver => @user5, :saved => 'like', :save_scope => 'rank', :save_weight => 3
|
172
|
+
|
173
|
+
# negative saves
|
174
|
+
@post.downsave_from @user2, :save_scope => 'rank', :save_weight => 1
|
175
|
+
@post.save_by :saver => @user2, :saved => 'bad', :save_scope => 'rank', :save_weight => 3
|
176
|
+
|
177
|
+
# tally them up!
|
178
|
+
@post.find_saves_for(:save_scope => 'rank').sum(:save_weight) # => 6
|
179
|
+
@post.get_likes(:save_scope => 'rank').sum(:save_weight) # => 6
|
180
|
+
@post.get_upsaves(:save_scope => 'rank').sum(:save_weight) # => 6
|
181
|
+
@post.get_dislikes(:save_scope => 'rank').sum(:save_weight) # => 4
|
182
|
+
@post.get_downsaves(:save_scope => 'rank').sum(:save_weight) # => 4
|
183
|
+
```
|
184
|
+
|
185
|
+
### The Saver
|
186
|
+
|
187
|
+
You can have your savers `acts_as_saver` to provide some reserve functionality.
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
class User < ActiveRecord::Base
|
191
|
+
acts_as_saver
|
192
|
+
end
|
193
|
+
|
194
|
+
@user.likes @article
|
195
|
+
|
196
|
+
@article.saves.size # => 1
|
197
|
+
@article.likes.size # => 1
|
198
|
+
@article.dislikes.size # => 0
|
199
|
+
```
|
200
|
+
|
201
|
+
To check if a saver has saved on a model, you can use ``saved_for?``. You can
|
202
|
+
check how the saver saved by using ``saved_as_when_saved_for``.
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
@user.likes @comment1
|
206
|
+
@user.up_saves @comment2
|
207
|
+
# user has not saved on @comment3
|
208
|
+
|
209
|
+
@user.saved_for? @comment1 # => true
|
210
|
+
@user.saved_for? @comment2 # => true
|
211
|
+
@user.saved_for? @comment3 # => false
|
212
|
+
|
213
|
+
@user.saved_as_when_saved_for @comment1 # => true, he liked it
|
214
|
+
@user.saved_as_when_saved_for @comment2 # => false, he didnt like it
|
215
|
+
@user.saved_as_when_saved_for @comment3 # => nil, he has yet to saved
|
216
|
+
```
|
217
|
+
|
218
|
+
You can also check whether the saver has saved up or down.
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
@user.likes @comment1
|
222
|
+
@user.dislikes @comment2
|
223
|
+
# user has not saved on @comment3
|
224
|
+
|
225
|
+
@user.saved_up_on? @comment1 # => true
|
226
|
+
@user.saved_down_on? @comment1 # => false
|
227
|
+
|
228
|
+
@user.saved_down_on? @comment2 # => true
|
229
|
+
@user.saved_up_on? @comment2 # => false
|
230
|
+
|
231
|
+
@user.saved_up_on? @comment3 # => false
|
232
|
+
@user.saved_down_on? @comment3 # => false
|
233
|
+
```
|
234
|
+
|
235
|
+
Aliases for methods `saved_up_on?` and `saved_down_on?` are: `saved_up_for?`, `saved_down_for?`, `liked?` and `disliked?`.
|
236
|
+
|
237
|
+
Also, you can obtain a list of all the objects a user has saved for.
|
238
|
+
This returns the actual objects instead of instances of the Vote model.
|
239
|
+
All objects are eager loaded
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
@user.find_saved_items
|
243
|
+
|
244
|
+
@user.find_up_saved_items
|
245
|
+
@user.find_liked_items
|
246
|
+
|
247
|
+
@user.find_down_saved_items
|
248
|
+
@user.find_disliked_items
|
249
|
+
```
|
250
|
+
|
251
|
+
Members of an individual model that a user has saved for can also be
|
252
|
+
displayed. The result is an ActiveRecord Relation.
|
253
|
+
|
254
|
+
```ruby
|
255
|
+
@user.get_saved Comment
|
256
|
+
|
257
|
+
@user.get_up_saved Comment
|
258
|
+
|
259
|
+
@user.get_down_saved Comment
|
260
|
+
```
|
261
|
+
|
262
|
+
### Registered Votes
|
263
|
+
|
264
|
+
Savers can only saved once per model. In this example the 2nd saved does not count
|
265
|
+
because `@user` has already saved for `@shoe`.
|
266
|
+
|
267
|
+
```ruby
|
268
|
+
@user.likes @shoe
|
269
|
+
@user.likes @shoe
|
270
|
+
|
271
|
+
@shoe.saves # => 1
|
272
|
+
@shoe.likes # => 1
|
273
|
+
```
|
274
|
+
|
275
|
+
To check if a saved counted, or registered, use `save_registered?` on your model
|
276
|
+
after saving. For example:
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
@hat.upsaved_by @user
|
280
|
+
@hat.save_registered? # => true
|
281
|
+
|
282
|
+
@hat.upsaved_by => @user
|
283
|
+
@hat.save_registered? # => false, because @user has already saved this way
|
284
|
+
|
285
|
+
@hat.dissaved_by @user
|
286
|
+
@hat.save_registered? # => true, because user changed their saved
|
287
|
+
|
288
|
+
@hat.saves.size # => 1
|
289
|
+
@hat.positives.size # => 0
|
290
|
+
@hat.negatives.size # => 1
|
291
|
+
```
|
292
|
+
|
293
|
+
To permit duplicates entries of a same saver, use option duplicate. Also notice that this
|
294
|
+
will limit some other methods that didn't deal with multiples saves, in this case, the last saved will be considered.
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
@hat.save_by saver: @user, :duplicate => true
|
298
|
+
```
|
299
|
+
|
300
|
+
## Caching
|
301
|
+
|
302
|
+
To speed up perform you can add cache columns to your saveable model's table. These
|
303
|
+
columns will automatically be updated after each saved. For example, if we wanted
|
304
|
+
to speed up @post we would use the following migration:
|
305
|
+
|
306
|
+
```ruby
|
307
|
+
class AddCachedVotesToPosts < ActiveRecord::Migration
|
308
|
+
def self.up
|
309
|
+
add_column :posts, :cached_saves_total, :integer, :default => 0
|
310
|
+
add_column :posts, :cached_saves_score, :integer, :default => 0
|
311
|
+
add_column :posts, :cached_saves_up, :integer, :default => 0
|
312
|
+
add_column :posts, :cached_saves_down, :integer, :default => 0
|
313
|
+
add_column :posts, :cached_weighted_score, :integer, :default => 0
|
314
|
+
add_column :posts, :cached_weighted_total, :integer, :default => 0
|
315
|
+
add_column :posts, :cached_weighted_average, :float, :default => 0.0
|
316
|
+
add_index :posts, :cached_saves_total
|
317
|
+
add_index :posts, :cached_saves_score
|
318
|
+
add_index :posts, :cached_saves_up
|
319
|
+
add_index :posts, :cached_saves_down
|
320
|
+
add_index :posts, :cached_weighted_score
|
321
|
+
add_index :posts, :cached_weighted_total
|
322
|
+
add_index :posts, :cached_weighted_average
|
323
|
+
|
324
|
+
# Uncomment this line to force caching of existing saves
|
325
|
+
# Post.find_each(&:update_cached_saves)
|
326
|
+
end
|
327
|
+
|
328
|
+
def self.down
|
329
|
+
remove_column :posts, :cached_saves_total
|
330
|
+
remove_column :posts, :cached_saves_score
|
331
|
+
remove_column :posts, :cached_saves_up
|
332
|
+
remove_column :posts, :cached_saves_down
|
333
|
+
remove_column :posts, :cached_weighted_score
|
334
|
+
remove_column :posts, :cached_weighted_total
|
335
|
+
remove_column :posts, :cached_weighted_average
|
336
|
+
end
|
337
|
+
end
|
338
|
+
```
|
339
|
+
|
340
|
+
`cached_weighted_average` can be helpful for a rating system, e.g.:
|
341
|
+
|
342
|
+
Order by average rating:
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
Post.order(:cached_weighted_average => :desc)
|
346
|
+
```
|
347
|
+
|
348
|
+
Display average rating:
|
349
|
+
|
350
|
+
```erb
|
351
|
+
<%= post.weighted_average.round(2) %> / 5
|
352
|
+
<!-- 3.5 / 5 -->
|
353
|
+
```
|
354
|
+
|
355
|
+
## Testing
|
356
|
+
|
357
|
+
All tests follow the RSpec format and are located in the spec directory.
|
358
|
+
They can be run with:
|
359
|
+
|
360
|
+
```
|
361
|
+
rake spec
|
362
|
+
```
|
363
|
+
|
364
|
+
## Changes
|
365
|
+
|
366
|
+
### Fixes for saveable saver model
|
367
|
+
|
368
|
+
In version 0.8.0, there are bugs for a model that is both saveable and saver.
|
369
|
+
Some name-conflicting methods are renamed:
|
370
|
+
+ Renamed Saveable.saves to saves_for
|
371
|
+
+ Renamed Saveable.saved to save_by,
|
372
|
+
+ Removed Saveable.save_by alias (was an alias for :save_up)
|
373
|
+
+ Renamed Saveable.unsave_for to unsave_by
|
374
|
+
+ Renamed Saveable.find_saves to find_saves_for
|
375
|
+
+ Renamed Saveable.up_saves to get_upsaves
|
376
|
+
+ and its aliases :get_true_saves, :get_ups, :get_upsaves, :get_likes, :get_positives, :get_for_saves
|
377
|
+
+ Renamed Saveable.down_saves to get_downsaves
|
378
|
+
+ and its aliases :get_false_saves, :get_downs, :get_downsaves, :get_dislikes, :get_negatives
|
379
|
+
|
380
|
+
|
381
|
+
## License
|
382
|
+
|
383
|
+
Acts as saveable is released under the [MIT
|
384
|
+
License](http://www.opensource.org/licenses/MIT).
|
385
|
+
|
386
|
+
## TODO
|
387
|
+
|
388
|
+
- Pass in a block of options when creating acts_as. Allow for things
|
389
|
+
like disabling the aliasing
|
390
|
+
|
391
|
+
- Smarter language syntax. Example: `@user.likes` will return all of the saveables
|
392
|
+
that the user likes, while `@user.likes @model` will cast a saved for @model by
|
393
|
+
@user.
|
394
|
+
|
395
|
+
|
396
|
+
- The aliased methods are referred to by using the terms 'up/down' and/or
|
397
|
+
'true/false'. Need to come up with guidelines for naming these methods.
|
398
|
+
|
399
|
+
- Create more aliases. Specifically for counting saves and finding saves.
|