has_moderated 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -50,6 +50,18 @@ To moderate destruction of records, use
50
50
 
51
51
  has_moderated_destroy
52
52
 
53
+ == 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
55
+
56
+ post.comments << new_comment
57
+ post.scores << new_score
58
+
59
+ You will *instead* have to call add_associations_moderated like so
60
+
61
+ post.add_associations_moderated(:comments => [new_comment], :scores => [new_score])
62
+
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.
64
+
53
65
  == Manage moderations
54
66
  To see pending moderations, simply call
55
67
 
@@ -1,76 +1,5 @@
1
1
  class Moderation < ActiveRecord::Base
2
2
  belongs_to :moderatable, :polymorphic => true
3
-
4
- def accept
5
- # case: moderated destruction
6
- if attr_name == '-' && attr_value.class == String && attr_value == "destroy"
7
- moderatable.has_moderated_updating = true
8
- moderatable.destroy
9
- moderatable.has_moderated_updating = false
10
- self.destroy
11
- elsif attr_name == '-'
12
- loaded_val = YAML::load(attr_value)
13
- # case: moderated existance (new record)
14
- if moderatable_id.blank?
15
- # create the main record
16
- rec = moderatable_type.constantize.new
17
- attrs = loaded_val[:main_model]
18
- # bypass attr_accessible protection
19
- attrs.each_pair do |key, val|
20
- rec.send(key.to_s+"=", val) unless key.to_s == 'id'
21
- end
22
- # temporarily disable moderation check on save, and save updated record
23
- rec.has_moderated_updating = true
24
- rec.save(:validate => false) # don't run validations
25
- rec.has_moderated_updating = false
26
- # case: moderated associations (existing record)
27
- else
28
- rec = moderatable
29
- end
30
-
31
- # check for saved associated records
32
- loaded_val[:associations].each_pair do |assoc_name, assoc_records|
33
- # read reflections attribute to determine proper class name and primary key
34
- assoc_details = rec.class.reflections[assoc_name.to_sym]
35
- m = assoc_details.class_name.constantize
36
-
37
- # all records for this associated model
38
- assoc_records.each do |attrs|
39
- if attrs.class == Fixnum # associate to existing record
40
- arec = m.find_by_id(attrs)
41
- rec.send(assoc_name.to_s) << arec if arec # add the association, if the record still exists
42
- else # create a new record
43
- arec = m.new # new associated model
44
- attrs.each_pair do |key, val|
45
- arec.send(key.to_s+"=", val) unless key.to_s == 'id'
46
- end
47
- fk = if assoc_details.respond_to?(:foreign_key)
48
- assoc_details.foreign_key
49
- else # version < 3.1
50
- assoc_details.primary_key_name
51
- end
52
- arec.send(fk.to_s+"=", rec.id) # set association to the newly created record
53
- # disable moderation for associated model (if enabled)
54
- arec.has_moderated_updating = true if arec.respond_to?("has_moderated_updating=")
55
- arec.save(:validate => false) # don't run validations
56
- arec.has_moderated_updating = false if arec.respond_to?("has_moderated_updating=")
57
- end
58
- end
59
- end
60
- self.destroy # destroy this moderation since it has been applied
61
- rec
62
- # case: moderated attribute (existing record)
63
- else
64
- moderatable.has_moderated_updating = true
65
- # bypass attr_accessible protection
66
- moderatable.send(attr_name.to_s+"=", YAML::load(attr_value))
67
- moderatable.save(:validate => false) # don't run validations
68
- moderatable.has_moderated_updating = false
69
- self.destroy # destroy this moderation since it has been applied
70
- end
71
- end
72
-
73
- def discard
74
- self.destroy
75
- end
3
+
4
+ include HasModerated::ModerationModel
76
5
  end
