acts_as_sourceable 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # ActsAsSourceable
2
+
3
+ Allows the RRN to perform garbage project on categories that are no longer referenced.
4
+
5
+ ## Installation
6
+
7
+ ### In your gemfile
8
+ ```ruby
9
+ gem 'acts_as_sourceable'
10
+ ```
11
+
12
+ ### Migration
13
+ ```ruby
14
+ create_table :acts_as_sourceable_registry do |t|
15
+ t.belongs_to :sourceable, :polymorphic => true
16
+ t.belongs_to :source, :polymorphic => true
17
+ t.timestamps
18
+ end
19
+
20
+ add_index :acts_as_sourceable_registry, [:sourceable_id, :sourceable_type], :name => :index_acts_as_sourceable_sourceables
21
+ add_index :acts_as_sourceable_registry, [:source_id, :source_type], :name => :index_acts_as_sourceable_sources
22
+
23
+ ```
24
+
25
+ ## Usage
@@ -15,8 +15,8 @@ module ActsAsSourceable
15
15
  if acts_as_sourceable_options[:through]
16
16
  def sources; send(acts_as_sourceable_options[:through]) || []; end
17
17
  else
18
- has_one :sourceable_institution, :class_name => 'ActsAsSourceable::Registry', :as => :sourceable, :dependent => :delete
19
- def sources; sourceable_institution.try(:sources) || []; end
18
+ has_many :sourceable_registry_entries, :class_name => 'ActsAsSourceable::RegistryEntry', :as => :sourceable, :dependent => :delete_all
19
+ def sources; self.sourceable_registry_entries.includes(:source).collect(&:source); end
20
20
  end
21
21
  end
22
22
 
@@ -25,7 +25,7 @@ module ActsAsSourceable
25
25
 
26
26
  # If a cache column is provided, use that to determine which records are sourced and unsourced
27
27
  # Elsif the records can be derived, we need to check the flattened item tables for any references
28
- # Else we check the sourceable_institutions to see if the record has a recorded source
28
+ # Else we check the registry_entries to see if the record has a recorded source
29
29
  if options[:cache_column]
30
30
  scope :sourced, where(options[:cache_column] => true)
31
31
  scope :unsourced, where(options[:cache_column] => false)
@@ -33,25 +33,18 @@ module ActsAsSourceable
33
33
  scope :sourced, joins(options[:through]).uniq
34
34
  scope :unsourced, joins("LEFT OUTER JOIN (#{sourced.to_sql}) sourced ON sourced.id = #{table_name}.id").where("sourced.id IS NULL")
35
35
  else
36
- scope :sourced, joins(:sourceable_institution)
36
+ scope :sourced, joins(:sourceable_registry_entries).uniq
37
37
  scope :unsourced, joins("LEFT OUTER JOIN (#{sourced.to_sql}) sourced ON sourced.id = #{table_name}.id").where("sourced.id IS NULL")
38
38
  end
39
39
 
40
40
  # Add a way of finding everything sourced by a particular set of records
41
41
  if options[:through]
42
- def sourced_by(*sources)
43
- raise NotImplementedError # TODO
42
+ def self.sourced_by(source)
43
+ self.joins(acts_as_sourceable_options[:through]).where(reflect_on_association(acts_as_sourceable_options[:through]).table_name => {:id => source.id})
44
44
  end
45
45
  else
46
- def sourced_by(*sources)
47
- holding_institution_ids, collection_ids, item_ids = ActsAsSourceable::HelperMethods.group_ids_by_class(sources)
48
-
49
- arel_table = ActsAsSourceable::Registry.arel_table
50
- h_contraint = arel_table[:holding_institution_ids].array_overlap(holding_institution_ids)
51
- c_contraint = arel_table[:collection_ids].array_overlap(collection_ids)
52
- i_contraint = arel_table[:item_ids].array_overlap(item_ids)
53
-
54
- sourced.where(h_contraint.or(c_contraint).or(i_contraint))
46
+ def self.sourced_by(source)
47
+ self.joins(:sourceable_registry_entries).where(ActsAsSourceable::RegistryEntry.table_name => {:source_type => source.class, :source_id => source.id}).uniq
55
48
  end
56
49
  end
57
50
 
@@ -86,7 +79,7 @@ module ActsAsSourceable
86
79
 
