has_moderated 0.0.34 → 1.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +17 -0
- data/lib/generators/has_moderated/install/templates/migration.rb +4 -3
- data/lib/has_moderated/adapters/active_record.rb +109 -0
- data/lib/has_moderated/adapters/proxy.rb +23 -0
- data/lib/has_moderated/associations/base.rb +157 -0
- data/lib/has_moderated/associations/collection.rb +123 -0
- data/lib/has_moderated/associations/has_many.rb +15 -0
- data/lib/has_moderated/associations/has_one.rb +58 -0
- data/lib/has_moderated/carrier_wave.rb +3 -3
- data/lib/has_moderated/common.rb +62 -113
- data/lib/has_moderated/moderated_attributes.rb +24 -11
- data/lib/has_moderated/moderated_create.rb +22 -14
- data/lib/has_moderated/moderated_destroy.rb +11 -10
- data/lib/has_moderated/moderation_model.rb +6 -200
- data/lib/has_moderated/user_hooks.rb +3 -2
- data/lib/has_moderated/version.rb +1 -1
- data/lib/has_moderated.rb +15 -7
- data/test/dummy/app/models/moderation.rb +0 -0
- data/test/dummy/app/models/subtask.rb +0 -1
- data/test/dummy/app/models/task.rb +0 -13
- data/test/dummy/app/models/task_connection.rb +4 -0
- data/test/dummy/db/migrate/20120515155730_create_moderations2.rb +20 -0
- data/test/dummy/db/migrate/20120515174306_prepare_for_new_tests.rb +36 -0
- data/test/dummy/db/migrate/20120515175621_remove_photo_relateds.rb +8 -0
- data/test/dummy/db/schema.rb +17 -96
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/development.log +35 -0
- data/test/dummy/log/test.log +81290 -0
- data/test/dummy/spec/models/task_spec.rb +414 -247
- data/test/dummy/spec/spec_helper.rb +4 -0
- metadata +26 -57
- data/lib/has_moderated/moderated_associations.rb +0 -69
- data/test/dummy/app/models/habtm_name_test.rb +0 -3
- data/test/dummy/app/models/hjoin_test.rb +0 -3
- data/test/dummy/app/models/hmany_fk_test.rb +0 -3
- data/test/dummy/app/models/hmanythrough_join.rb +0 -4
- data/test/dummy/app/models/hmanythrough_test.rb +0 -4
- data/test/dummy/app/models/hone_as_test.rb +0 -3
- data/test/dummy/app/models/hone_test.rb +0 -3
- data/test/dummy/app/models/hook_test.rb +0 -9
- data/test/dummy/app/models/photo.rb +0 -8
- data/test/dummy/app/models/photo_holder.rb +0 -4
- data/test/dummy/app/models/photo_related.rb +0 -3
- data/test/dummy/app/models/task_all.rb +0 -4
- data/test/dummy/app/models/task_photo.rb +0 -5
- data/test/dummy/spec/models/habtm_name_test_spec.rb +0 -22
- data/test/dummy/spec/models/hjoin_test_spec.rb +0 -54
- data/test/dummy/spec/models/hmany_fk_test_spec.rb +0 -36
- data/test/dummy/spec/models/hmanythrough_test_spec.rb +0 -99
- data/test/dummy/spec/models/hone_as_test_spec.rb +0 -36
- data/test/dummy/spec/models/hone_test_spec.rb +0 -36
- data/test/dummy/spec/models/hooks_spec.rb +0 -30
- data/test/dummy/spec/models/photo_holder_spec.rb +0 -21
- data/test/dummy/spec/models/photo_spec.rb +0 -69
- data/test/dummy/spec/models/task_photo_spec.rb +0 -25
data/README.rdoc
CHANGED
@@ -164,9 +164,26 @@ You can run the tests by running
|
|
164
164
|
|
165
165
|
in the root directory of this gem (so you have to clone it first).
|
166
166
|
|
167
|
+
== NOTES
|
168
|
+
- to moderate has_many :through assoc, must add both associations to moderation
|
167
169
|
== TODO
|
168
170
|
|
169
171
|
This is just for my personal todo list...
|
172
|
+
|
173
|
+
hm maybe has_moderated_association could be just a has_moderated_create on the assoc? just check stuff like if task_id is not nil or something. could be much easier to implement then
|
174
|
+
|
175
|
+
also maybe use dirty https://github.com/rails/rails/blob/master/activemodel/lib/active_model/dirty.rb
|
176
|
+
|
177
|
+
and provide the same interface to show changes before accepting a moderation (just overwrite the dirty methods on the instance, also overwrite save at a low level to throw exception since its only a preview)
|
178
|
+
|
179
|
+
to make it possible to support other than activerecord, use activemodel when its possible AND try to make it so its possible to call the last step before activerecord (e.g. a func that reads all the values of current attributes and figures out which ones changed) - and use this in activerecord.
|
180
|
+
|
181
|
+
use YARD for docs
|
182
|
+
check again railscasts episodes for gems (e.g. railties)
|
183
|
+
-
|
184
|
+
|
185
|
+
make hasone, hasmany etc. each a separate "extension" (like carrierwave). for tests just use 2 models (task, subtask) and dont define has_many in model but in test
|
186
|
+
|
170
187
|
Amend moderations... Eg if you create a new record and save it, then change something additionally and save again.
|
171
188
|
Preview method which gives changed object but doesnt save it.
|
172
189
|
|
@@ -1,14 +1,15 @@
|
|
1
1
|
class CreateModerations < ActiveRecord::Migration
|
2
2
|
def self.up
|
3
3
|
if table_exists? :moderations # for upgrading
|
4
|
-
Moderation.
|
4
|
+
if Moderation.count > 0
|
5
|
+
raise "Moderations table must be empty before upgrading has_moderated!"
|
6
|
+
end
|
5
7
|
drop_table :moderations
|
6
8
|
end
|
7
9
|
create_table "moderations" do |t|
|
8
10
|
t.integer "moderatable_id", :null => true
|
9
11
|
t.string "moderatable_type", :null => true
|
10
|
-
t.
|
11
|
-
t.text "attr_value", :null => false
|
12
|
+
t.text "data", :null => false
|
12
13
|
t.timestamps
|
13
14
|
end
|
14
15
|
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# everything that does with activerecord should be here.
|
2
|
+
# use a wrapper that calls stuff from here, so that impl. can be switched eg with mongomapper
|
3
|
+
# also write test that if one attr is moderated and one is not, the unmoderated one must still save immediately!
|
4
|
+
|
5
|
+
module HasModerated
|
6
|
+
module Adapters
|
7
|
+
module ActiveRecord
|
8
|
+
# AR specific
|
9
|
+
def self.foreign_key(reflection)
|
10
|
+
if reflection.respond_to?(:foreign_key)
|
11
|
+
reflection.foreign_key
|
12
|
+
else # Rails < v3.1
|
13
|
+
reflection.primary_key_name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# General
|
18
|
+
def self.add_moderations_association(klass)
|
19
|
+
klass.class_eval do
|
20
|
+
has_many :moderations, :as => :moderatable, :dependent => :destroy
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.get_default_moderation_attributes(record)
|
25
|
+
record.attributes
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.hashize_association(from_record, assoc_name, m)
|
29
|
+
# todo
|
30
|
+
through_assocs = {}
|
31
|
+
from_record.class.reflections.keys.each do |assoc|
|
32
|
+
join_model = from_record.class.reflections[assoc.to_sym].options[:through]
|
33
|
+
if join_model
|
34
|
+
join_model = join_model.to_sym
|
35
|
+
through_assocs[join_model] ||= []
|
36
|
+
through_assocs[join_model].push(assoc)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
assoc = nil
|
41
|
+
if m.class == Fixnum
|
42
|
+
assoc = m
|
43
|
+
elsif m.kind_of? Hash # already a hash (for has_many :through association)
|
44
|
+
assoc = m
|
45
|
+
elsif m.respond_to?(:get_moderation_attributes) && m.new_record?
|
46
|
+
assoc = m.get_moderation_attributes
|
47
|
+
elsif m.respond_to?(:get_moderation_attributes)
|
48
|
+
assoc = m.id
|
49
|
+
else
|
50
|
+
raise "don't know how to convert #{m.class} to hash"
|
51
|
+
end
|
52
|
+
|
53
|
+
if through_assocs[assoc_name.to_sym] # TODO !
|
54
|
+
assoc[:associations] = get_assocs_for_moderation(:all, m)
|
55
|
+
end
|
56
|
+
assoc
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.get_assocs_for_moderation assocs, from_record = nil
|
60
|
+
from_record ||= self
|
61
|
+
return if assocs.blank?
|
62
|
+
|
63
|
+
if assocs == :all
|
64
|
+
assocs = from_record.class.reflections.keys.reject do |r|
|
65
|
+
r == :moderations
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
assocs = [assocs] unless assocs.respond_to?("[]")
|
70
|
+
|
71
|
+
# check for through assocs
|
72
|
+
assocs = assocs.dup
|
73
|
+
through_assocs = {}
|
74
|
+
assocs.each do |assoc|
|
75
|
+
join_model = from_record.class.reflections[assoc.to_sym].options[:through]
|
76
|
+
if join_model
|
77
|
+
join_model = join_model.to_sym
|
78
|
+
through_assocs[join_model] ||= []
|
79
|
+
through_assocs[join_model].push(assoc)
|
80
|
+
assocs.push(join_model) unless assocs.include?(join_model)
|
81
|
+
#assocs.delete(assoc)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
assoc_attrs = {}
|
86
|
+
assocs.each do |assoc|
|
87
|
+
one_assoc = []
|
88
|
+
assoc_value = from_record.send(assoc)
|
89
|
+
# if it's has_one it won't be an array
|
90
|
+
assoc_value = [assoc_value] if assoc_value && assoc_value.class != Array
|
91
|
+
assoc_value ||= []
|
92
|
+
assoc_value.each do |m|
|
93
|
+
if m.new_record?
|
94
|
+
one_assoc.push(m.get_moderation_attributes)
|
95
|
+
else
|
96
|
+
one_assoc.push(m.id)
|
97
|
+
end
|
98
|
+
if through_assocs[assoc.to_sym]
|
99
|
+
one_assoc.last[:associations] = get_assocs_for_moderation(:all, m)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
assoc_attrs[assoc] = one_assoc unless one_assoc.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
assoc_attrs
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module HasModerated
|
2
|
+
module Adapters
|
3
|
+
module Proxy
|
4
|
+
def self.add_moderations_association(klass)
|
5
|
+
if klass.superclass.to_s == "ActiveRecord::Base"
|
6
|
+
Adapters::ActiveRecord::add_moderations_association(klass)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.get_default_moderation_attributes(record)
|
11
|
+
if record.class.superclass.to_s == "ActiveRecord::Base"
|
12
|
+
Adapters::ActiveRecord::get_default_moderation_attributes(record)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.hashize_association(record, assoc_name, m)
|
17
|
+
if record.class.superclass.to_s == "ActiveRecord::Base"
|
18
|
+
Adapters::ActiveRecord::hashize_association(record, assoc_name, m)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,157 @@
|
|
1
|
+
module HasModerated
|
2
|
+
module Associations
|
3
|
+
module Base
|
4
|
+
|
5
|
+
# Class methods included into ActiveRecord::Base so that you can call them in
|
6
|
+
# your ActiveRecord models.
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# Will moderate the passed in associations if they are supported.
|
10
|
+
# Example: has_moderated_association(:posts, :comments).
|
11
|
+
# Also supports passing :all to moderate all associations, but I personally
|
12
|
+
# do not recommend using this option.
|
13
|
+
# @param [Hash] associations the associations to moderate
|
14
|
+
def has_moderated_association(*args)
|
15
|
+
# some common initialization (lazy loading)
|
16
|
+
HasModerated::Common::init(self)
|
17
|
+
|
18
|
+
args = [args] unless args.kind_of? Array
|
19
|
+
|
20
|
+
# handle :all option
|
21
|
+
assoc_names = if args.include?(:all)
|
22
|
+
self.reflections.keys.reject do |r|
|
23
|
+
r == :moderations
|
24
|
+
end
|
25
|
+
else
|
26
|
+
args
|
27
|
+
end
|
28
|
+
|
29
|
+
# process associations + lazy loading
|
30
|
+
assoc_names.map{ |name| self.reflections[name] }.each do |assoc|
|
31
|
+
case assoc.macro
|
32
|
+
when :has_many then
|
33
|
+
self.send :extend, HasModerated::Associations::HasMany::ClassMethods
|
34
|
+
has_moderated_has_many_association(assoc)
|
35
|
+
when :has_one then
|
36
|
+
self.send :extend, HasModerated::Associations::HasOne::ClassMethods
|
37
|
+
has_moderated_has_one_association(assoc)
|
38
|
+
else raise "don't know how to moderate association macro #{assoc.macro}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end # module
|
43
|
+
|
44
|
+
module ApplyModeration
|
45
|
+
# just a helper
|
46
|
+
def self.try_without_moderation(*args, &block)
|
47
|
+
HasModerated::Common::try_without_moderation(*args, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.add_assoc_to_record(to, assoc_id, reflection)
|
51
|
+
return unless to && assoc_id
|
52
|
+
|
53
|
+
# TODO has_one weirness?
|
54
|
+
if reflection.macro == :has_many
|
55
|
+
HasModerated::Associations::Collection::AssociationHelpers::add_assoc_to_record(to, assoc_id, reflection)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.delete_assoc_from_record(from, assoc_id, reflection)
|
60
|
+
return unless from && assoc_id
|
61
|
+
|
62
|
+
if reflection.macro == :has_one
|
63
|
+
HasModerated::Associations::HasOne::AssociationHelpers::delete_assoc_from_record(from, assoc_id, reflection)
|
64
|
+
else
|
65
|
+
HasModerated::Associations::Collection::AssociationHelpers::delete_assoc_from_record(from, assoc_id, reflection)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.apply_add_association(to, reflection, attrs)
|
70
|
+
klass = reflection.class_name.constantize
|
71
|
+
fk = HasModerated::Adapters::ActiveRecord::foreign_key(reflection)
|
72
|
+
|
73
|
+
attrs = HashWithIndifferentAccess.new(attrs) if attrs.kind_of? Hash
|
74
|
+
|
75
|
+
# TODO: perhaps allow to change existing associated object
|
76
|
+
if attrs.class != Fixnum && !attrs[:id].blank?
|
77
|
+
attrs = attrs[:id].to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
# parse new associations
|
81
|
+
try_without_moderation(to) do |rec|
|
82
|
+
arec = nil
|
83
|
+
# PARAM = ID
|
84
|
+
if attrs.class == Fixnum
|
85
|
+
arec = klass.find_by_id(attrs)
|
86
|
+
add_assoc_to_record(rec, arec, reflection)
|
87
|
+
# PARAM = Hash (create)
|
88
|
+
elsif attrs.kind_of? Hash
|
89
|
+
arec = klass.new
|
90
|
+
# set foreign key first, may be required sometimes
|
91
|
+
add_assoc_to_record(rec, arec, reflection)
|
92
|
+
attrs.each_pair do |key, val|
|
93
|
+
next if key.to_s == 'associations' || key.to_s == 'id' || key.to_s == fk
|
94
|
+
arec.send(key.to_s+"=", val)
|
95
|
+
end
|
96
|
+
# recursive, used for has_many :through
|
97
|
+
apply(arec, attrs) if attrs[:associations].present?
|
98
|
+
else
|
99
|
+
raise "don't know how to parse #{attrs.class}"
|
100
|
+
end
|
101
|
+
if arec
|
102
|
+
try_without_moderation(arec) do
|
103
|
+
arec.save(:validate => false) # don't run validations
|
104
|
+
# TODO: validations? sup?
|
105
|
+
end
|
106
|
+
if reflection.collection?
|
107
|
+
rec.send(reflection.name.to_s) << arec
|
108
|
+
else
|
109
|
+
rec.send(reflection.name.to_s + "=", arec)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.apply_delete_association(to, reflection, attrs)
|
116
|
+
m = reflection.class_name.constantize
|
117
|
+
|
118
|
+
fk = HasModerated::Adapters::ActiveRecord::foreign_key(reflection)
|
119
|
+
|
120
|
+
return if attrs.blank?
|
121
|
+
return if attrs.class != Fixnum
|
122
|
+
|
123
|
+
try_without_moderation(to) do
|
124
|
+
delete_assoc_from_record(to, attrs, reflection)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# add/delete associations to a record
|
129
|
+
def self.apply(moderation, data)
|
130
|
+
record = if moderation.kind_of? Moderation
|
131
|
+
moderation.moderatable
|
132
|
+
else
|
133
|
+
moderation
|
134
|
+
end
|
135
|
+
associations = data[:associations]
|
136
|
+
delete_associations = data[:delete_associations]
|
137
|
+
|
138
|
+
associations && associations.each_pair do |assoc_name, assoc_records|
|
139
|
+
reflection = record.class.reflections[assoc_name.to_sym]
|
140
|
+
|
141
|
+
assoc_records.each do |attrs|
|
142
|
+
apply_add_association(record, reflection, attrs) if attrs.present?
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
delete_associations && delete_associations.each_pair do |assoc_name, assoc_records|
|
147
|
+
reflection = record.class.reflections[assoc_name.to_sym]
|
148
|
+
|
149
|
+
assoc_records.each do |attrs|
|
150
|
+
apply_delete_association(record, reflection, attrs) if attrs.present?
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end # module
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module HasModerated
|
2
|
+
module Associations
|
3
|
+
module Collection
|
4
|
+
module AssociationPatches
|
5
|
+
# moderate adding associations
|
6
|
+
def add_to_target_with_moderation record
|
7
|
+
if owner.moderation_disabled
|
8
|
+
add_to_target_without_moderation record
|
9
|
+
else
|
10
|
+
owner.add_associations_moderated(self.reflection.name => [record])
|
11
|
+
record
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# moderate removing associations
|
16
|
+
def delete_records_with_moderation(records, method) # method comes from :dependent => :destroy
|
17
|
+
if owner.new_record? || owner.moderation_disabled
|
18
|
+
delete_records_without_moderation(records, method)
|
19
|
+
else
|
20
|
+
# TODO only care for records which are already in database
|
21
|
+
record_ids = records.map(&:id)
|
22
|
+
owner.delete_associations_moderated(self.reflection.name => record_ids)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
protected
|
29
|
+
def has_moderated_collection_association(reflection)
|
30
|
+
after_initialize do
|
31
|
+
assoc = self.association(reflection.name)
|
32
|
+
# check association type
|
33
|
+
if reflection.collection?
|
34
|
+
# avoid multiple patch problem and still work for tests
|
35
|
+
if assoc.respond_to?(:add_to_target_without_moderation)
|
36
|
+
assoc.class_eval do
|
37
|
+
alias_method :add_to_target, :add_to_target_without_moderation
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# patch methods to moderate changes
|
41
|
+
assoc.class.send(:include, AssociationPatches)
|
42
|
+
assoc.class_eval do
|
43
|
+
alias_method_chain :add_to_target, :moderation
|
44
|
+
alias_method_chain :delete_records, :moderation
|
45
|
+
end
|
46
|
+
else
|
47
|
+
raise "called on an invalid association"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
module AssociationHelpers
|
54
|
+
def self.add_assoc_to_record_habtm_hmt(target, assoc_record, reflection)
|
55
|
+
field = if reflection.options[:join_table].present?
|
56
|
+
join_table = reflection.options[:join_table].to_s
|
57
|
+
results = assoc_record.class.reflections.reject do |assoc_name, assoc|
|
58
|
+
!(assoc.options[:join_table] && assoc.options[:join_table].to_s == join_table)
|
59
|
+
end
|
60
|
+
if results.count != 1
|
61
|
+
raise "has_moderated: Cannot determine join table for a Habtm association!"
|
62
|
+
end
|
63
|
+
results.first[1].name.to_s
|
64
|
+
elsif reflection.options[:through].present?
|
65
|
+
through_model = reflection.options[:through].to_s
|
66
|
+
results = assoc_record.class.reflections.reject do |assoc_name, assoc|
|
67
|
+
!(assoc.options[:through] && assoc.options[:through].to_s == through_model)
|
68
|
+
end
|
69
|
+
if results.count != 1
|
70
|
+
raise "has_moderated: Cannot determine correct association for a has_many :through association!"
|
71
|
+
end
|
72
|
+
results.first[1].name.to_s
|
73
|
+
else
|
74
|
+
raise "has_moderated: Cannot determine association details!"
|
75
|
+
end
|
76
|
+
assoc_record.send(field) << target
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.add_assoc_to_record_hm(to, record, reflection)
|
80
|
+
fk = HasModerated::Adapters::ActiveRecord::foreign_key(reflection).try(:to_s)
|
81
|
+
field = if !reflection.options[:as].blank?
|
82
|
+
# todo: extract
|
83
|
+
reflection.options[:as].to_s
|
84
|
+
elsif !fk.blank?
|
85
|
+
all_keys = []
|
86
|
+
results = record.class.reflections.reject do |assoc_name, assoc|
|
87
|
+
all_keys.push(HasModerated::Adapters::ActiveRecord::foreign_key(assoc).try(:to_s))
|
88
|
+
!(HasModerated::Adapters::ActiveRecord::foreign_key(assoc).try(:to_s) == fk)
|
89
|
+
end
|
90
|
+
if results.blank?
|
91
|
+
raise "Please set foreign_key for both belongs_to and has_one/has_many! fk: #{fk}, keys: #{all_keys.to_yaml}"
|
92
|
+
end
|
93
|
+
results.first[1].name.to_s
|
94
|
+
else
|
95
|
+
to.class.to_s.underscore # TODO hardcoded, fix
|
96
|
+
end
|
97
|
+
HasModerated::Common::try_without_moderation(record) do
|
98
|
+
record.send(field + "=", to)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.add_assoc_to_record(to, record, reflection)
|
103
|
+
if reflection.macro == :has_and_belongs_to_many || !reflection.options[:through].blank?
|
104
|
+
add_assoc_to_record_habtm_hmt(to, record, reflection)
|
105
|
+
else
|
106
|
+
add_assoc_to_record_hm(to, record, reflection)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.delete_assoc_from_record(from, assoc_id, reflection)
|
111
|
+
return unless from && assoc_id
|
112
|
+
klass = reflection.class_name.constantize
|
113
|
+
|
114
|
+
if reflection.macro == :has_and_belongs_to_many || !reflection.options[:through].blank? || reflection.macro == :has_many
|
115
|
+
from.send(reflection.name).delete(klass.find_by_id(assoc_id))
|
116
|
+
else
|
117
|
+
raise "Cannot delete association for this type of associations!"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module HasModerated
|
2
|
+
module Associations
|
3
|
+
module HasMany
|
4
|
+
module ClassMethods
|
5
|
+
protected
|
6
|
+
def has_moderated_has_many_association(reflection)
|
7
|
+
# lazy load
|
8
|
+
self.send :extend, HasModerated::Associations::Collection::ClassMethods
|
9
|
+
has_moderated_collection_association(reflection)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module HasModerated
|
2
|
+
module Associations
|
3
|
+
module HasOne
|
4
|
+
module AssociationPatches
|
5
|
+
def replace_with_moderation(record, save = true)
|
6
|
+
# TODO: code duplication
|
7
|
+
if owner.new_record? || owner.moderation_disabled
|
8
|
+
replace_without_moderation record
|
9
|
+
else
|
10
|
+
if record.blank? # use dummy value so it's not nil
|
11
|
+
owner.delete_associations_moderated(self.reflection.name => [1])
|
12
|
+
else
|
13
|
+
owner.add_associations_moderated(self.reflection.name => [record])
|
14
|
+
end
|
15
|
+
record
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
protected
|
22
|
+
def has_moderated_has_one_association(reflection)
|
23
|
+
after_initialize do
|
24
|
+
assoc = self.association(reflection.name)
|
25
|
+
# check association type
|
26
|
+
if !reflection.collection?
|
27
|
+
# avoid multiple patch problem and still work for tests
|
28
|
+
if assoc.respond_to?(:replace_without_moderation)
|
29
|
+
assoc.class_eval do
|
30
|
+
alias_method :replace, :replace_without_moderation
|
31
|
+
end
|
32
|
+
end
|
33
|
+
# patch methods to moderate changes
|
34
|
+
assoc.class_eval do
|
35
|
+
include AssociationPatches
|
36
|
+
alias_method_chain :replace, :moderation
|
37
|
+
end
|
38
|
+
else
|
39
|
+
raise "called on an invalid association"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module AssociationHelpers
|
46
|
+
def self.add_assoc_to_record(*args)
|
47
|
+
# same as HasMany
|
48
|
+
HasModerated::Associations::Collection::AssociationHelpers::add_assoc_to_record(*args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.delete_assoc_from_record(from, assoc_id, reflection)
|
52
|
+
from.send("#{reflection.name}=", nil)
|
53
|
+
from.save # TODO necessary?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -47,7 +47,7 @@ module HasModerated
|
|
47
47
|
end
|
48
48
|
|
49
49
|
module InstanceMethods
|
50
|
-
attr_accessor :
|
50
|
+
attr_accessor :moderation_disabled # in case this model itself is not moderated
|
51
51
|
# maybe autodetect fields that use carrierwave, or specify them
|
52
52
|
def moderatable_hashize
|
53
53
|
attrs = self.attributes
|
@@ -64,7 +64,7 @@ module HasModerated
|
|
64
64
|
def store_photo_with_moderation!
|
65
65
|
is_moderated = self.class.respond_to?(:moderated_attributes) &&
|
66
66
|
self.class.moderated_attributes.include?("carrierwave_photo")
|
67
|
-
if self.
|
67
|
+
if self.moderation_disabled || !is_moderated || !self.photo_changed?
|
68
68
|
store_photo_without_moderation!
|
69
69
|
else
|
70
70
|
self.moderations.create!({
|
@@ -77,7 +77,7 @@ module HasModerated
|
|
77
77
|
def write_photo_identifier_with_moderation
|
78
78
|
is_moderated = self.class.respond_to?(:moderated_attributes) &&
|
79
79
|
self.class.moderated_attributes.include?("carrierwave_photo")
|
80
|
-
if self.
|
80
|
+
if self.moderation_disabled || !is_moderated || !self.photo_changed?
|
81
81
|
write_photo_identifier_without_moderation
|
82
82
|
end
|
83
83
|
end
|