@@ -0,0 +1,67 @@
1
+ require 'fileutils'
2
+ module HasModerated
3
+ module CarrierWave
4
+
5
+ def self.included(base)
6
+ base.send :extend, ClassMethods
7
+ base.send :include, InstanceMethods
8
+
9
+ base.alias_method_chain :store_photo!, :moderation
10
+ base.alias_method_chain :write_photo_identifier, :moderation
11
+ end
12
+
13
+ def self.photo_tmp_delete(value)
14
+ FileUtils.rm(value) # remove temp file
15
+ FileUtils.rmdir(File.expand_path("..", value)) # will only remove folder if empty
16
+ end
17
+
18
+ module ClassMethods
19
+ # use class method because we only operate on hash parameters, not with a real record
20
+ # here we can delete the photo from tmp
21
+ def moderatable_discard(moderation)
22
+ value = moderation.interpreted_value
23
+ if value && value.respond_to?("[]") &&
24
+ value[:main_model] && value[:main_model][:photo_tmp_file]
25
+ HasModerated::CarrierWave::photo_tmp_delete(value[:main_model][:photo_tmp_file])
26
+ end
27
+ end
28
+ end
29
+
30
+ module InstanceMethods
31
+ attr_accessor :has_moderated_updating # in case this model itself is not moderated
32
+ # maybe autodetect fields that use carrierwave, or specify them
33
+ def moderatable_hashize
34
+ attrs = self.attributes
35
+ attrs = attrs.merge({
36
+ :photo_tmp_file => self.photo.file.file
37
+ }) if self.photo && self.photo.file
38
+ end
39
+
40
+ def photo_tmp_file=(value)
41
+ self.photo.store!(File.open(value))
42
+ HasModerated::CarrierWave::photo_tmp_delete(value)
43
+ end
44
+
45
+ def store_photo_with_moderation!
46
+ is_moderated = self.class.respond_to?(:moderated_attributes) &&
47
+ self.class.moderated_attributes.include?("photo")
48
+ if self.has_moderated_updating || !is_moderated
49
+ store_photo_without_moderation!
50
+ else
51
+ self.moderations.create!({
52
+ :attr_name => "photo_tmp_file",
53
+ :attr_value => self.photo.file.file
54
+ })
55
+ end
56
+ end
57
+
58
+ def write_photo_identifier_with_moderation
59
+ is_moderated = self.class.respond_to?(:moderated_attributes) &&
60
+ self.class.moderated_attributes.include?("photo")
61
+ if self.has_moderated_updating || !is_moderated
62
+ write_photo_identifier_without_moderation
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,106 @@
1
+ module HasModerated
2
+ module ModerationModel
3
+
4
+ def interpreted_value
5
+ @interpreted_value ||= if attr_name == '-'
6
+ YAML::load(attr_value)
7
+ else
8
+ attr_value
9
+ end
10
+ end
11
+
12
+ def create_from_value
13
+ if moderatable_id.blank?
14
+ # create the main record
15
+ rec = moderatable_type.constantize.new
16
+ attrs = interpreted_value[:main_model]
17
+ # bypass attr_accessible protection
18
+ attrs && attrs.each_pair do |key, val|
19
+ rec.send(key.to_s+"=", val) unless key.to_s == 'id'
20
+ end
21
+ rec
22
+ else
23
+ nil
24
+ end
25
+ end
26
+
27
+ def update_associations_from_value rec
28
+ # loop association types, e.g. :comments
29
+ interpreted_value[:associations].each_pair do |assoc_name, assoc_records|
30
+ # read reflections attribute to determine proper class name and primary key
31
+ assoc_details = rec.class.reflections[assoc_name.to_sym]
32
+ m = assoc_details.class_name.constantize
33
+
34
+ # all instances for this association type
35
+ assoc_records.each do |attrs|
36
+ # PARAM = ID
37
+ if attrs.class == Fixnum
38
+ arec = m.find_by_id(attrs)
39
+ # add the association, if the record still existss
40
+ rec.send(assoc_name.to_s) << arec if arec
41
+ # PARAM = Hash (create)
42
+ else
43
+ arec = m.new
44
+ attrs.each_pair do |key, val|
45
+ arec.send(key.to_s+"=", val) unless key.to_s == 'id'
46
+ end
47
+ fk = if assoc_details.respond_to?(:foreign_key)
48
+ assoc_details.foreign_key
49
+ else # Rails < v3.1
50
+ assoc_details.primary_key_name
51
+ end
52
+ arec.send(fk.to_s+"=", rec.id) # set association to the newly created record
53
+ # disable moderation for associated model (if moderated)
54
+ arec.has_moderated_updating = true if arec.respond_to?("has_moderated_updating=")
55
+ arec.save(:validate => false) # don't run validations
56
+ arec.has_moderated_updating = false if arec.respond_to?("has_moderated_updating=")
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ def accept
63
+ # DESTROY
64
+ if attr_name == '-' && attr_value.class == String && attr_value == "destroy"
65
+ moderatable.moderatable_updating { moderatable.destroy }
66
+ self.destroy
67
+
68
+ # CREATE or ASSOCIATIONS
69
+ elsif attr_name == '-'
70
+ loaded_val = YAML::load(attr_value)
71
+ # case: moderated existance (new record)
72
+ if moderatable_id.blank?
73
+ rec = create_from_value
74
+ # save, don't run validations
75
+ rec.moderatable_updating { rec.save(:validate => false) }
76
+ # case: moderated associations (existing record)
77
+ else
78
+ rec = moderatable
79
+ end
80
+
81
+ # check for saved associated records
82
+ update_associations_from_value rec
83
+
84
+ self.destroy # destroy this moderation since it has been applied
85
+ rec
86
+
87
+ # CHANGE ATTRIBUTE
88
+ else
89
+ moderatable.moderatable_updating do
90
+ # bypass attr_accessible protection
91
+ moderatable.send(attr_name.to_s+"=", YAML::load(attr_value))
92
+ moderatable.save(:validate => false) # don't run validations
93
+ end
94
+ self.destroy # destroy this moderation since it has been applied
95
+ end
96
+ end
97
+
98
+ def discard
99
+ if moderatable_type
100
+ klass = moderatable_type.constantize
101
+ klass.moderatable_discard(self) if klass.respond_to?(:moderatable_discard)
102
+ end
103
+ self.destroy
104
+ end
105
+ end
106
+ end
@@ -1,3 +1,3 @@
1
1
  module HasModerated
