lib/mongoid/history/options.rb
module Mongoid
module History
class Options
attr_reader :trackable, :options
def initialize(trackable, opts = {})
@trackable = trackable
@options = default_options.merge(opts)
end
def scope
trackable.collection_name.to_s.singularize.to_sym
end
def prepared
return @prepared if @prepared
@prepared = options.dup
prepare_skipped_fields
prepare_formatted_fields
parse_tracked_fields_and_relations
@prepared
end
private
def default_options
{ on: :all,
except: %i[created_at updated_at],
tracker_class_name: nil,
modifier_field: :modifier,
version_field: :version,
changes_method: :changes,
scope: scope,
track_create: true,
track_update: true,
track_destroy: true,
format: nil }
end
# Sets the :except attributes and relations in `options` to be an [ Array <String> ]
# The attribute names and relations are stored by their `database_field_name`s
# Removes the `nil` and duplicate entries from skipped attributes/relations list
def prepare_skipped_fields
# normalize :except fields to an array of database field strings
@prepared[:except] = Array(@prepared[:except])
@prepared[:except] = @prepared[:except].map { |field| trackable.database_field_name(field) }.compact.uniq
end
def prepare_formatted_fields
formats = {}
if @prepared[:format].class == Hash
@prepared[:format].each do |field, format|
next if field.nil?
field = trackable.database_field_name(field)
if format.class == Hash && trackable.embeds_many?(field)
relation_class = trackable.relation_class_of(field)
formats[field] = format.inject({}) { |a, e| a.merge(relation_class.database_field_name(e.first) => e.last) }
elsif format.class == Hash && trackable.embeds_one?(field)
relation_class = trackable.relation_class_of(field)
formats[field] = format.inject({}) { |a, e| a.merge(relation_class.database_field_name(e.first) => e.last) }
else
formats[field] = format
end
end
end
@prepared[:format] = formats
end
def parse_tracked_fields_and_relations
# case `options[:on]`
# when `posts: [:id, :title]`, then it will convert it to `[[:posts, [:id, :title]]]`
# when `:foo`, then `[:foo]`
# when `[:foo, { posts: [:id, :title] }]`, then return as is
@prepared[:on] = Array(@prepared[:on])
@prepared[:on] = @prepared[:on].map { |opt| opt == :all ? :fields : opt }
if @prepared[:on].include?(:fields)
@prepared[:on] = @prepared[:on].reject { |opt| opt == :fields }
@prepared[:on] = @prepared[:on] | trackable.fields.keys.map(&:to_sym) - reserved_fields.map(&:to_sym)
end
if @prepared[:on].include?(:embedded_relations)
@prepared[:on] = @prepared[:on].reject { |opt| opt == :embedded_relations }
@prepared[:on] = @prepared[:on] | trackable.embedded_relations.keys
end
@prepared[:fields] = []
@prepared[:dynamic] = []
@prepared[:relations] = { embeds_one: {}, embeds_many: {} }
@prepared[:on].each do |option|
if option.is_a?(Hash)
option.each { |k, v| split_and_categorize(k => v) }
else
split_and_categorize(option)
end
end
end
def split_and_categorize(field_and_options)
field = get_database_field_name(field_and_options)
field_options = get_field_options(field_and_options)
categorize_tracked_option(field, field_options)
end
# Returns the database_field_name key for tracked option
#
# @param [ String | Symbol | Array | Hash ] option The field or relation name to track
#
# @return [ String ] the database field name for tracked option
def get_database_field_name(option)
key = if option.is_a?(Hash)
option.keys.first
elsif option.is_a?(Array)
option.first
end
trackable.database_field_name(key || option)
end
# Returns the tracked attributes for embedded relations, otherwise `nil`
#
# @param [ String | Symbol | Array | Hash ] option The field or relation name to track
#
# @return [ nil | Array <String | Symbol> ] the list of tracked fields for embedded relation
def get_field_options(option)
if option.is_a?(Hash)
option.values.first
elsif option.is_a?(Array)
option.last
end
end
# Tracks the passed option under:
# `fields`
# `dynamic`
# `relations -> embeds_one` or
# `relations -> embeds_many`
#
# @param [ String ] field The database field name of field or relation to track
# @param [ nil | Array <String | Symbol> ] field_options The tracked fields for embedded relations
def categorize_tracked_option(field, field_options = nil)
return if @prepared[:except].include?(field)
return if reserved_fields.include?(field)
field_options = Array(field_options)
if trackable.embeds_one?(field)
track_relation(field, :embeds_one, field_options)
elsif trackable.embeds_many?(field)
track_relation(field, :embeds_many, field_options)
elsif trackable.fields.keys.include?(field)
@prepared[:fields] << field
else
@prepared[:dynamic] << field
end
end
def track_relation(field, kind, field_options)
relation_class = trackable.relation_class_of(field)
@prepared[:relations][kind][field] = if field_options.blank?
relation_class.fields.keys
else
%w[_id] | field_options.map { |opt| relation_class.database_field_name(opt) }
end
end
def reserved_fields
@reserved_fields ||= ['_id', '_type', @prepared[:version_field].to_s, "#{@prepared[:modifier_field]}_id"]
end
end
end
end