has_moderated 0.0.21 → 0.0.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/README.rdoc +48 -9
  2. data/lib/has_moderated.rb +13 -234
  3. data/lib/has_moderated/carrier_wave.rb +2 -2
  4. data/lib/has_moderated/common.rb +129 -0
  5. data/lib/has_moderated/moderated_associations.rb +59 -0
  6. data/lib/has_moderated/moderated_attributes.rb +74 -0
  7. data/lib/has_moderated/moderated_create.rb +48 -0
  8. data/lib/has_moderated/moderated_destroy.rb +36 -0
  9. data/lib/has_moderated/moderation_model.rb +64 -23
  10. data/lib/has_moderated/user_hooks.rb +10 -0
  11. data/lib/has_moderated/version.rb +1 -1
  12. data/test/dummy/app/models/hjoin_test.rb +3 -0
  13. data/test/dummy/app/models/hmanythrough_join.rb +4 -0
  14. data/test/dummy/app/models/hmanythrough_test.rb +4 -0
  15. data/test/dummy/app/models/hone_test.rb +3 -0
  16. data/test/dummy/app/models/photo.rb +1 -1
  17. data/test/dummy/app/models/subtask.rb +1 -0
  18. data/test/dummy/app/models/task.rb +7 -2
  19. data/test/dummy/db/development.sqlite3 +0 -0
  20. data/test/dummy/db/migrate/20111008195728_create_hone_tests.rb +9 -0
  21. data/test/dummy/db/migrate/20111008195809_create_hjoin_tests.rb +13 -0
  22. data/test/dummy/db/migrate/20111009193145_fix_join_table.rb +8 -0
  23. data/test/dummy/db/migrate/20111009201729_add_title_to_hone_tests.rb +5 -0
  24. data/test/dummy/db/migrate/20111009205517_create_hmanythrough_tests.rb +9 -0
  25. data/test/dummy/db/migrate/20111009205545_create_hmanythrough_joins.rb +11 -0
  26. data/test/dummy/db/schema.rb +33 -1
  27. data/test/dummy/db/test.sqlite3 +0 -0
  28. data/test/dummy/log/development.log +4508 -0
  29. data/test/dummy/log/test.log +78674 -0
  30. data/test/dummy/public/test.jpg +0 -0
  31. data/test/dummy/public/uploads/task_photo/photo/1/test.jpg +0 -0
  32. data/test/dummy/spec/factories/hjoin_tests.rb +7 -0
  33. data/test/dummy/spec/factories/hmanythrough_joins.rb +9 -0
  34. data/test/dummy/spec/factories/hmanythrough_tests.rb +7 -0
  35. data/test/dummy/spec/factories/hone_tests.rb +7 -0
  36. data/test/dummy/spec/models/hjoin_test_spec.rb +36 -0
  37. data/test/dummy/spec/models/hmanythrough_test_spec.rb +99 -0
  38. data/test/dummy/spec/models/hone_test_spec.rb +36 -0
  39. data/test/dummy/spec/models/photo_holder_spec.rb +9 -17
  40. data/test/dummy/spec/models/photo_spec.rb +16 -29
  41. data/test/dummy/spec/models/task_photo_spec.rb +3 -9
  42. data/test/dummy/spec/models/task_spec.rb +36 -2
  43. data/test/dummy/spec/support/photos.rb +28 -0
  44. metadata +50 -8
  45. data/test/dummy/falaf/test.rb +0 -0
  46. data/test/dummy/public/uploads/task_photo/photo/1/logo_arnes.gif +0 -0
data/README.rdoc CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  == Installing
4
4
 
5
- gem install has_moderated
6
-
7
- And add it to your project's Gemfile
5
+ Add it to your project's Gemfile
8
6
 
9
7
  gem "has_moderated"
10
8
 
9
+ and run
10
+
11
+ bundle install
12
+
11
13
  To set up has_moderated in your project, use
12
14
 
13
15
  rails generate has_moderated:install
@@ -50,17 +52,19 @@ To moderate destruction of records, use
50
52
 
51
53
  has_moderated_destroy
52
54
 
55
+ Warning! Always put has_many etc. BEFORE has_moderated calls in your model!
53
56
  == Moderating associations on existing records
54
- Sometimes you will want to moderate association changes for existing records, not only on create. Right now, there is only one way to do this and it's not as automagic as most of the other things. Instead of doing something like
57
+ There is an automatic way to moderate associations on already-created records. Use:
55
58
 
56
- post.comments << new_comment
57
- post.scores << new_score
59
+ has_moderated_association :links, :comments
60
+
61
+ to moderate the links and comments associations. You can use :all to moderate all associations, but I recommend you explicitly specify them.
58
62
 
59
- You will *instead* have to call add_associations_moderated like so
63
+ You can also manually add associations to moderation like so
60
64
 
61
65
  post.add_associations_moderated(:comments => [new_comment], :scores => [new_score])
62
66
 
63
- The values can be either new records (not in the database), existing records, or Fixnum (numerical) IDs. Please note you should not use .build to create new records, because if you call save on the parent model it will automatically create the record, bypassing moderation.
67
+ The values can be either new records (not in the database), existing records, or Fixnum (numerical) IDs. Please note in this case you should not use .build to create new records, because if you call save on the parent model it will automatically create the record (this only applies if you use the manual way).
64
68
 
65
69
  == Manage moderations
66
70
  To see pending moderations, simply call
@@ -88,7 +92,7 @@ to discard (destroy) it, call
88
92
 
89
93
  If you need any special data attached to the Moderation model, you can use the moderation_creating hook.
90
94
 
91
- === Example for moderation_creating hook
95
+ === Attaching user ID to moderations
92
96
 
93
97
  For example you have a Comment model, and it is moderated. But your visitors are logged in when they post comments, so you want to add the user ID to the moderation.
94
98
 
@@ -119,6 +123,9 @@ There is support for CarrierWave uploads to be moderated. You must put this line
119
123
  Right now *you must use the field name "photo" for the upload filename* because it is currently hardcoded into this module. If you do this, then moderation for the photo should work correctly.
120
124
 
121
125
  It does not matter if this model has any moderation itself or if you just have an association to it from some other model that is moderated. You must include this module in either case, because it ensures proper serialization of the photo information.
126
+ If you want to moderate changes to the photo on this model itself (e.g. not only when its associated to something else), add also
127
+
128
+ has_moderated :carrierwave_photo
122
129
 
123
130
  If you need some more customization look at this module in lib/has_moderated/carrier_wave.rb and just copy the methods into your model and customize them (with some care when you do this, some methods should be class methods).
124
131
 
@@ -136,7 +143,39 @@ in the root directory.
136
143
 
137
144
  == TODO
138
145
 
146
+ DONT save association when calling .build... only when calling save!
147
+
148
+ This is just for my personal todo list...
139
149
  Amend moderations... Eg if you create a new record and save it, then change something additionally and save again.
150
+ Preview method which gives changed object but doesnt save it.
151
+
152
+ For automagic associations, look at https://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations/collection_association.rb method concat, concat_records, add_to_target
153
+ u get the instance of this class from with .association("name")
154
+ assoc = t.association(:subtasks)
155
+ def assoc.concat *args ; whatever ; end
156
+ or if we need block (capture outer scope)
157
+ metaclass = class << assoc; self; end
158
+ metaclass.send(:define_method, :concat) do
159
+ puts "concat 2"
160
+ end
161
+
162
+ in model use
163
+ after_initialize do...end
164
+ to overwrite these methods
165
+ ruby-1.8.7-p334 :013 > def assoc.concat_with_mo *args
166
+ ruby-1.8.7-p334 :014?> puts "in concat mo"
167
+ ruby-1.8.7-p334 :015?> concat_without_mo *args
168
+ ruby-1.8.7-p334 :016?> end
169
+ => nil
170
+ ruby-1.8.7-p334 :017 > assoc.class_eval do
171
+ ruby-1.8.7-p334 :018 > alias_method_chain :concat, :mo
172
+ ruby-1.8.7-p334 :019?> end
173
+ use add_to_target!!! lowest lvl
174
+ add something like has_moderated_association
175
+ don't do anything on create (parent.new_record?) since we already handle that
176
+ test also for one-to-one and join table and has_many through assoc
177
+
178
+ fix tests for offline working
140
179
  == Problems
141
180
 
142
181
  You may encounter problems with models that have some sort of non-serializable attributes. This might be something like file attachments, you'll have to try it to see.
data/lib/has_moderated.rb CHANGED
@@ -1,242 +1,21 @@
1
1
  require 'has_moderated/moderation_model'
2
2
  require 'has_moderated/carrier_wave'
3
+ require 'has_moderated/common'
4
+ require 'has_moderated/user_hooks'
5
+ require 'has_moderated/moderated_create'
6
+ require 'has_moderated/moderated_destroy'
7
+ require 'has_moderated/moderated_attributes'
8
+ require 'has_moderated/moderated_associations'
3
9
 