2
- VERSION = "0.0.9"
2
+ VERSION = "0.0.10"
3
3
  end
data/lib/has_moderated.rb CHANGED
@@ -1,3 +1,6 @@
1
+ require 'has_moderated/moderation_model'
2
+ require 'has_moderated/carrier_wave'
3
+
1
4
  module HasModerated
2
5
 
3
6
  def self.included(base)
@@ -110,7 +113,7 @@ module HasModerated
110
113
 
111
114
  def get_assocs_for_moderation options
112
115
  assocs = []
113
-
116
+
114
117
  unless options.blank?
115
118
  unless options[:with_associations].blank?
116
119
  if options[:with_associations] == :all
@@ -129,7 +132,7 @@ module HasModerated
129
132
  one_assoc = []
130
133
  self.send(assoc).each do |m|
131
134
  if m.new_record?
132
- one_assoc.push(m.attributes)
135
+ one_assoc.push(get_moderation_attributes(m))
133
136
  else
134
137
  one_assoc.push(m.id)
135
138
  end
@@ -140,11 +143,19 @@ module HasModerated
140
143
  assoc_attrs
141
144
  end
142
145
 
146
+ def get_moderation_attributes(model)
147
+ if model.respond_to?(:moderatable_hashize)
148
+ model.moderatable_hashize
149
+ else
150
+ model.attributes
151
+ end
152
+ end
153
+
143
154
  def to_moderation_created options
144
155
  assoc_attrs = get_assocs_for_moderation(options)
145
156
 
146
157
  attr_value = {
147
- :main_model => self.attributes,
158
+ :main_model => get_moderation_attributes(self),
148
159
  :associations => assoc_attrs
149
160
  }
150
161
 
@@ -182,7 +193,7 @@ module HasModerated
182
193
  if m.class == Fixnum
