Module: ApplicationEnumeration

Defined in:
lib/application_enumeration.rb

Overview

Methods for enumerating models, tables, columns etc.

!! If you think that a method belongs here chances are it already exists in a Rails extension.

Note the use of Module.nesting (urbanautomaton.com/blog/2013/08/27/rails-autoloading-hell/)

Constant Summary collapse

[
  :versions,
  :dwc_occurrence,
  :pinboard_items,
  :cached_map_register,
  :cached_map_items,
  :cached_maps
].freeze

Class Method Summary collapse

Class Method Details

.all_submodels(klass) ⇒ Array of Classes

!! See the built in self.descendants for actual inheritance tracking, this is path based. Used in Ranks.

Parameters:

  • klass (Object)

Returns:

  • (Array of Classes)

    all models in the /app/models/#klassklass.name (not necessarily inheriting)



82
83
84
# File 'lib/application_enumeration.rb', line 82

def self.all_submodels(klass)
  Dir.glob(Rails.root + "app/models/#{klass.name.underscore}/**/*.rb").collect{|a| self.model_from_file_name(a) }
end

.alternate_value_attributes(object) ⇒ Array

Returns a list symbols that represent populated, non “cached”, non “_id”, non reserved attributes.

Returns:

  • (Array)

    a list symbols that represent populated, non “cached”, non “_id”, non reserved attributes



25
26
27
28
29
30
31
# File 'lib/application_enumeration.rb', line 25

def self.alternate_value_attributes(object)
  if object.class::ALTERNATE_VALUES_FOR.blank?
    raise("#{object.class} attempted to annotate a class without ALTERNATE_VALUES_FOR -  please inform the programmers")
  else
    object.attributes.select{|k,v| v.present? && object.class::ALTERNATE_VALUES_FOR.include?(k.to_sym)}.keys.map(&:to_sym)
  end
end

.annotatable_attributes(object) ⇒ Array of Symbols

!! Some models have blacklists (e.g. Serial)

Parameters:

  • object (Object)

Returns:

  • (Array of Symbols)

    a whitelist of the attributes of a given instance that may be annotated



37
38
39
# File 'lib/application_enumeration.rb', line 37

def self.annotatable_attributes(object)
  object.attributes.select{|k,v| v.present? && !(k =~ /.*_id\z|cached_*.*/)}.keys.map(&:to_sym) - ( RESERVED_ATTRIBUTES - [:parent_id])
end

.attributes(target) ⇒ Array of Symbol

Returns a list attributes except “id”, ‘md5_“, and postfixed ”_id“, ”_at“ This is an arbitrary convention, wrap this to further refine.

Parameters:

Returns:

  • (Array of Symbol)

    a list attributes except “id”, ‘md5_“, and postfixed ”_id“, ”_at“ This is an arbitrary convention, wrap this to further refine.



19
20
21
# File 'lib/application_enumeration.rb', line 19

def self.attributes(target)
   target.attributes.select{|k,v| !(k =~ /\Amd5_|_id\z|\Aid\z|_at\z/)}.symbolize_keys.keys.sort
end

.citable_relations(klass, relationship_type = :all) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/application_enumeration.rb', line 145

def self.citable_relations(klass, relationship_type = :all)
  types = relationship_type == :all ?
    [:has_many, :has_one, :belongs_to] : [relationship_type]

  h = {}

  types.each do |t|
    h[t] = klass.reflect_on_all_associations(t).filter_map do |r|
      if r.klass.ancestors.include?(Shared::Citations)
        if t == :has_many
          r.plural_name.to_sym
        else
          r.name.to_sym
        end
      else
        nil
      end
    end
  end

  h
end

.community_data_classesArray

Returns all superclass models that are community/shared.

Returns:

  • (Array)

    all superclass models that are community/shared



61
62
63
# File 'lib/application_enumeration.rb', line 61

def self.community_data_classes
  superclass_models.select{|a| a < Shared::SharedAcrossProjects }
end

.community_modelsArray

Returns all superclass data models.

Returns:

  • (Array)

    all superclass data models



73
74
75
# File 'lib/application_enumeration.rb', line 73

def self.community_models
  superclass_models.select{|a| a < Shared::SharedAcrossProjects}
end

.data_modelsArray

Returns all superclass data models.

Returns:

  • (Array)

    all superclass data models



67
68
69
# File 'lib/application_enumeration.rb', line 67

def self.data_models
  superclass_models.select{|a| a < Shared::IsData}
end

.filter_sti_relations(klass, relation_names) ⇒ Hash

