acts_as_sourceable 1.0.3 → 1.0.4

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.
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ .DS_Store
@@ -0,0 +1,3 @@
1
+ == ActsAsSourceable
2
+
3
+ Allows the RRN to perform garbage project on categories that are no longer referenced.
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'acts_as_sourceable'
3
+ s.version = '1.0.4'
4
+ s.date = %q{2010-09-16}
5
+ s.email = 'technical@rrnpilot.org'
6
+ s.homepage = 'http://github.com/rrn/acts_as_sourceable'
7
+ s.summary = 'perform garbage collection on categories that are no longer referenced'
8
+ s.description = 'Allows the RRN to perform garbage collection on categories that are no longer referenced.'
9
+ s.authors = ['Nicholas Jakobsen', 'Ryan Wallace']
10
+ s.require_paths = ["lib"]
11
+ s.files = `git ls-files`.split("\n")
12
+ end
@@ -0,0 +1,4 @@
1
+ require 'acts_as_sourceable/acts_as_sourceable'
2
+ require 'acts_as_sourceable/sourceable_institution'
3
+
4
+ ActiveRecord::Base.extend ActsAsSourceable::ActMethod
@@ -0,0 +1,122 @@
1
+ module ActsAsSourceable
2
+ module ActMethod
3
+ def acts_as_sourceable(options = {})
4
+ # Include Class and Instance Methods
5
+ ActiveRecord::Relation.send(:include, ActsAsSourceable::ActiveRelationMethods)
6
+ extend ActsAsSourceable::ClassMethods
7
+ include ActsAsSourceable::InstanceMethods
8
+
9
+ has_many :sourceable_institutions, :as => :sourceable, :dependent => :destroy
10
+ has_many :sources, :through => :sourceable_institutions, :source => :holding_institution
11
+
12
+ # Delegate the relation methods to the relation
13
+ class << self
14
+ delegate :update_sources, :unsource, :to => :scoped
15
+ end
16
+
17
+ class_attribute :sourceable_cache_column, :sourceable_used_by, :sourceable_sourced_by
18
+ self.sourceable_cache_column = options[:cache_column]
19
+ self.sourceable_used_by = options[:used_by]
20
+ self.sourceable_sourced_by = options[:sourced_by]
21
+
22
+ # If a cache column is provided, use that to determine which records are sourced and unsourced
23
+ # Elsif the records can be derived, we need to check the flattened item tables for any references
24
+ # Else we check the sourceable_institutions to see if the record has a recorded source
25
+ if sourceable_cache_column
26
+ scope :sourced, where(sourceable_cache_column => true)
27
+ scope :unsourced, where(sourceable_cache_column => false)
28
+ else
29
+ scope :sourced, joins(:sourceable_institutions).group("#{table_name}.id")
30
+ scope :unsourced, joins("LEFT OUTER JOIN sourceable_institutions ON sourceable_id = #{quoted_table_name}.id and sourceable_type = '#{self.name}'").where("sourceable_id IS NULL")
31
+ end
32
+
33
+ # Create a scope that returns record that is not used by the associations in sourceable_used_by
34
+ if sourceable_used_by
35
+ scope :unused, where(Array(sourceable_used_by).collect {|usage_association| "id NOT IN (" + select("#{table_name}.id").joins(usage_association).group("#{table_name}.id").to_sql + ")"}.join(' AND '))
36
+ scope :orphaned, unsourced.unused
37
+ else
38
+ scope :orphaned, unsourced
39
+ end
40
+ end
41
+ end
42
+
43
+ module ActiveRelationMethods
44
+ def update_sources
45
+ scoping { @klass.find_each(&:update_sources) }
46
+ end
47
+
48
+ def unsource
49
+ scoping { @klass.update_all("#{sourceable_cache_column} = false", @klass.sourceable_cache_column => true) } if @klass.sourceable_cache_column
50
+ scoping { SourceableInstitution.where("sourceable_type = ? AND sourceable_id IN (#{@klass.select(:id).to_sql})", @klass.name).delete_all }
51
+ end
52
+ end
53
+
54
+ module ClassMethods
55
+ def acts_like_sourceable?
56
+ true
57
+ end
58
+ end
59
+
60
+ module InstanceMethods
61
+ def acts_like_sourceable?
62
+ true
63
+ end
64
+
65
+ # Automatically update the sources for this model
66
+ # If the model gets its sources from another model, collect the sources of that model and record them as your own
67
+ # Else, this model must belong to a holding institution, so that is the source
68
+ def update_sources
69
+ if sourceable_sourced_by
70
+ if self.class.reflect_on_association(sourceable_sourced_by.to_sym).collection?
71
+ source_id_sql = send(sourceable_sourced_by).joins(:sourceable_institutions).select("sourceable_institutions.holding_institution_id").to_sql
72
+ sources = HoldingInstitution.where("id IN (#{source_id_sql})")
73
+ set_sources(sources)
74
+ else
75
+ set_sources(send(sourceable_sourced_by).sources)
76
+ end
77
+ else
78
+ set_sources(holding_institution)
79
+ end
80
+ end
81
+
82
+ def sourced?
83
+ if sourceable_cache_column
84
+ self[sourceable_cache_column]
85
+ else
86
+ self.class.sourced.exists?(self)
87
+ end
88
+ end
89
+
90
+ def unsourced?
91
+ !sourced?
92
+ end
93
+
94
+ # NOTE: We do a much more verbose method of assigning sources than the obvious self.sources = Array(holding_institutions)
95
+ # because HoldingInstitutions are present in the production database, and assigning sources causes rails to use the
96
+ # production database (as opposed to the conversion database) to check for existing sources. This is obviously bad
97
+ # because the sources in the production database do not reflect those that are in the conversion database since we
98
+ # unsource many things during conversion.
99
+ def set_sources(holding_institutions)
100
+ holding_institution_ids = Array(holding_institutions).collect(&:id)
101
+ existing_source_ids = sourceable_institutions.pluck('holding_institution_id')
102
+
103
+ # Delete those that have been removed
104
+ condition = holding_institution_ids.any? ? "holding_institution_id NOT IN (?)" : nil # Can't use "NOT IN (?)" for an empty array because the result is always false
105
+ SourceableInstitution.where(:sourceable_type => self.class.name, :sourceable_id => self.id).delete_all(condition)
106
+
107
+ # Add those that are not present
108
+ holding_institution_ids.each do |holding_institution_id|
109
+ self.sourceable_institutions << SourceableInstitution.new(:holding_institution_id => holding_institution_id) unless existing_source_ids.include?(holding_institution_id)
110
+ end
111
+
112
+ set_sourceable_cache_column(holding_institution_ids.any?)
113
+ end
114
+
115
+ private
116
+
117
+ def set_sourceable_cache_column(value)
118
+ # Update via sql because we don't need callbacks and validations called
119
+ self.class.update_all({sourceable_cache_column => value}, :id => id) if sourceable_cache_column
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,16 @@
1
+ class SourceableInstitution < ActiveRecord::Base
2
+ belongs_to :sourceable, :polymorphic => true
3
+ belongs_to :holding_institution
4
+
5
+ validates_presence_of :sourceable_type, :sourceable_id, :holding_institution_id
6
+
7
+ # Removes sourceable institutions that no longer belong to a record or holding institution
8
+ def self.garbage_collect
9
+ ActiveRecord::Base.connection.select_values(SourceableInstitution.select("DISTINCT sourceable_type").to_sql).each do |sourceable_type|
10
+ sourceable_table_name = sourceable_type.constantize.table_name
11
+ sourceable_id_sql = SourceableInstitution.select("sourceable_institutions.id").where(:sourceable_type => sourceable_type).joins("LEFT OUTER JOIN #{sourceable_table_name} ON #{sourceable_table_name}.id = sourceable_institutions.sourceable_id").where("#{sourceable_table_name}.id IS NULL").to_sql
12
+ SourceableInstitution.delete_all("id IN (#{sourceable_id_sql})")
13
+ end
14
+ SourceableInstitution.delete_all(["holding_institution_id NOT IN (?)", HoldingInstitution.all.collect(&:id)])
15
+ end
16
+ end
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: 1.0.3
4
+ version: 1.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -18,7 +18,13 @@ email: technical@rrnpilot.org
18
18
  executables: []
19
19
  extensions: []
20
20
  extra_rdoc_files: []
21
- files: []
21
+ files:
22
+ - .gitignore
23
+ - README.rdoc
24
+ - acts_as_sourceable.gemspec
25
+ - lib/acts_as_sourceable.rb
26
+ - lib/acts_as_sourceable/acts_as_sourceable.rb
27
+ - lib/acts_as_sourceable/sourceable_institution.rb
22
28
  homepage: http://github.com/rrn/acts_as_sourceable
23
29
  licenses: []
24
30
  post_install_message: