class Praxis::Mapper::SelectorGeneratorNode
Attributes
fields_node[R]
prepend SelectorGeneratorNodeDebugger
# Uncomment this to see the traces of how methods are called
model[R]
prepend SelectorGeneratorNodeDebugger
# Uncomment this to see the traces of how methods are called
resource[R]
prepend SelectorGeneratorNodeDebugger
# Uncomment this to see the traces of how methods are called
select[R]
prepend SelectorGeneratorNodeDebugger
# Uncomment this to see the traces of how methods are called
tracks[R]
prepend SelectorGeneratorNodeDebugger
# Uncomment this to see the traces of how methods are called
Public Class Methods
new(resource)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 76 def initialize(resource) @resource = resource @select = Set.new @select_star = false @fields_node = FieldDependenciesNode.new(name: '/', selector_node: self) @tracks = {} end
Public Instance Methods
add(fields)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 88 def add(fields) fields.each do |name, field| fields_node.start_field(name) map_property(name, field) fields_node.end_field end end
add_association(name, fields)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 146 def add_association(name, fields) # fields_node.retrieve_last_of_chain = true association = resource.model._praxis_associations.fetch(name) do raise "missing association for #{resource} with name #{name}" end associated_resource = resource.model_map[association[:model]] raise "Whoops! could not find a resource associated with model #{association[:model]} (root resource #{resource})" unless associated_resource # Add the required columns in this model to make sure the association can be loaded association[:local_key_columns].each { |col| add_select(col, add_field: false) } node = SelectorGeneratorNode.new(associated_resource) unless association[:remote_key_columns].empty? # Make sure we add the required columns for this association to the remote model query fields = {} if fields == true new_fields_as_hash = association[:remote_key_columns].each_with_object({}) do |key, hash| hash[key] = true end fields = fields.merge(new_fields_as_hash) end node.add(fields) unless fields == true merge_track(name, node) node end
add_fwding_property(name, fields)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 182 def add_fwding_property(name, fields) aliased_as = resource.properties[name][:as] if aliased_as == :self # Special keyword to add itself as the association, but still continue procesing the fields # This is useful when we expose resource fields tucked inside another sub-struct, this way # we can make sure that if the fields necessary to compute things inside the struct, they are preloaded add(fields) unless fields == true else # Assumes (as: option of the property DSL should check check) that all forwarded properties need to be pure associations # We know we've now added the chain of association dependencies under our node...so we'll start getting the 'first' of them # and recurse down the node until the leaf. # Then, we need to apply the incoming fields to that. leaf_node = add_string_association(*aliased_as.to_s.split('.').map(&:to_sym)) leaf_node.add(fields) unless fields == true # If true, no fields to apply leaf_node end end
add_property(name, fields)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 200 def add_property(name, fields) dependencies = resource.properties[name][:dependencies] # Always add the underlying association if we're overriding the name... if (praxis_compat_model = resource.model&.respond_to?(:_praxis_associations)) aliased_as = resource.properties[name][:as] if aliased_as if aliased_as == :self # Special keyword to add itself as the association, but still continue procesing the fields # This is useful when we expose resource fields tucked inside another sub-struct, this way # we can make sure that if the fields necessary to compute things inside the struct, they are preloaded add(fields) else first, *rest = aliased_as.to_s.split('.').map(&:to_sym) extended_fields = \ if rest.empty? fields else rest.reverse.inject(fields) do |accum, prop| { prop => accum } end end add_association(first, extended_fields) if resource.model._praxis_associations[first] end elsif resource.model._praxis_associations[name] # Not aliased ... but if there is an existing association for the propety name, we add it (and ignore any deps in place) add_association(name, fields) end end # If we have a property group, and the subfields want to selectively restrict what to depend on if fields != true && resource.property_groups[name] # Prepend the group name to fields if it's an inner hash prefixed_fields = fields == true ? {} : fields.keys.each_with_object({}) { |k, h| h["#{name}_#{k}".to_sym] = k } # Try to match all inner fields prefixed_fields.each do |prefixedname, origfieldname| next unless dependencies.include?(prefixedname) fields_node.start_field(origfieldname) # Mark it as orig name apply_dependency(prefixedname, fields[origfieldname]) fields_node.end_field end else dependencies&.each do |dependency| # To detect recursion, let's allow mapping depending fields to the same name of the property # but properly detecting if it's a real association...in which case we've already added it above if dependency == name add_select(name) unless praxis_compat_model && resource.model._praxis_associations.key?(name) else apply_dependency(dependency) end end end end
add_select(name, add_field: true)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 173 def add_select(name, add_field: true) return @select_star = true if name == :* return if @select_star # Do not add a field dependency, if we know we're just adding a Local/FK constraint @fields_node.add_local_dep(name) if add_field @select.add name end
add_string_association(first, *rest)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 120 def add_string_association(first, *rest) association = resource.model._praxis_associations.fetch(first) do raise "missing association for #{resource} with name #{first}" end associated_resource = resource.model_map[association[:model]] raise "Whoops! could not find a resource associated with model #{association[:model]} (root resource #{resource})" unless associated_resource # Add the required columns in this model to make sure the association can be loaded association[:local_key_columns].each { |col| add_select(col, add_field: false) } node = SelectorGeneratorNode.new(associated_resource) unless association[:remote_key_columns].empty? # Make sure we add the required columns for this association to the remote model query fields = {} new_fields_as_hash = association[:remote_key_columns].each_with_object({}) do |key, hash| hash[key] = true end fields = fields.merge(new_fields_as_hash) end node.add(fields) unless fields == true leaf_node = rest.empty? ? nil : node.add_string_association(*rest) merge_track(first, node) leaf_node || node # Return the leaf (i.e., us, if we're the last component or the result of the string_association if there was one) end
apply_dependency(dependency, fields = true)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 255 def apply_dependency(dependency, fields = true) case dependency when Symbol map_property(dependency, fields, as_dependency: true) when String head, *tail = dependency.split('.').collect(&:to_sym) raise 'String dependencies can not be singular' if tail.nil? add_association(head, tail.reverse.inject(true) { |hash, dep| { dep => hash } }) end end
dump(mode: :columns_and_tracks)
click to toggle source
Debugging method for rspec, to easily match the desired output By default it only outputs the info related to computing columns and track dependencies. Overriding the mode will allow to dump the model and only the field dependencies
# File lib/praxis/mapper/selector_generator.rb, line 286 def dump(mode: :columns_and_tracks) hash = {} hash[:model] = resource.model case mode when :columns_and_tracks if !@select.empty? || @select_star hash[:columns] = @select_star ? [:*] : @select.to_a end when :fields dumped_fields_node = @fields_node.dump raise "Fields node has more keys than fields!! #{dumped_fields_node}" if dumped_fields_node.keys.size > 1 hash[:fields] = dumped_fields_node[:fields] if dumped_fields_node[:fields] else raise "Unknown mode #{mode} for dumping SelectorGenerator" end hash[:tracks] = @tracks.transform_values { |v| v.dump(mode: mode) } unless @tracks.empty? hash end
inspect()
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 84 def inspect "<#{self.class}# @resource=#{@resource.name} @select=#{@select} @select_star=#{@select_star} tracking: #{@tracks.keys} (recursion omited)>" end
map_property(name, fields, as_dependency: false)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 96 def map_property(name, fields, as_dependency: false) praxis_compat_model = resource.model&.respond_to?(:_praxis_associations) if resource.properties.key?(name) if (target = resource.properties[name][:as]) leaf_node = add_fwding_property(name, fields) fields_node.save_reference(leaf_node) unless target == :self else add_property(name, fields) end fields_node.add_local_dep(name) elsif praxis_compat_model && resource.model._praxis_associations.key?(name) add_association(name, fields) # Single association properties are also pointing to the corresponding tracked SelectorGeneratorNode # but only if they are implicit properties, without dependencies if as_dependency fields_node.add_local_dep(name) else fields_node.save_reference(tracks[name]) end else add_select(name) end end
merge_track(track_name, node)
click to toggle source
# File lib/praxis/mapper/selector_generator.rb, line 267 def merge_track(track_name, node) raise "Cannot merge another node for association #{track_name}: incompatible model" unless node.model == model existing = tracks[track_name] if existing node.select.each do |col_name| existing.add_select(col_name) end node.tracks.each do |name, n| existing.merge_track(name, n) end else tracks[track_name] = node end end