class Flounder::Query::Select
A query obtained by calling any of the chain methods on an entity.
Attributes
All projected fields if no custom projection is made. Fields are encoded so that they can be traced back to the entity that contributed them.
Set to true once project
is used. From that point on, no fields can be added through default projections.
Arel SqlManager that accumulates this query.
Each projection has a unique prefix mapping to the entity that uses this prefix during this query.
maps relations that were included in this select to their entities.
Public Class Methods
Flounder::Query::Base::new
# File lib/flounder/query/select.rb, line 30 def initialize domain, from_entity super domain, Arel::SelectManager, from_entity @has_projection = false @projection_prefixes = Hash.new @default_projection = [] # by default, joins are made to the entity that you start the query with @join_entity = entity @relation_entity_map = {} add_fields_to_default from_entity manager.from from_entity.table end
Public Instance Methods
# File lib/flounder/query/select.rb, line 48 def _join join_node, entity entity = convert_to_entity(entity) @last_join = entity table = entity.table manager.join(table, join_node) add_fields_to_default(entity) self end
# File lib/flounder/query/select.rb, line 60 def add_fields_to_default entity # Don't add fields through the default method if #project was already # used. return if has_projection prefix = entity.name.to_s table = entity.table warn "Table alias #{prefix} already used in select; field aliasing will occur!" \ if projection_prefixes.has_key? prefix projection_prefixes[prefix] = entity entity.column_names.each do |name| default_projection << table[name].as("_#{prefix}_#{name}") end end
# File lib/flounder/query/select.rb, line 94 def anchor @join_entity = last_join self end
Follows relationships on the currently anchored entity. This is like an explicit join, but allows to eliminate repetition.
Example:
users. connect(:posts => {:comments => :author}). where(:author, id: 2) # roughly aequivalent to authors = users.as(:authors, :author) users. join(:posts).on(:id => :user_id).anchor. join(:comments).on(:id => :post_id).anchor. join(authors).on(:author_id => :id). where(authors, id: 2)
# File lib/flounder/query/select.rb, line 185 def connect *connect_spec follow_relation_spec(join_entity, connect_spec) self end
# File lib/flounder/query/select.rb, line 147 def each &block all.each(&block) end
# File lib/flounder/query/select.rb, line 152 def first manager.take(1) all.first end
# File lib/flounder/query/select.rb, line 115 def group_by *field_list manager.group *map_to_arel(field_list) self end
# File lib/flounder/query/select.rb, line 119 def having *conditions parse_conditions(*conditions) { |bit| manager.having(bit) } end
# File lib/flounder/query/select.rb, line 98 def hoist @join_entity = entity self end
# File lib/flounder/query/select.rb, line 78 def inner_join *args _join(Arel::Nodes::InnerJoin, *args) end
# File lib/flounder/query/select.rb, line 136 def limit n manager.take n self end
# File lib/flounder/query/select.rb, line 140 def offset n manager.skip n self end
# File lib/flounder/query/select.rb, line 87 def on join_conditions manager.on( *join_conditions.map { |(k, v)| transform_tuple( join_entity, k, join_field(v)) }) self end
Orders by a list of field references.
Note: Replaces previous order_by.
# File lib/flounder/query/select.rb, line 127 def order_by *field_list manager.orders.clear field_list.each do |field| field = transform field manager.order field end self end
# File lib/flounder/query/select.rb, line 83 def outer_join *args _join(Arel::Nodes::OuterJoin, *args) end
Adds a field to the projection clause of the SQL statement (the part between SELECT and FROM). Projection of ‘*’ is the default, so you can omit this call entirely if you want that.
# File lib/flounder/query/select.rb, line 107 def project *field_list @has_projection = true @default_projection = [] manager.project *map_to_arel(field_list) self end
Executes a ‘select count(*)` query on the database and returns the result. If you want to avoid modifying your query (!) using this, dup it or execute it first and then call size
on the resulting array.
# File lib/flounder/query/select.rb, line 162 def size manager.projections = [] project 'count(*)::int as count' all.first.count end
Private Instance Methods
# File lib/flounder/query/select.rb, line 243 def column_name_to_entity name unless default_projection.empty? extract_source_info_from_name(name) end end
# File lib/flounder/query/select.rb, line 343 def extract_source_info_from_name name md = name.match(@re_field) fail "ASSERTION FAILURE Source info extraction failed." unless md entity = projection_prefixes[md[:prefix]] fail "ASSERTION FAILURE entity cannot be nil" unless entity name = md[:field_name] return entity, name end
Follows a given relationship spec. Does NOT change anchoring.
# File lib/flounder/query/select.rb, line 213 def follow_relation_spec entity, spec old_join_entity = join_entity case spec when Symbol relation = entity.relations[spec] raise "No such relation #{spec.inspect} on entity #{entity}." unless relation relation.apply(self) relation_entity_map[spec] = relation.linked_entity when Hash spec.each do |k, v| follow_relation_spec(join_entity, k) self.anchor follow_relation_spec(join_entity, v) end when Array spec.each do |v| follow_relation_spec(join_entity, v) end else raise ArgumentError, "#{spec.inspect} not allowed in #connect." end @join_entity = old_join_entity end
Checks if a symbol can be turned into an entity. Overwritten from base query because we want to allow entity names that are introduced through connect
as well.
Flounder::Query::Base#has_entity?
# File lib/flounder/query/select.rb, line 196 def has_entity? sym super || relation_entity_map.has_key?(sym) end
Transforms a simple symbol into either a field of the last .join table, or respects field values passed in.
# File lib/flounder/query/select.rb, line 252 def join_field name return @last_join[name] if name.kind_of?(Symbol) name end
Maps a field reference (see fields) to an Arel::Field.
# File lib/flounder/query/select.rb, line 290 def map_to_arel field_list map_to_fields(field_list).map(&:to_arel_field) end
# File lib/flounder/query/select.rb, line 272 def map_to_field field_ref case field_ref when Symbol entity[field_ref] when String Flounder::Immediate.new(field_ref) when Flounder::Field field_ref when Flounder::Expression::Expr field_ref.to_immediate else fail Flounder::InvalidFieldReference, "Cannot resolve #{field_ref.inspect} to a field." end end
Maps an array of field references to Flounder::Field
objects. A field reference can be:
* a symbol, interpreted as a field name of the main enity of the operation * a string, interpreted as something to be passed into the SQL statement as is. Caution: Don't expose this to unsecure channels! * a Flounder::Field, left alone (obtained through calling #[] on any entity)
# File lib/flounder/query/select.rb, line 267 def map_to_fields field_list field_list.map { |x| map_to_field(x) } end
Prepares a kick (aka transformation into sql/result). This should include all actions that need to be performed to validate the query.
# File lib/flounder/query/select.rb, line 297 def prepare_kick unless @has_projection @has_projection = true # Prepare the regular expression that we'll use to extract entities # from column names. @re_field = %r( ^_ # indicates one of our own (?<prefix>#{projection_prefixes.keys.join('|')}) _ (?<field_name>.*) $ )x manager.project *default_projection end end
Resolves an entity through the domain. Overwritten from base query because we want to allow entity names that are introduced through connect
as well.
# File lib/flounder/query/select.rb, line 204 def resolve_entity sym return domain[sym] if domain.has_entity?(sym) return relation_entity_map[sym] if relation_entity_map.has_key?(sym) raise Flounder::NoSuchEntity, "No such entity #{sym.inspect} either in this select statement or in the domain." end
Called on a value for a
* order by
clause, this returns a field that can be passed to Arel
* #where * #on
# File lib/flounder/query/select.rb, line 321 def transform field if defined?(DataMapper) && defined?(DataMapper::Query::Operator) && field.kind_of?(DataMapper::Query::Operator) # interop: datamapper hijacks some of our operators, let's reverse this field = Flounder::SymbolExtensions::Modifier.new( field.target, field.operator) end case field when String Flounder::Immediate.new(field).to_arel_field when Flounder::Field field.fully_qualified_name when Flounder::SymbolExtensions::Modifier field.to_arel_field(entity).send field.kind else entity[field].arel_field end end