acts_as_sourceable 2.0.0 → 2.1.0

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.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