Filter out STI subclass relations that are redundant with their parent class (e.g., ‘protonym` belongs_to duplicates `taxon_name` belongs_to). A subclass relation is only dropped when the parent association has no scope

  • an unscoped parent always covers all records the subclass relation would

return.

!! There’s no general way to compare scoped STI relations, so this method does not filter out STI relations when the parent relation is scoped. !! TODO: maybe we return a separate list of those if any occur.

Parameters:

  • klass (Class)

    an ActiveRecord model class

  • relation_names (Array<Symbol>)

Returns:

  • (Hash)

    { relation_name => reflection }



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/application_enumeration.rb', line 219

def self.filter_sti_relations(klass, relation_names)
  reflections =
    relation_names.map { |name| [name, klass.reflect_on_association(name)] }.to_h

  filtered_names = relation_names.reject do |relation_name|
    reflection = reflections[relation_name]
    next false if reflection.nil? || reflection.options[:polymorphic]

    # Drop this relation if its class is a subclass of an unscoped parent relation,
    # or if it has the same class but is scoped while the other is not.
    reflections.any? do |other_name, other_reflection|
      next false if relation_name == other_name
      next false if other_reflection.nil? || other_reflection.options[:polymorphic]
      next false unless other_reflection.scope.nil?
      reflection.klass < other_reflection.klass ||
        (reflection.klass == other_reflection.klass && reflection.scope.present?)
    end
  end

  reflections.slice(*filtered_names)
end

.klass_reflections(klass, relationship_type = :all) ⇒ Object

Returns Array of AR associations.

Returns:

  • Array of AR associations



103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/application_enumeration.rb', line 103

def self.klass_reflections(klass, relationship_type = :all)
  relationship_types = relationship_type == :all ?
    [:belongs_to, :has_one, :has_many] : [relationship_type]

  a = []
  relationship_types.each do |t|
    a += klass.reflect_on_all_associations(t).sort{ |a, b|
      a.name <=> b.name
    }
  end

  a
end

.model_from_file_name(file_name) ⇒ Class

e.g. given ‘app/models/specimen.rb’ the Specimen class is returned

Parameters:

  • file_name (String)

Returns:

  • (Class)

    represented by a path included filename from /app/models.



90
91
92
# File 'lib/application_enumeration.rb', line 90

def self.model_from_file_name(file_name)
  file_name.split(/app\/models\//).last[0..-4].split(/\\/).collect{|b| b.camelize}.join('::').safe_constantize
end

.nested_subclasses(parent = self) ⇒ Hash

Parameters:

  • parent (Object) (defaults to: self)

Returns:

  • (Hash)


96
97
98
99
100
# File 'lib/application_enumeration.rb', line 96

def self.nested_subclasses(parent = self)
  parent.subclasses.inject({}) { | hsh, subclass |
    hsh.merge!(subclass.name => nested_subclasses(subclass))
  }
end

Returns true if object has no data in any has_many or has_one associations Excludes ‘through` associations and cached/computed relations.

Parameters:

  • object (ApplicationRecord)
  • ignore (Array<Symbol>) (defaults to: [])

    additional relation names to ignore

Returns:

  • (Boolean)

    true if object has no data in any has_many or has_one associations Excludes ‘through` associations and cached/computed relations.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/application_enumeration.rb', line 183

def self.no_related_data?(object, ignore: [])
  excluded = EXCLUDE_RELATIONS_FOR_RELATED_DATA + ignore

  (klass_reflections(object.class, :has_many) + klass_reflections(object.class, :has_one)).each do |relation|
    next if relation.options[:through].present?
    next if excluded.include?(relation.name)
    next if relation.options[:foreign_key]&.match?(/cache/)

    related = object.send(relation.name)

    has_data = if relation.collection?
                 related.any?
               else
                 related.present?
               end

    return false if has_data
  end

  true
end

.non_project_data_classesArray of Classes

Returns data models that do not have a project_id attribute.

Returns:

  • (Array of Classes)

    data models that do not have a project_id attribute



55
56
57
# File 'lib/application_enumeration.rb', line 55

def self.non_project_data_classes
  data_models - project_data_classes
end

.project_data_classesArray of Classes

Returns all models with a project_id attribute.

Returns:

  • (Array of Classes)

    all models with a project_id attribute



49
50
51
# File 'lib/application_enumeration.rb', line 49

def self.project_data_classes
  superclass_models.select{|a| a.column_names.include?('project_id') }
end

.relation_targets_community?(relation) ⇒ Boolean

Returns:

  • (Boolean)


117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/application_enumeration.rb', line 117

def self.relation_targets_community?(relation)
  case relationship_type(relation)
  when :has_many
    relation.class_name.safe_constantize.is_community?
  when :has_one
    raise TaxonWorks::Error, "Has one support not implemented in unify, throw eggs at the devs."
  when :belongs_to
    if k = relation.options[:class_name]
      k.safe_constantize.is_community?
    else
      raise TaxonWorks::Error, "Missing attribute class_name on #{relation.name}."
    end
  end
end

.relationship_type(relation) ⇒ Object

collection?, has_mone? belongs_to?

Raises:

  • (TaxonWorks::Error)


133
134
135
136
137
138
139
140
141
142
143
# File 'lib/application_enumeration.rb', line 133

def self.relationship_type(relation)
  if relation.collection? # class.name.match('HasMany')
    return :has_many
  elsif relation.has_one? # class.name.match('HasOne')
    return :has_one
  elsif relation.belongs_to? # class.name.match('BelongsTo')
    return :belongs_to
  end

  raise TaxonWorks::Error, "Unknown relationship type for #{relation.name}."
end

.superclass_modelsArray

Returns all models that inherit directly from ApplicationRecord.

Returns:

  • (Array)

    all models that inherit directly from ApplicationRecord



43
44
45
# File 'lib/application_enumeration.rb', line 43

def self.superclass_models
  ApplicationRecord.descendants.select{|a| a.superclass == ApplicationRecord }
end