class Penman::RecordTag
Public Class Methods
create_custom(attributes = {})
click to toggle source
# File lib/penman/record_tag.rb, line 123 def create_custom(attributes = {}) attributes = { record_type: 'custom_tag', tag: 'touched', candidate_key: 'n/a' }.merge attributes record_tag = RecordTag.find_or_create_by(attributes) record_tag.update(record_id: record_tag.id) if record_tag.record_id == 0 # notice validation above, this just ensures that we don't violate the table constraint. end
disable()
click to toggle source
# File lib/penman/record_tag.rb, line 38 def disable @@enabled = false end
enable()
click to toggle source
# File lib/penman/record_tag.rb, line 42 def enable @@enabled = true end
enabled?()
click to toggle source
# File lib/penman/record_tag.rb, line 46 def enabled? @@enabled end
generate_seed_for_model(model)
click to toggle source
# File lib/penman/record_tag.rb, line 154 def generate_seed_for_model(model) time = Time.now seed_files = [] seed_files << generate_update_seed(model, time.strftime('%Y%m%d%H%M%S')) seed_files << generate_destroy_seed(model, (time + 1.second).strftime('%Y%m%d%H%M%S')) RecordTag.where(record_type: model.name).delete_all seed_files.compact end
generate_seeds()
click to toggle source
# File lib/penman/record_tag.rb, line 129 def generate_seeds generate_seeds_for_models(seed_order) end
generate_seeds_for_models(models)
click to toggle source
# File lib/penman/record_tag.rb, line 133 def generate_seeds_for_models(models) models = valid_ordered_models_for_seeds(models) return [] if models.empty? time = Time.now seed_files = [] models.each do |model| seed_files << generate_update_seed(model, time.strftime('%Y%m%d%H%M%S')) time += 1.second end models.reverse.each do |model| seed_files << generate_destroy_seed(model, time.strftime('%Y%m%d%H%M%S')) time += 1.second end RecordTag.where(record_type: models.map(&:name)).delete_all seed_files.compact end
register(model)
click to toggle source
# File lib/penman/record_tag.rb, line 50 def register(model) @@taggable_models |= [model] end
tag(record, tag)
click to toggle source
# File lib/penman/record_tag.rb, line 54 def tag(record, tag) return unless @@enabled candidate_key = record.class.try(:candidate_key) || Penman.config.default_candidate_key candidate_key = [candidate_key] unless candidate_key.is_a? Array raise RecordTagExceptions::InvalidCandidateKeyForRecord unless record_has_attributes?(record, candidate_key) candidate_key_to_store = if ['created', 'destroyed'].include? tag Hash[candidate_key.map { |k| [k, record.send(k)] }].to_json else # updated Hash[candidate_key.map { |k| [k, record.send("#{k}_was")] }].to_json end created_tag = RecordTag.find_by(record: record, tag: 'created') updated_tag = RecordTag.find_by(record: record, tag: 'updated') destroyed_tag = RecordTag.find_by(record_type: record.class.name, candidate_key: candidate_key_to_store, tag: 'destroyed') raise RecordTagExceptions::TooManyTagsForRecord if [created_tag, updated_tag, destroyed_tag].count { |t| t.present? } > 1 if created_tag.present? case tag when 'created' raise RecordTagExceptions::BadTracking, format_error_message('created', 'created', candidate_key_to_store) when 'updated' created_tag.update!(tag: tag) when 'destroyed' created_tag.destroy! end elsif updated_tag.present? case tag when 'created' raise RecordTagExceptions::BadTracking, format_error_message('updated', 'created', candidate_key_to_store) when 'updated' updated_tag.update!(tag: tag) when 'destroyed' if updated_tag.created_this_session updated_tag.destroy! else updated_tag.update!(tag: tag) end end elsif destroyed_tag.present? case tag when 'created' # We make an updated tag in case non-candidate key attributes have changed, since we don't tack those. destroyed_tag.update!(tag: 'updated', record_id: record.id) when 'updated' raise RecordTagExceptions::BadTracking, format_error_message('destroyed', 'updated', candidate_key_to_store) when 'destroyed' raise RecordTagExceptions::BadTracking, format_error_message('destroyed', 'destroyed', candidate_key_to_store) end else # new tag RecordTag.create!(record: record, tag: tag, candidate_key: candidate_key_to_store, created_this_session: tag == 'created') end end
valid_ordered_models_for_seeds(models)
click to toggle source
# File lib/penman/record_tag.rb, line 118 def valid_ordered_models_for_seeds(models) valid_models = @@taggable_models & models seed_order(valid_models) end
Private Class Methods
add_model_to_tree(model)
click to toggle source
# File lib/penman/record_tag.rb, line 170 def add_model_to_tree(model) reflections = model.reflect_on_all_associations(:belongs_to) if reflections.find { |r| r.options[:polymorphic] }.present? @@polymorphic << model else @@roots.push(model) unless @@tree.key?(model) end @@tree[model] = reflections.reject { |r| r.options[:polymorphic] || r.klass == model }.map(&:klass) @@tree[model].each { |ch| @@tree[ch] ||= [] } @@roots -= @@tree[model] end
attribute_string_from_hash(model, column_hash)
click to toggle source
# File lib/penman/record_tag.rb, line 266 def attribute_string_from_hash(model, column_hash) column_hash.symbolize_keys! formatted_candidate_key = [] column_hash.each do |k, v| reflection = find_foreign_key_relation(model, k) if reflection && v.present? associated_model = if reflection.polymorphic? column_hash[reflection.foreign_type.to_sym].constantize else reflection.klass end if associated_model.ancestors.include?(Taggable) || associated_model.respond_to?(:candidate_key) primary_key = reflection.options[:primary_key] || associated_model.primary_key associated_record = associated_model.find_by(primary_key => v) to_add = "#{reflection.name}: #{associated_model.name}.find_by(" if associated_record.present? to_add += "#{print_candidate_key(associated_record)})" else # likely this record was destroyed, so we should have a tag for it tag = RecordTag.find_by(record_type: associated_model.name, record_id: v) raise RecordTagExceptions::RecordNotFound, "while processing #{column_hash}" if tag.nil? to_add += "#{attribute_string_from_hash(associated_model, tag.candidate_key)})" end formatted_candidate_key << to_add next end end formatted_candidate_key << "#{k}: #{primitive_string(v)}" end formatted_candidate_key.join(', ') end
find_foreign_key_relation(model, accessor)
click to toggle source
# File lib/penman/record_tag.rb, line 304 def find_foreign_key_relation(model, accessor) model.reflect_on_all_associations.find do |r| begin r.foreign_key.to_sym == accessor.to_sym rescue NameError false end end end
format_error_message(existing_tag, new_tag, record_to_store)
click to toggle source
# File lib/penman/record_tag.rb, line 334 def format_error_message(existing_tag, new_tag, record_to_store) "found an existing '#{existing_tag}' tag for record while tagging, '#{new_tag}' - #{record_to_store}" end
generate_destroy_seed(model, timestamp)
click to toggle source
# File lib/penman/record_tag.rb, line 232 def generate_destroy_seed(model, timestamp) destroyed_tags = RecordTag.where(record_type: model.name, tag: 'destroyed') return nil if destroyed_tags.empty? seed_code = SeedCode.new seed_code << 'penman_initially_enabled = Penman.enabled?' seed_code << 'Penman.disable' destroyed_tags.map(&:candidate_key).each do |record_candidate_key| seed_code << "record = #{model.name}.find_by(#{attribute_string_from_hash(model, record_candidate_key)})" seed_code << "record.try(:destroy)" end seed_code << 'Penman.enable if penman_initially_enabled' seed_file_name = Penman.config.file_name_formatter.call(model.name, 'destroys') sfg = SeedFileGenerator.new(seed_file_name, timestamp, seed_code) sfg.write_seed end
generate_update_seed(model, timestamp)
click to toggle source
# File lib/penman/record_tag.rb, line 203 def generate_update_seed(model, timestamp) validate_records_for_model(model) if Penman.config.validate_records_before_seed_generation touched_tags = RecordTag.where(record_type: model.name, tag: ['created', 'updated']).includes(:record) return nil if touched_tags.empty? seed_code = SeedCode.new seed_code << 'penman_initially_enabled = Penman.enabled?' seed_code << 'Penman.disable' touched_tags.each do |tag| seed_code << "# Generating seed for #{tag.tag.upcase} tag." seed_code << "record = #{model.name}.find_by(#{print_candidate_key(tag.record)})" seed_code << "record = #{model.name}.find_or_initialize_by(#{attribute_string_from_hash(model, tag.candidate_key)}) if record.nil?" column_hash = Hash[ model.attribute_names .reject { |col| col == model.primary_key } .map { |col| [col, tag.record.send(col)] } ] seed_code << "record.update!(#{attribute_string_from_hash(model, column_hash)})" end seed_code << 'Penman.enable if penman_initially_enabled' model_name = model.name.tr('/', '_') seed_file_name = Penman.config.file_name_formatter.call(model_name, 'updates') sfg = SeedFileGenerator.new(seed_file_name, timestamp, seed_code) sfg.write_seed end
primitive_string(p)
click to toggle source
# File lib/penman/record_tag.rb, line 322 def primitive_string(p) if p.nil? 'nil' elsif p.is_a? String p.inspect elsif p.is_a? Time "Time.parse('#{p}')" else "#{p}" end end
print_candidate_key(record)
click to toggle source
# File lib/penman/record_tag.rb, line 256 def print_candidate_key(record) candidate_key = record.class.try(:candidate_key) || Penman.config.default_candidate_key candidate_key = [candidate_key] unless candidate_key.is_a? Array raise RecordTagExceptions::InvalidCandidateKeyForRecord unless record_has_attributes?(record, candidate_key) candidate_key_hash = {} candidate_key.each { |key| candidate_key_hash[key] = record.send(key) } attribute_string_from_hash(record.class, candidate_key_hash) end
record_has_attributes?(record, attributes)
click to toggle source
# File lib/penman/record_tag.rb, line 314 def record_has_attributes?(record, attributes) attributes.each do |attribute| return false unless record.has_attribute?(attribute) end true end
reset_tree()
click to toggle source
# File lib/penman/record_tag.rb, line 164 def reset_tree @@roots = [] @@tree = {} @@polymorphic = [] end
seed_order(models = @@taggable_models)
click to toggle source
# File lib/penman/record_tag.rb, line 185 def seed_order(models = @@taggable_models) reset_tree models.each { |m| add_model_to_tree(m) } seed_order = [] recurse_on = -> (node) do return unless node.ancestors.include?(Taggable) @@tree[node].each { |n| recurse_on.call(n) } seed_order |= [node] end @@roots.each { |node| recurse_on.call(node) } @@polymorphic.each { |node| recurse_on.call(node) } seed_order | @@polymorphic end
validate_records_for_model(model)
click to toggle source
# File lib/penman/record_tag.rb, line 250 def validate_records_for_model(model) RecordTag.where(record_type: model.name, tag: ['updated', 'created']) .includes(:record) .each { |r| r.record.validate! } end
Public Instance Methods
candidate_key()
click to toggle source
Calls superclass method
# File lib/penman/record_tag.rb, line 33 def candidate_key decode_candidate_key(super) end
decode_candidate_key(key)
click to toggle source
# File lib/penman/record_tag.rb, line 19 def decode_candidate_key(key) begin ActiveSupport::JSON.decode(key).symbolize_keys rescue JSON::ParserError # This will occur if the candidate key isn't encoded as json. # An example of this is when we tag yaml files as touched when updating lang. # In that case we store the file path in the candidate key column as a regular string. key end end
encode_candidate_key()
click to toggle source
# File lib/penman/record_tag.rb, line 13 def encode_candidate_key if self.candidate_key.is_a? Hash self.candidate_key = self.candidate_key.to_json end end