183
194
  one_assoc.push(m)
184
195
  elsif m.new_record?
185
- one_assoc.push(m.attributes)
196
+ one_assoc.push(get_moderation_attributes(m))
186
197
  else
187
198
  one_assoc.push(m.id)
188
199
  end
@@ -202,6 +213,12 @@ module HasModerated
202
213
 
203
214
  moderations
204
215
  end
216
+
217
+ def moderatable_updating
218
+ self.has_moderated_updating = true
219
+ yield(self)
220
+ self.has_moderated_updating = false
221
+ end
205
222
  end
206
223
  end
207
224
 
@@ -1,76 +1,5 @@
1
1
  class Moderation < ActiveRecord::Base
2
2
  belongs_to :moderatable, :polymorphic => true
3
-
4
- def accept
5
- # case: moderated destruction
6
- if attr_name == '-' && attr_value.class == String && attr_value == "destroy"
7
- moderatable.has_moderated_updating = true
8
- moderatable.destroy
9
- moderatable.has_moderated_updating = false
10
- self.destroy
11
- elsif attr_name == '-'
12
- loaded_val = YAML::load(attr_value)
13
- # case: moderated existance (new record)
14
- if moderatable_id.blank?
15
- # create the main record
16
- rec = moderatable_type.constantize.new
17
- attrs = loaded_val[:main_model]
18
- # bypass attr_accessible protection
19
- attrs.each_pair do |key, val|
20
- rec.send(key.to_s+"=", val) unless key.to_s == 'id'
21
- end
22
- # temporarily disable moderation check on save, and save updated record
23
- rec.has_moderated_updating = true
24
- rec.save(:validate => false) # don't run validations
25
- rec.has_moderated_updating = false
26
- # case: moderated associations (existing record)
27
- else
28
- rec = moderatable
29
- end
30
-
31
- # check for saved associated records
32
- loaded_val[:associations].each_pair do |assoc_name, assoc_records|
33
- # read reflections attribute to determine proper class name and primary key
34
- assoc_details = rec.class.reflections[assoc_name.to_sym]
35
- m = assoc_details.class_name.constantize
36
-
37
- # all records for this associated model
38
- assoc_records.each do |attrs|
39
- if attrs.class == Fixnum # associate to existing record
40
- arec = m.find_by_id(attrs)
41
- rec.send(assoc_name.to_s) << arec if arec # add the association, if the record still exists
42
- else # create a new record
43
- arec = m.new # new associated model
44
- attrs.each_pair do |key, val|
45
- arec.send(key.to_s+"=", val) unless key.to_s == 'id'
46
- end
47
- fk = if assoc_details.respond_to?(:foreign_key)
48
- assoc_details.foreign_key
49
- else # version < 3.1
50
- assoc_details.primary_key_name
51
- end
52
- arec.send(fk.to_s+"=", rec.id) # set association to the newly created record
53
- # disable moderation for associated model (if enabled)
54
- arec.has_moderated_updating = true if arec.respond_to?("has_moderated_updating=")
55
- arec.save(:validate => false) # don't run validations
56
- arec.has_moderated_updating = false if arec.respond_to?("has_moderated_updating=")
57
- end
58
- end
59
- end
60
- self.destroy # destroy this moderation since it has been applied
61
- rec
62
- # case: moderated attribute (existing record)
63
- else
64
- moderatable.has_moderated_updating = true
65
- # bypass attr_accessible protection
66
- moderatable.send(attr_name.to_s+"=", YAML::load(attr_value))
67
- moderatable.save(:validate => false) # don't run validations
68
- moderatable.has_moderated_updating = false
69
- self.destroy # destroy this moderation since it has been applied
70
- end
71
- end
72
-
73
- def discard
74
- self.destroy
75
- end
3
+
4
+ include HasModerated::ModerationModel
76
5
  end
@@ -0,0 +1,6 @@
1
+ class Photo < ActiveRecord::Base
2
+ mount_uploader :photo, GenericUploader
3
+ has_moderated_create
4
+ has_moderated :photo
5
+ include HasModerated::CarrierWave
6
+ end
@@ -1,7 +1,8 @@
1
1
  class Task < ActiveRecord::Base
