has_moderated 0.0.34 → 1.0.alpha
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.
- 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
|