acts_as_saveable 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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