4
10
  module HasModerated
5
-
6
11
  def self.included(base)
7
- base.send :extend, ClassMethods
8
- end
9
-
10
- module ClassMethods
11
- def has_moderated *args, &block
12
- # Lazily include the instance methods so we don't clutter up
13
- # any more ActiveRecord models than we have to.
14
- send :include, InstanceMethods
15
-
16
- has_many :moderations, :as => :moderatable, :dependent => :destroy
17
-
18
- cattr_accessor :moderated_attributes
19
- cattr_accessor :moderated_options
20
-
21
- self.moderated_attributes ||= []
22
- self.moderated_options ||= {}
23
-
24
- args.each do |arg|
25
- if arg.respond_to?("[]")
26
- self.moderated_options = self.moderated_options.merge(arg)
27
- else
28
- self.moderated_attributes.push(arg.to_s)
29
- end
30
- end
31
-
32
- # use an attribute to temporarily disable moderation before_save filter
33
- attr_accessor :has_moderated_updating
34
-
35
- # send moderated attributes to moderation before saving the model
36
- before_save do
37
- if self.valid? && @has_moderated_updating != true &&
38
- # don't save moderated attributes if create is moderated and it's a new record
39
- !(self.class.respond_to?("moderated_create_options") && new_record?)
40
- moderations = self.to_moderation
41
- if self.id.blank?
42
- @pending_moderations ||= []
43
- @pending_moderations.concat(moderations)
44
- end
45
- end
46
- end
47
-
48
- # when creating a new record, we must update moderations' id after it is known (after create)
49
- after_create do
50
- if !self.id.blank? && !@pending_moderations.blank?
51
- @pending_moderations.each do |m|
52
- m.update_attributes(:moderatable_id => self.id)
53
- end
54
- @pending_moderations.clear
55
- end
56
- end
57
- end
58
-
59
- def moderation_creating &block
60
- cattr_accessor :moderation_creating_hook
61
- self.moderation_creating_hook = block
62
- end
63
-
64
- def has_moderated_create *options
65
- # Lazily include the instance methods so we don't clutter up
66
- # any more ActiveRecord models than we have to.
67
- send :include, InstanceMethods
68
-
69
- # use an attribute to temporarily disable moderation before_save filter
70
- attr_accessor :has_moderated_updating
71
-
72
- # save options for use later
73
- cattr_accessor :moderated_create_options
74
- self.moderated_create_options = (options.count > 0) ? options[0] : nil
75
-
76
- alias_method_chain :create_or_update, :moderated_check
77
- end
78
-
79
- def has_moderated_destroy *options
80
- # Lazily include the instance methods so we don't clutter up
81
- # any more ActiveRecord models than we have to.
82
- send :include, InstanceMethods
83
-
84
- # use an attribute to temporarily disable moderation before_save filter
85
- attr_accessor :has_moderated_updating
86
-
87
- alias_method_chain :destroy, :moderated_check
88
- end
89
- end
90
-
91
- module InstanceMethods
92
- def create_or_update_with_moderated_check *args
93
- if valid? && new_record? && @has_moderated_updating != true
94
- self.to_moderation_created(self.class.moderated_create_options)
95
- true
96
- else
97
- create_or_update_without_moderated_check *args
98
- end
99
- end
100
-
101
- def destroy_with_moderated_check *args
102
- if @has_moderated_updating == true
103
- destroy_without_moderated_check *args
104
- else
105
- to_moderation_destroyed
106
- true
107
- end
108
- end
109
-
110
- def call_creating_hook moderation
111
- if self.class.respond_to?(:moderation_creating_hook)
112
- self.instance_exec moderation, &self.class.moderation_creating_hook
113
- end
114
- end
115
-
116
- def create_moderation_with_hooks!(*args)
117
- m = Moderation.new(*args)
118
- call_creating_hook(m)
119
- m.save!
120
- m
121
- end
122
-
123
- def to_moderation_destroyed
124
- create_moderation_with_hooks!({
125
- :moderatable_type => self.class.to_s,
126
- :moderatable_id => self.id,
127
- :attr_name => "-",
128
- :attr_value => "destroy"
129
- })
130
- end
131
-
132
- def get_assocs_for_moderation options
133
- assocs = []
134
-
135
- unless options.blank?
136
- unless options[:with_associations].blank?
137
- if options[:with_associations] == :all
138
- assocs = self.class.reflections.keys.reject do |r|
139
- r == :moderations
140
- end
141
- else
142
- assocs = options[:with_associations]
143
- assocs = [assocs] unless assocs.respond_to?("[]")
144
- end
145
- end
146
- end
147
-
148
- assoc_attrs = {}
149
- assocs.each do |assoc|
150
- one_assoc = []
151
- self.send(assoc).each do |m|
152
- if m.new_record?
153
- one_assoc.push(get_moderation_attributes(m))
154
- else
155
- one_assoc.push(m.id)
156
- end
157
- end
158
- assoc_attrs[assoc] = one_assoc unless one_assoc.empty?
159
- end
160
-
161
- assoc_attrs
162
- end
163
-
164
- def get_moderation_attributes(model)
165
- if model.respond_to?(:moderatable_hashize)
166
- model.moderatable_hashize
167
- else
168
- model.attributes
169
- end
170
- end
171
-
172
- def to_moderation_created options
173
- assoc_attrs = get_assocs_for_moderation(options)
174
-
175
- attr_value = {
176
- :main_model => get_moderation_attributes(self),
177
- :associations => assoc_attrs
178
- }
179
-
180
- create_moderation_with_hooks!({
181
- :moderatable_type => self.class.to_s,
182
- :moderatable_id => self.id,
183
- :attr_name => "-",
184
- :attr_value => attr_value.to_yaml
185
- })
186
- end
187
-
188
- def to_moderation
189
- moderations = []
190
- self.changes.each_pair do |att_name, values|
191
- att_name = att_name.to_s
192
- if self.class.moderated_attributes.include?(att_name) && !(values[0].blank? && values[1].blank?)
193
- moderations.push(create_moderation_with_hooks!({
194
- :moderatable_type => self.class.to_s,
195
- :moderatable_id => self.id,
196
- :attr_name => att_name,
197
- :attr_value => self.attributes[att_name].to_yaml
198
- }))
199
- self.send(att_name+"=", values[0])
200
- end
201
- end
202
-
203
- moderations
204
- end
205
-
206
- def add_associations_moderated assocs
207
- assoc_attrs = {}
208
- assocs.each_pair do |assoc_name, assoc|
209
- one_assoc = []
210
- assoc.each do |m|
211
- if m.class == Fixnum
212
- one_assoc.push(m)
213
- elsif m.new_record?
214
- one_assoc.push(get_moderation_attributes(m))
215
- else
216
- one_assoc.push(m.id)
217
- end
218
- end
219
- assoc_attrs[assoc_name] = one_assoc unless one_assoc.empty?
220
- end
221
-
222
- moderations = []
223
- if !assoc_attrs.empty?
224
- moderations.push(create_moderation_with_hooks!({
225
- :moderatable_type => self.class.to_s,
226
- :moderatable_id => self.id,
227
- :attr_name => "-",
228
- :attr_value => { :associations => assoc_attrs }
229
- }))
230
- end
231
-
232
- moderations
233
- end
234
-
235
- def moderatable_updating
236
- self.has_moderated_updating = true
237
- yield(self)
238
- self.has_moderated_updating = false
239
- end
12
+ #base.send :extend, HasModerated::Common
13
+ HasModerated::Common::included(base)
14
+ base.send :extend, HasModerated::UserHooks::ClassMethods
15
+ base.send :extend, HasModerated::ModeratedCreate::ClassMethods
16
+ base.send :extend, HasModerated::ModeratedDestroy::ClassMethods
17
+ base.send :extend, HasModerated::ModeratedAssociations::ClassMethods
18
+ base.send :extend, HasModerated::ModeratedAttributes::ClassMethods
240
19
  end