2
2
  attr_accessible :title
3
3
  has_many :subtasks
4
+ has_many :task_photos
4
5
  has_moderated :title, :desc, { :with_associations => [:subtasks] }
5
- has_moderated_create :with_associations => [:subtasks]
6
+ has_moderated_create :with_associations => [:subtasks, :task_photos]
6
7
  has_moderated_destroy
7
8
  end
@@ -0,0 +1,5 @@
1
+ class TaskPhoto < ActiveRecord::Base
2
+ belongs_to :task
3
+ mount_uploader :photo, GenericUploader
4
+ include HasModerated::CarrierWave
5
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ class GenericUploader < CarrierWave::Uploader::Base
4
+
5
+ # Include RMagick or ImageScience support:
6
+ # include CarrierWave::RMagick
7
+ # include CarrierWave::MiniMagick
8
+ # include CarrierWave::ImageScience
9
+
10
+ # Choose what kind of storage to use for this uploader:
11
+ storage :file
12
+ # storage :fog
13
+
14
+ # Override the directory where uploaded files will be stored.
15
+ # This is a sensible default for uploaders that are meant to be mounted:
16
+ def store_dir
17
+ "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
18
+ end
19
+
20
+ # Provide a default URL as a default if there hasn't been a file uploaded:
21
+ # def default_url
22
+ # "/images/fallback/" + [version_name, "default.png"].compact.join('_')
23
+ # end
24
+
25
+ # Process files as they are uploaded:
26
+ # process :scale => [200, 300]
27
+ #
28
+ # def scale(width, height)
29
+ # # do something
30
+ # end
31
+
32
+ # Create different versions of your uploaded files:
33
+ # version :thumb do
34
+ # process :scale => [50, 50]
35
+ # end
36
+
37
+ # Add a white list of extensions which are allowed to be uploaded.
38
+ # For images you might use something like this:
39
+ # def extension_white_list
40
+ # %w(jpg jpeg gif png)
41
+ # end
42
+
43
+ # Override the filename of the uploaded files:
44
+ # Avoid using model.id or version_name here, see uploader/store.rb for details.
45
+ # def filename
46
+ # "something.jpg" if original_filename
47
+ # end
48
+
49
+ end
Binary file
@@ -0,0 +1,9 @@
1
+ class CreatePhotos < ActiveRecord::Migration
2
+ def change
3
+ create_table :photos do |t|
4
+ t.string :photo
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ class CreateTaskPhotos < ActiveRecord::Migration
2
+ def change
3
+ create_table :task_photos do |t|
4
+ t.string :photo
5
+ t.integer :task_id
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended to check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(:version => 20110908025606) do
13
+ ActiveRecord::Schema.define(:version => 20111003234101) do
14
14
 
15
15
  create_table "moderations", :force => true do |t|
16
16
  t.integer "moderatable_id"
@@ -21,6 +21,12 @@ ActiveRecord::Schema.define(:version => 20110908025606) do
21
21
  t.datetime "updated_at"
22
22
  end
23
23
 
24
+ create_table "photos", :force => true do |t|
25
+ t.string "photo"
26
+ t.datetime "created_at"
27
+ t.datetime "updated_at"
28
+ end
29
+
24
30
  create_table "subtasks", :force => true do |t|
25
31
  t.integer "task_id"
26
32
  t.string "title"
@@ -37,6 +43,13 @@ ActiveRecord::Schema.define(:version => 20110908025606) do
37
43
  t.datetime "updated_at"
38
44
  end
39
45
 
46
+ create_table "task_photos", :force => true do |t|
47
+ t.string "photo"
48
+ t.integer "task_id"
49
+ t.datetime "created_at"
50
+ t.datetime "updated_at"
51
+ end
52
+
40
53
  create_table "tasks", :force => true do |t|
41
54
  t.string "title"
42
55
  t.string "desc"
Binary file
File without changes