87
80
  def unsource
88
81
  scoping { @klass.update_all("#{acts_as_sourceable_options[:cache_column]} = false", @klass.acts_as_sourceable_options[:cache_column] => true) } if @klass.acts_as_sourceable_options[:cache_column]
89
- scoping { ActsAsSourceable::Registry.where("sourceable_type = ? AND sourceable_id IN (#{@klass.select("#{@klass.table_name}.id").to_sql})", @klass.name).delete_all }
82
+ scoping { ActsAsSourceable::RegistryEntry.where("sourceable_type = ? AND sourceable_id IN (#{@klass.select("#{@klass.table_name}.id").to_sql})", @klass.name).delete_all }
90
83
  end
91
84
  end
92
85
 
@@ -115,94 +108,73 @@ module ActsAsSourceable
115
108
 
116
109
  # Add the given holding_institutions, collections, and items
117
110
  def add_sources(*sources)
118
- holding_institution_ids, collection_ids, item_ids = ActsAsSourceable::HelperMethods.group_ids_by_class(sources)
119
- registry = init_registry_entry
120
- set_sources(registry.holding_institution_ids + holding_institution_ids, registry.collection_ids + collection_ids, registry.item_ids + item_ids)
111
+ raise "Cannot set sources of a #{self.class.name}. They are sourced through #{acts_as_sourceable_options[:through]}" if acts_as_sourceable_options[:through]
112
+
113
+ sources = Array(sources).flatten
114
+ sources.each do |source|
115
+ source_scope(source).first_or_create!
116
+ end
117
+ update_sourceable_cache_column(true) if sources.present?
121
118
  end
122
119
  alias_method :add_source, :add_sources
123
120
 
124
121
  # Remove the given holding_institutions, collections, and items
125
122
  def remove_sources(*sources)
126
- holding_institution_ids, collection_ids, item_ids = ActsAsSourceable::HelperMethods.group_ids_by_class(sources)
127
- registry = init_registry_entry
128
- set_sources(registry.holding_institution_ids - holding_institution_ids, registry.collection_ids - collection_ids, registry.item_ids - item_ids)
129
- end
130
- alias_method :remove_source, :remove_sources
123
+ raise "Cannot set sources of a #{self.class.name}. They are sourced through #{acts_as_sourceable_options[:through]}" if acts_as_sourceable_options[:through]
131
124
 
132
- # Record which holding_institution, collection, and optionally which item the record came from
133
- # If the record has no sources, the sourceable institution is deleted
134
- # NOTE: HoldingInstitutions are stored in the production database (so don't get any crazy ideas when refactoring this code)
135
- def set_sources(holding_institution_ids, collection_ids, item_ids)
136
- registry = init_registry_entry
137
- registry.holding_institution_ids = Array(holding_institution_ids).uniq
138
- registry.collection_ids = Array(collection_ids).uniq
139
- registry.item_ids = Array(item_ids).uniq
140
-
141
- if holding_institution_ids.any? || collection_ids.any? || item_ids.any?
142
- registry.save!
143
- set_sourceable_cache_column(true)
144
- elsif registry.persisted?
145
- registry.destroy
146
- set_sourceable_cache_column(false)
125
+ sources = Array(sources).flatten
126
+ sources.each do |source|
127
+ source_scope(source).delete_all
147
128
  end
129
+ update_sourceable_cache_column(false) if self.sourceable_registry_entries.empty?
148
130
  end
131
+ alias_method :remove_source, :remove_sources
149
132
 
150
133
  private
151
134
 
152
- def init_registry_entry
153
- raise "Cannot set sources of a #{self.class.name}. They are sourced through #{acts_as_sourceable_options[:through]}" if acts_as_sourceable_options[:through]
154
-
155
- ActsAsSourceable::Registry.where(:sourceable_type => self.class.name, :sourceable_id => self.id).first_or_initialize
135
+ def source_scope(source)
136
+ ActsAsSourceable::RegistryEntry.where(:sourceable_type => self.class.name, :sourceable_id => self.id, :source_type => source.class, :source_id => source.id)
156
137
  end
157
-
158
- def set_sourceable_cache_column(value)
159
- update_column(acts_as_sourceable_options[:cache_column], value) if acts_as_sourceable_options[:cache_column] # Update via sql because we don't need callbacks and validations called
138
+
139
+ def update_sourceable_cache_column(value = nil)
140
+ return unless acts_as_sourceable_options[:cache_column] # Update via sql because we don't need callbacks and validations called
141
+
142
+ if value
143
+ update_column(acts_as_sourceable_options[:cache_column], value)
144
+ else
145
+ update_column(acts_as_sourceable_options[:cache_column], sourceable_registry_entries.present?)
146
+ end
160
147
  end
161
148
  end
162
149
 
163
150
  module HelperMethods
164
- # Given an array of HoldingInstitutions, Collections, and Items, returns arrays containing only the records of each class.
165
- # Order of return arrays is [HoldingInstitutions, Collections, Items]
166
- def self.group_by_class(*sources)
167
- groups = Array(sources).flatten.group_by(&:class)
168
- return [groups[HoldingInstitution] || [], groups[Collection] || [], groups[Item] || []]
169
- end
170
-
171
- def self.group_ids_by_class(*sources)
172
- group_by_class(*sources).collect!{|group| group.collect(&:id)}
173
- end
174
-
175
- # Removes sourceable institutions that no longer belong to a record or holding institution
151
+ # Removes registry entries that no longer belong to a sourceable, item, collection, or holding institution
176
152
  def self.garbage_collect
177
- ActsAsSourceable::Registry.pluck(:sourceable_type).uniq.each do |sourceable_type|
153
+ # Remove all registry entries where the sourceable is gone
154
+ ActsAsSourceable::RegistryEntry.pluck(:sourceable_type).uniq.each do |sourceable_type|
178
155
  sourceable_table_name = sourceable_type.constantize.table_name
179
- sourceable_id_sql = ActsAsSourceable::Registry
180
- .select("#{ActsAsSourceable::Registry.table_name}.id")
156
+ sourceable_id_sql = ActsAsSourceable::RegistryEntry
157
+ .select("#{ActsAsSourceable::RegistryEntry.table_name}.id")
181
158
  .where(:sourceable_type => sourceable_type)
182
- .joins("LEFT OUTER JOIN #{sourceable_table_name} ON #{sourceable_table_name}.id = #{ActsAsSourceable::Registry.table_name}.sourceable_id")
159
+ .joins("LEFT OUTER JOIN #{sourceable_table_name} ON #{sourceable_table_name}.id = #{ActsAsSourceable::RegistryEntry.table_name}.sourceable_id")
183
160
  .where("#{sourceable_table_name}.id IS NULL").to_sql
184
161
 
185
- ActsAsSourceable::Registry.delete_all("id IN (#{sourceable_id_sql})")
162
+ ActsAsSourceable::RegistryEntry.delete_all("id IN (#{sourceable_id_sql})")
186
163
  end
187
164
 
188
-
189
- # Repair all Registry entries that reference missing items, collections, or holding institutions
190
- holding_institution_ids = HoldingInstitution.pluck(:id)
191
- collection_ids = Collection.pluck(:id)
192
-
193
- [:holding_institution, :collection, :item].each do |type|
194
- registries = ActsAsSourceable::Registry
195
- registries = registries.joins("LEFT OUTER JOIN #{type}s ON #{type}s.id = ANY(#{type}_ids)")
196
- registries = registries.group("#{ActsAsSourceable::Registry.table_name}.id")
197
- # Having at least one listed source_id and no matching sources, or fewer matches than the total listed sources
198
- registries = registries.having("(array_length(#{type}_ids, 1) > 0 AND EVERY(#{type}s.id IS NULL)) OR count(*) < array_length(#{type}_ids, 1)")
199
-
200
- # Fix the registry entries that are wrong
201
- registries.includes(:sourceable).each do |registry|
202
- item_ids = Item.where(:id => registry.item_ids).pluck(:id)
203
- # ActiveRecord::Base.logger.debug "Registry #{registry.id}: holding_institution_ids: #{registry.holding_institution_ids.inspect} => #{registry.holding_institution_ids & holding_institution_ids}, collection_ids: #{registry.collection_ids.inspect} => #{registry.collection_ids & collection_ids}, item_ids: #{registry.item_ids.inspect} => #{registry.item_ids & item_ids} "
204
- registry.sourceable.set_sources(registry.holding_institution_ids & holding_institution_ids, registry.collection_ids & collection_ids, registry.item_ids & item_ids)
205
- end
165
+ # Remove all registry entries where the source is gone
166
+ ActsAsSourceable::RegistryEntry.pluck(:source_type).uniq.each do |source_type|
167
+ source_class = source_type.constantize
168
+ source_table_name = source_class.table_name
169
+ source_id_sql = ActsAsSourceable::RegistryEntry
170
+ .select("#{ActsAsSourceable::RegistryEntry.table_name}.id")
171
+ .where(:source_type => source_type)
172
+ .joins("LEFT OUTER JOIN #{source_table_name} ON #{source_table_name}.id = #{ActsAsSourceable::RegistryEntry.table_name}.source_id")
173
+ .where("#{source_table_name}.id IS NULL").to_sql
174
+
175
+ sourceables = ActsAsSourceable::RegistryEntry.where("id IN (#{source_id_sql})").collect(&:sourceable)
176
+ ActsAsSourceable::RegistryEntry.where("id IN (#{source_id_sql})").delete_all
177
+ sourceables.each{|sourceable| sourceable.send(:update_sourceable_cache_column) }
206
178
  end
207
179
  end
208
180
  end
@@ -0,0 +1,9 @@
1
+ module ActsAsSourceable
2
+ class RegistryEntry < ActiveRecord::Base
3
+ self.table_name = 'acts_as_sourceable_registry'
4
+
5
+ belongs_to :sourceable, :polymorphic => true
6
+ belongs_to :source, :polymorphic => true
7
+ validates_presence_of :sourceable_type, :sourceable_id, :source_type, :source_id
8
+ end
9
+ end
@@ -1,4 +1,4 @@
1
1
  require 'acts_as_sourceable/acts_as_sourceable'
2
- require 'acts_as_sourceable/registry'
2
+ require 'acts_as_sourceable/registry_entry'
3
3
 
4
4
  ActiveRecord::Base.extend ActsAsSourceable::ActMethod
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_sourceable
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,18 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
  date: 2013-01-26 00:00:00.000000000 Z
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: postgres_ext
17
- requirement: &70193900896400 !ruby/object:Gem::Requirement
18
- none: false
19
- requirements:
20
- - - ~>
21
- - !ruby/object:Gem::Version
22
- version: 0.1.0
23
- type: :runtime
24
- prerelease: false
25
- version_requirements: *70193900896400
14
+ dependencies: []
26
15
  description: Allows the RRN to perform garbage collection on categories that are no
27
16
  longer referenced.
28
17
  email: technical@rrnpilot.org
@@ -31,9 +20,9 @@ extensions: []
31
20
  extra_rdoc_files: []
32
21
  files:
33
22
  - lib/acts_as_sourceable/acts_as_sourceable.rb
34
- - lib/acts_as_sourceable/registry.rb
23
+ - lib/acts_as_sourceable/registry_entry.rb
35
24
  - lib/acts_as_sourceable.rb
36
- - README.rdoc
25
+ - README.md
37
26
  homepage: http://github.com/rrn/acts_as_sourceable
38
27
  licenses: []
39
28
  post_install_message:
@@ -54,7 +43,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
54
43
  version: '0'
55
44
  requirements: []
56
45
  rubyforge_project:
57
- rubygems_version: 1.8.10
46
+ rubygems_version: 1.8.25
58
47
  signing_key:
59
48
  specification_version: 3
60
49
  summary: perform garbage collection on categories that are no longer referenced
data/README.rdoc DELETED
@@ -1,3 +0,0 @@
1
- == ActsAsSourceable
2
-
3
- Allows the RRN to perform garbage project on categories that are no longer referenced.
@@ -1,12 +0,0 @@
1
- module ActsAsSourceable
2
- class Registry < ActiveRecord::Base
3
- self.table_name = 'acts_as_sourceable_registry'
4
-
5
- belongs_to :sourceable, :polymorphic => true
6
- validates_presence_of :sourceable_type, :sourceable_id
7
-
8
- def sources
9
- HoldingInstitution.find(self.holding_institution_ids) + Collection.find(self.collection_ids) + Item.find(self.item_ids)
10
- end
11
- end
12
- end