241
20
  end
242
21
 
@@ -47,7 +47,7 @@ module HasModerated
47
47
 
48
48
  def store_photo_with_moderation!
49
49
  is_moderated = self.class.respond_to?(:moderated_attributes) &&
50
- self.class.moderated_attributes.include?("photo")
50
+ self.class.moderated_attributes.include?("carrierwave_photo")
51
51
  if self.has_moderated_updating || !is_moderated
52
52
  store_photo_without_moderation!
53
53
  else
@@ -60,7 +60,7 @@ module HasModerated
60
60
 
61
61
  def write_photo_identifier_with_moderation
62
62
  is_moderated = self.class.respond_to?(:moderated_attributes) &&
63
- self.class.moderated_attributes.include?("photo")
63
+ self.class.moderated_attributes.include?("carrierwave_photo")
64
64
  if self.has_moderated_updating || !is_moderated
65
65
  write_photo_identifier_without_moderation
66
66
  end
@@ -0,0 +1,129 @@
1
+ module HasModerated
2
+ module Common
3
+ def self.included(base)
4
+ base.send :include, InstanceMethods
5
+ end
6
+
7
+ module InstanceMethods
8
+ def create_moderation_with_hooks!(*args)
9
+ m = Moderation.new(*args)
10
+ HasModerated::Common::call_creating_hook(self, m)
11
+ m.save!
12
+ m
13
+ end
14
+
15
+ def moderatable_updating
16
+ self.has_moderated_updating = true
17
+ yield(self)
18
+ self.has_moderated_updating = false
19
+ end
20
+
21
+ def get_moderation_attributes(model)
22
+ if model.respond_to?(:moderatable_hashize)
23
+ model.moderatable_hashize
24
+ else
25
+ model.attributes
26
+ end
27
+ end
28
+
29
+ def add_associations_moderated assocs
30
+ # check for through assocs
31
+ from_record = self
32
+ through_assocs = {}
33
+ from_record.class.reflections.keys.each do |assoc|
34
+ join_model = from_record.class.reflections[assoc.to_sym].options[:through]
35
+ if join_model
36
+ join_model = join_model.to_sym
37
+ through_assocs[join_model] ||= []
38
+ through_assocs[join_model].push(assoc)
39
+ end
40
+ end
41
+
42
+ assoc_attrs = {}
43
+ assocs.each_pair do |assoc_name, assoc|
44
+ one_assoc = []
45
+ assoc.each do |m|
46
+ if m.class == Fixnum
47
+ one_assoc.push(m)
48
+ elsif m.new_record?
49
+ one_assoc.push(from_record.get_moderation_attributes(m))
50
+ else
51
+ one_assoc.push(m.id)
52
+ end
53
+ if through_assocs[assoc_name.to_sym]
54
+ one_assoc.last[:associations] = HasModerated::Common::get_assocs_for_moderation(through_assocs[assoc_name.to_sym], m)
55
+ end
56
+ end
57
+ assoc_attrs[assoc_name] = one_assoc unless one_assoc.empty?
58
+ end
59
+
60
+ moderations = []
61
+ if !assoc_attrs.empty?
62
+ moderations.push(create_moderation_with_hooks!({
63
+ :moderatable_type => self.class.to_s,
64
+ :moderatable_id => self.id,
65
+ :attr_name => "-",
66
+ :attr_value => { :associations => assoc_attrs }
67
+ }))
68
+ end
69
+
70
+ moderations
71
+ end
72
+ end
73
+
74
+ def self.get_assocs_for_moderation assocs, from_record = nil
75
+ from_record ||= self
76
+ return if assocs.blank?
77
+
78
+ if assocs == :all
79
+ assocs = from_record.class.reflections.keys.reject do |r|
80
+ r == :moderations
81
+ end
82
+ end
83
+
84
+ assocs = [assocs] unless assocs.respond_to?("[]")
85
+
86
+ # check for through assocs
87
+ assocs = assocs.dup
88
+ through_assocs = {}
89
+ assocs.each do |assoc|
90
+ join_model = from_record.class.reflections[assoc.to_sym].options[:through]
91
+ if join_model
92
+ join_model = join_model.to_sym
93
+ through_assocs[join_model] ||= []
94
+ through_assocs[join_model].push(assoc)
95
+ assocs.push(join_model) unless assocs.include?(join_model)
96
+ #assocs.delete(assoc)
97
+ end
98
+ end
99
+
100
+ assoc_attrs = {}
101
+ assocs.each do |assoc|
102
+ one_assoc = []
103
+ assoc_value = from_record.send(assoc)
104
+ # if it's has_one it won't be an array
105
+ assoc_value = [assoc_value] if assoc_value && assoc_value.class != Array
106
+ assoc_value ||= []
107
+ assoc_value.each do |m|
108
+ if m.new_record?
109
+ one_assoc.push(from_record.get_moderation_attributes(m))
110
+ else
111
+ one_assoc.push(m.id)
112
+ end
113
+ if through_assocs[assoc.to_sym]
114
+ one_assoc.last[:associations] = get_assocs_for_moderation(through_assocs[assoc.to_sym], m)
115
+ end
116
+ end
117
+ assoc_attrs[assoc] = one_assoc unless one_assoc.empty?
118
+ end
119
+
120
+ assoc_attrs
121
+ end
122
+
123
+ def self.call_creating_hook model, moderation
124
+ if model.class.respond_to?(:moderation_creating_hook)
125
+ model.instance_exec moderation, &(model.class.moderation_creating_hook)
126
+ end
127
+ end
128
+ end
129
+ end