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.
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run specs"
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ desc 'Default: run specs.'
10
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "acts_as_saveable/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "acts_as_saveable"
7
+ s.version = ActsAsSaveable::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ali Ibrahim"]
10
+ s.email = ["aliibrahim@gmail.com"]
11
+ s.homepage = "http://rubygems.org/gems/acts_as_saveable"
12
+ s.summary = %q{Rails gem to allowing records to be saveable}
13
+ s.description = %q{Rails gem to allowing records to be saveable}
14
+
15
+ s.rubyforge_project = "acts_as_saveable"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ 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) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "sqlite3", '~> 1.3.9'
24
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_record'
2
+ require 'active_support/inflector'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+ module ActsAsSaveable
7
+
8
+ if defined?(ActiveRecord::Base)
9
+ require 'acts_as_saveable/extenders/saveable'
10
+ require 'acts_as_saveable/extenders/saver'
11
+ require 'acts_as_saveable/save'
12
+ ActiveRecord::Base.extend ActsAsSaveable::Extenders::Saveable
13
+ ActiveRecord::Base.extend ActsAsSaveable::Extenders::Saver
14
+ end
15
+
16
+ end
17
+
18
+ require 'acts_as_saveable/extenders/controller'
19
+ ActiveSupport.on_load(:action_controller) do
20
+ include ActsAsSaveable::Extenders::Controller
21
+ end
@@ -0,0 +1,19 @@
1
+ module ActsAsSaveable
2
+ module Extenders
3
+
4
+ module Controller
5
+
6
+ def saver_params(params_object = params[:saved])
7
+ params_object.permit(:saveable_id, :saveable_type,
8
+ :saver_id, :saver_type,
9
+ :saveable, :saver,
10
+ :save_flag, :save_scope)
11
+ end
12
+
13
+ def saveable_params(params_object = params[:saved])
14
+ params_object.permit(:save_registered)
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module ActsAsSaveable
2
+ module Extenders
3
+
4
+ module Saveable
5
+
6
+ def saveable?
7
+ false
8
+ end
9
+
10
+ def acts_as_saveable
11
+ require 'acts_as_saveable/saveable'
12
+ include ActsAsSaveable::Saveable
13
+
14
+ class_eval do
15
+ def self.saveable?
16
+ true
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ module ActsAsSaveable
2
+ module Extenders
3
+
4
+ module Saver
5
+
6
+ def saver?
7
+ false
8
+ end
9
+
10
+ def acts_as_saver(*args)
11
+ require 'acts_as_saveable/saver'
12
+ include ActsAsSaveable::Saver
13
+
14
+ class_eval do
15
+ def self.saver?
16
+ true
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,36 @@
1
+ module ActsAsSaveable::Helpers
2
+
3
+ # this helper provides methods that help find what words are
4
+ # up saves and what words are down saves
5
+ #
6
+ # It can be called
7
+ #
8
+ # saveable_object.saveable_words.that_mean_true
9
+ #
10
+ module Words
11
+
12
+ def saveable_words
13
+ SaveableWords
14
+ end
15
+
16
+ end
17
+
18
+ class SaveableWords
19
+
20
+ def self.that_mean_true
21
+ ['up', 'upsaved', 'like', 'liked', 'positive', 'yes', 'good', 'true', 1, true]
22
+ end
23
+
24
+ def self.that_mean_false
25
+ ['down', 'downsaved', 'dislike', 'disliked', 'negative', 'no', 'bad', 'false', 0, false]
26
+ end
27
+
28
+ # check is word is a true or bad saved
29
+ # if the word is unknown, then it counts it as a true/good
30
+ # saved. this exists to allow all saving to be good by default
31
+ def self.meaning_of word
32
+ !that_mean_false.include?(word)
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ require 'acts_as_saveable/helpers/words'
2
+
3
+ module ActsAsSaveable
4
+ class Save < ::ActiveRecord::Base
5
+
6
+ include Helpers::Words
7
+
8
+ if defined?(ProtectedAttributes) || ::ActiveRecord::VERSION::MAJOR < 4
9
+ attr_accessible :saveable_id, :saveable_type,
10
+ :saver_id, :saver_type,
11
+ :saveable, :saver,
12
+ :save_flag, :save_scope
13
+ end
14
+
15
+ belongs_to :saveable, :polymorphic => true
16
+ belongs_to :saver, :polymorphic => true
17
+
18
+ scope :up, lambda{ where(:save_flag => true) }
19
+ scope :down, lambda{ where(:save_flag => false) }
20
+ scope :for_type, lambda{ |klass| where(:saveable_type => klass) }
21
+ scope :by_type, lambda{ |klass| where(:saver_type => klass) }
22
+
23
+ validates_presence_of :saveable_id
24
+ validates_presence_of :saver_id
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,330 @@
1
+ require 'acts_as_saveable/helpers/words'
2
+
3
+ module ActsAsSaveable
4
+ module Saveable
5
+
6
+ include Helpers::Words
7
+
8
+ def self.included base
9
+
10
+ # allow the user to define these himself
11
+ aliases = {
12
+
13
+ :save_up => [
14
+ :upsaved_by, :upsave_from, :upsave_by, :save_from
15
+ ],
16
+
17
+ :save_down => [
18
+ :downsave_by, :downsave_from, :downsaved_by, :downsave_from
19
+ ],
20
+
21
+ :get_up_saves => [
22
+ :get_true_saves, :get_upsaves, :get_for_saves
23
+ ],
24
+
25
+ :get_down_saves => [
26
+ :get_false_saves, :get_downsaves
27
+ ],
28
+ :unsave_by => [
29
+ :unsave_up, :unsave_down
30
+ ]
31
+ }
32
+
33
+ base.class_eval do
34
+ has_many :saves_for, :class_name => 'ActsAsSaveable::Save', :as => :saveable, :dependent => :destroy do
35
+ def savers
36
+ includes(:saver).map(&:saver)
37
+ end
38
+ end
39
+
40
+ aliases.each do |method, links|
41
+ links.each do |new_method|
42
+ alias_method(new_method, method)
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+
49
+ attr_accessor :save_registered
50
+
51
+ def save_registered?
52
+ return self.save_registered
53
+ end
54
+
55
+ def default_conditions
56
+ {
57
+ :saveable_id => self.id,
58
+ :saveable_type => self.class.base_class.name.to_s
59
+ }
60
+ end
61
+
62
+ # saving
63
+ def save_by args = {}
64
+
65
+ options = {
66
+ :saved => true,
67
+ :save_scope => nil
68
+ }.merge(args)
69
+
70
+ self.save_registered = false
71
+
72
+ if options[:saver].nil?
73
+ return false
74
+ end
75
+
76
+ # find the saved
77
+ _saves_ = find_saves_for({
78
+ :saver_id => options[:saver].id,
79
+ :save_scope => options[:save_scope],
80
+ :saver_type => options[:saver].class.base_class.name
81
+ })
82
+
83
+ if _saves_.count == 0 or options[:duplicate]
84
+ # this saver has never saved
85
+ saved = ActsAsSaveable::Save.new(
86
+ :saveable => self,
87
+ :saver => options[:saver],
88
+ :save_scope => options[:save_scope]
89
+ )
90
+ else
91
+ # this saver is potentially changing his saved
92
+ saved = _saves_.last
93
+ end
94
+
95
+ last_update = saved.updated_at
96
+
97
+ saved.save_flag = saveable_words.meaning_of(options[:saved])
98
+
99
+ #Allowing for a save_weight to be associated with every saved. Could change with every saver object
100
+ saved.save_weight = (options[:save_weight].to_i if options[:save_weight].present?) || 1
101
+
102
+ if saved.save
103
+ self.save_registered = true if last_update != saved.updated_at
104
+ update_cached_saves options[:save_scope]
105
+ return true
106
+ else
107
+ self.save_registered = false
108
+ return false
109
+ end
110
+
111
+ end
112
+
113
+ def unsaved args = {}
114
+ return false if args[:saver].nil?
115
+ _saves_ = find_saves_for(:saver_id => args[:saver].id, :save_scope => args[:save_scope], :saver_type => args[:saver].class.base_class.name)
116
+
117
+ return true if _saves_.size == 0
118
+ _saves_.each(&:destroy)
119
+ update_cached_saves args[:save_scope]
120
+ self.save_registered = false if saves_for.count == 0
121
+ return true
122
+ end
123
+
124
+ def save_up saver, options={}
125
+ self.save_by :saver => saver, :saved => true, :save_scope => options[:save_scope], :save_weight => options[:save_weight]
126
+ end
127
+
128
+ def save_down saver, options={}
129
+ self.save_by :saver => saver, :saved => false, :save_scope => options[:save_scope], :save_weight => options[:save_weight]
130
+ end
131
+
132
+ def unsave_by saver, options = {}
133
+ self.unsaved :saver => saver, :save_scope => options[:save_scope] #Does not need save_weight since the saves_for are anyway getting destroyed
134
+ end
135
+
136
+ def scope_cache_field field, save_scope
137
+ return field if save_scope.nil?
138
+
139
+ case field
140
+ when :cached_saves_total=
141
+ "cached_scoped_#{save_scope}_saves_total="
142
+ when :cached_saves_total
143
+ "cached_scoped_#{save_scope}_saves_total"
144
+ when :cached_saves_up=
145
+ "cached_scoped_#{save_scope}_saves_up="
146
+ when :cached_saves_up
147
+ "cached_scoped_#{save_scope}_saves_up"
148
+ when :cached_saves_down=
149
+ "cached_scoped_#{save_scope}_saves_down="
150
+ when :cached_saves_down
151
+ "cached_scoped_#{save_scope}_saves_down"
152
+ when :cached_saves_score=
153
+ "cached_scoped_#{save_scope}_saves_score="
154
+ when :cached_saves_score
155
+ "cached_scoped_#{save_scope}_saves_score"
156
+ when :cached_weighted_total
157
+ "cached_weighted_#{save_scope}_total"
158
+ when :cached_weighted_total=
159
+ "cached_weighted_#{save_scope}_total="
160
+ when :cached_weighted_score
161
+ "cached_weighted_#{save_scope}_score"
162
+ when :cached_weighted_score=
163
+ "cached_weighted_#{save_scope}_score="
164
+ when :cached_weighted_average
165
+ "cached_weighted_#{save_scope}_average"
166
+ when :cached_weighted_average=
167
+ "cached_weighted_#{save_scope}_average="
168
+ end
169
+ end
170
+
171
+ # caching
172
+ def update_cached_saves save_scope = nil
173
+
174
+ updates = {}
175
+
176
+ if self.respond_to?(:cached_saves_total=)
177
+ updates[:cached_saves_total] = count_saves_total(true)
178
+ end
179
+
180
+ if self.respond_to?(:cached_saves_up=)
181
+ updates[:cached_saves_up] = count_saves_up(true)
182
+ end
183
+
184
+ if self.respond_to?(:cached_saves_down=)
185
+ updates[:cached_saves_down] = count_saves_down(true)
186
+ end
187
+
188
+ if self.respond_to?(:cached_saves_score=)
189
+ updates[:cached_saves_score] = (
190
+ (updates[:cached_saves_up] || count_saves_up(true)) -
191
+ (updates[:cached_saves_down] || count_saves_down(true))
192
+ )
193
+ end
194
+
195
+ if self.respond_to?(:cached_weighted_total=)
196
+ updates[:cached_weighted_total] = weighted_total(true)
197
+ end
198
+
199
+ if self.respond_to?(:cached_weighted_score=)
200
+ updates[:cached_weighted_score] = weighted_score(true)
201
+ end
202
+
203
+ if self.respond_to?(:cached_weighted_average=)
204
+ updates[:cached_weighted_average] = weighted_average(true)
205
+ end
206
+
207
+ if save_scope
208
+ if self.respond_to?(scope_cache_field :cached_saves_total=, save_scope)
209
+ updates[scope_cache_field :cached_saves_total, save_scope] = count_saves_total(true, save_scope)
210
+ end
211
+
212
+ if self.respond_to?(scope_cache_field :cached_saves_up=, save_scope)
213
+ updates[scope_cache_field :cached_saves_up, save_scope] = count_saves_up(true, save_scope)
214
+ end
215
+
216
+ if self.respond_to?(scope_cache_field :cached_saves_down=, save_scope)
217
+ updates[scope_cache_field :cached_saves_down, save_scope] = count_saves_down(true, save_scope)
218
+ end
219
+
220
+ if self.respond_to?(scope_cache_field :cached_weighted_total=, save_scope)
221
+ updates[scope_cache_field :cached_weighted_total, save_scope] = weighted_total(true, save_scope)
222
+ end
223
+
224
+ if self.respond_to?(scope_cache_field :cached_weighted_score=, save_scope)
225
+ updates[scope_cache_field :cached_weighted_score, save_scope] = weighted_score(true, save_scope)
226
+ end
227
+
228
+ if self.respond_to?(scope_cache_field :cached_saves_score=, save_scope)
229
+ updates[scope_cache_field :cached_saves_score, save_scope] = (
230
+ (updates[scope_cache_field :cached_saves_up, save_scope] || count_saves_up(true, save_scope)) -
231
+ (updates[scope_cache_field :cached_saves_down, save_scope] || count_saves_down(true, save_scope))
232
+ )
233
+ end
234
+
235
+ if self.respond_to?(scope_cache_field :cached_weighted_average=, save_scope)
236
+ updates[scope_cache_field :cached_weighted_average, save_scope] = weighted_average(true, save_scope)
237
+ end
238
+ end
239
+
240
+ if (::ActiveRecord::VERSION::MAJOR == 3) && (::ActiveRecord::VERSION::MINOR != 0)
241
+ self.update_attributes(updates, :without_protection => true) if updates.size > 0
242
+ else
243
+ self.update_attributes(updates) if updates.size > 0
244
+ end
245
+
246
+ end
247
+
248
+
249
+ # results
250
+ def find_saves_for extra_conditions = {}
251
+ saves_for.where(extra_conditions)
252
+ end
253
+
254
+ def get_up_saves options={}
255
+ save_scope_hash = scope_or_empty_hash(options[:save_scope])
256
+ find_saves_for({:save_flag => true}.merge(save_scope_hash))
257
+ end
258
+
259
+ def get_down_saves options={}
260
+ save_scope_hash = scope_or_empty_hash(options[:save_scope])
261
+ find_saves_for({:save_flag => false}.merge(save_scope_hash))
262
+ end
263
+
264
+
265
+ # counting
266
+ def count_saves_total skip_cache = false, save_scope = nil
267
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_saves_total, save_scope)
268
+ return self.send(scope_cache_field :cached_saves_total, save_scope)
269
+ end
270
+ find_saves_for(scope_or_empty_hash(save_scope)).count
271
+ end
272
+
273
+ def count_saves_up skip_cache = false, save_scope = nil
274
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_saves_up, save_scope)
275
+ return self.send(scope_cache_field :cached_saves_up, save_scope)
276
+ end
277
+ get_up_saves(:save_scope => save_scope).count
278
+ end
279
+
280
+ def count_saves_down skip_cache = false, save_scope = nil
281
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_saves_down, save_scope)
282
+ return self.send(scope_cache_field :cached_saves_down, save_scope)
283
+ end
284
+ get_down_saves(:save_scope => save_scope).count
285
+ end
286
+
287
+ def weighted_total skip_cache = false, save_scope = nil
288
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_total, save_scope)
289
+ return self.send(scope_cache_field :cached_weighted_total, save_scope)
290
+ end
291
+ ups = get_up_saves(:save_scope => save_scope).sum(:save_weight)
292
+ downs = get_down_saves(:save_scope => save_scope).sum(:save_weight)
293
+ ups + downs
294
+ end
295
+
296
+ def weighted_score skip_cache = false, save_scope = nil
297
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_score, save_scope)
298
+ return self.send(scope_cache_field :cached_weighted_score, save_scope)
299
+ end
300
+ ups = get_up_saves(:save_scope => save_scope).sum(:save_weight)
301
+ downs = get_down_saves(:save_scope => save_scope).sum(:save_weight)
302
+ ups - downs
303
+ end
304
+
305
+ def weighted_average skip_cache = false, save_scope = nil
306
+ if !skip_cache && self.respond_to?(scope_cache_field :cached_weighted_average, save_scope)
307
+ return self.send(scope_cache_field :cached_weighted_average, save_scope)
308
+ end
309
+
310
+ count = count_saves_total(skip_cache, save_scope).to_i
311
+ if count > 0
312
+ weighted_score(skip_cache, save_scope).to_f / count
313
+ else
314
+ 0.0
315
+ end
316
+ end
317
+
318
+ # savers
319
+ def saved_on_by? saver
320
+ saves = find_saves_for :saver_id => saver.id, :saver_type => saver.class.base_class.name
321
+ saves.count > 0
322
+ end
323
+
324
+ private
325
+
326
+ def scope_or_empty_hash(save_scope)
327
+ save_scope ? { :save_scope => save_scope } : {}
328
+ end
329
+ end
330
+ end