# encoding: utf-8
#
# = Query Model
#
################################################################################
class Query < AbstractQuery
belongs_to :user
# Parameters allowed in every query.
self.global_params = {
# Allow every query to customize its title.
:title? => [:string],
}
# Parameters allowed in every query for a given model.
self.model_params = {
:Comment => {
:created? => [:time],
:modified? => [:time],
:users? => [User],
:types? => :string,
:summary_has? => :string,
:content_has? => :string,
},
:Image => {
:created? => [:time],
:modified? => [:time],
:date? => [:date],
:users? => [User],
:names? => [:string],
:synonym_names? => [:string],
:children_names? => [:string],
:locations? => [:string],
:species_lists? => [:string],
:has_observation? => {:string => [:yes]},
:size? => [{:string => Image.all_sizes - [:full_size]}],
:content_types? => :string,
:has_notes? => :boolean,
:notes_has? => :string,
:copyright_holder_has? => :string,
:license? => License,
:has_votes? => :boolean,
:quality? => [:integer],
:confidence? => [:integer],
:ok_for_export? => :boolean,
},
:Location => {
:created? => [:time],
:modified? => [:time],
:users? => [User],
},
:LocationDescription => {
:created? => [:time],
:modified? => [:time],
:users? => [User],
},
:Name => {
:created? => [:time],
:modified? => [:time],
:users? => [User],
:synonym_names? => [:string],
:children_names? => [:string],
:misspellings? => {:string => [:no, :either, :only]},
:deprecated? => {:string => [:either, :no, :only]},
:has_synonyms? => :boolean,
:locations? => [:string],
:species_lists? => [:string],
:rank? => [{:string => Name.all_ranks}],
:is_deprecated? => :boolean,
:text_name_has? => :string,
:has_author? => :boolean,
:author_has? => :string,
:has_citation? => :boolean,
:citation_has? => :string,
:has_classification? => :boolean,
:classification_has? => :string,
:has_notes? => :boolean,
:notes_has? => :string,
:has_comments? => {:string => [:yes]},
:comments_has? => :string,
:has_default_desc? => :boolean,
:join_desc? => {:string => [:default,:any]},
:desc_type? => :string,
:desc_project? => [:string],
:desc_creator? => [User],
:desc_content? => :string,
:ok_for_export? => :boolean,
},
:NameDescription => {
:created? => [:time],
:modified? => [:time],
:users? => [User],
},
:Observation => {
:created? => [:time],
:modified? => [:time],
:date? => [:date],
:users? => [User],
:names? => [:string],
:synonym_names? => [:string],
:children_names? => [:string],
:locations? => [:string],
:species_lists? => [:string],
:confidence? => [:integer],
:include_admin? => :boolean,
:is_col_loc? => :boolean,
:has_specimen? => :boolean,
:has_location? => :boolean,
:has_notes? => :boolean,
:has_name? => :boolean,
:has_images? => :boolean,
:has_votes? => :boolean,
:has_comments? => {:string => [:yes]},
:notes_has? => :string,
:comments_has? => :string,
},
:Project => {
:created? => [:time],
:modified? => [:time],
:users? => [User],
},
:RssLog => {
:modified? => [:time],
:type? => :string,
},
:SpeciesList => {
:created? => [:time],
:modified? => [:time],
:date? => [:date],
:users? => [User],
:names? => [:string],
:synonym_names? => [:string],
:children_names? => [:string],
:locations? => [:string],
:title_has? => :string,
:has_notes? => :boolean,
:notes_has? => :string,
:has_comments? => {:string => [:yes]},
:comments_has? => :string,
},
:User => {
:created? => [:time],
:modified? => [:time],
},
}
# Parameters required for each flavor.
self.flavor_params = {
:advanced_search => {
:name? => :string,
:location? => :string,
:user? => :string,
:content? => :string,
},
:all => {
},
:at_location => {
:location => Location,
},
:at_where => {
:location => :string,
:user_where => :string,
},
:by_author => {
:user => User,
},
:by_editor => {
:user => User,
},
:by_user => {
:user => User,
},
:for_target => {
:target => AbstractModel,
:type => :string,
},
:for_user => {
:user => User,
},
:in_set => {
:ids => [AbstractModel],
},
:in_species_list => {
:species_list => SpeciesList,
},
:inside_observation => {
:observation => Observation,
:outer => Query,
},
:of_children => {
:name => Name,
:all? => :boolean,
},
:of_name => {
:name => Name,
:synonyms? => {:string => [:no, :all, :exclusive]},
:nonconsensus? => {:string => [:no, :all, :exclusive]},
},
:of_parents => {
:name => Name,
},
:pattern_search => {
:pattern => :string,
},
:with_descriptions_by_author => {
:user => User,
},
:with_descriptions_by_editor => {
:user => User,
},
:with_descriptions_by_user => {
:user => User,
},
:with_descriptions_in_set => {
:ids => [AbstractModel],
:old_title? => :string,
:old_by? => :string,
},
:with_observations_at_location => {
:location => Location,
},
:with_observations_at_where => {
:location => :string,
:user_where => :string,
},
:with_observations_by_user => {
:user => User,
},
:with_observations_in_set => {
:ids => [Observation],
:old_title? => :string,
:old_by? => :string,
},
:with_observations_in_species_list => {
:species_list => SpeciesList,
},
:with_observations_of_children => {
:name => Name,
:all? => :boolean,
},
:with_observations_of_name => {
:name => Name,
:synonyms? => {:string => [:no, :all, :exclusive]},
:nonconsensus? => {:string => [:no, :all, :exclusive]},
},
}
# Allowed flavors for each model.
self.allowed_model_flavors = {
:Comment => [
:all, # All comments, by created.
:by_user, # Comments created by user, by created.
:in_set, # Comments in a given set.
:for_target, # Comments on a given object, by created.
:for_user, # Comments sent to user, by created.
:pattern_search, # Comments matching a pattern, by created.
],
:Image => [
:advanced_search, # Advanced search results.
:all, # All images, by created.
:by_user, # Images created by user, by modified.
:in_set, # Images in a given set.
:inside_observation, # Images belonging to outer observation query.
:pattern_search, # Images matching a pattern, by ???.
:with_observations, # Images with observations, alphabetically.
:with_observations_at_location, # Images with observations at a defined location.
:with_observations_at_where, # Images with observations at an undefined 'where'.
:with_observations_by_user, # Images with observations by user.
:with_observations_in_set, # Images with observations in a given set.
:with_observations_in_species_list, # Images with observations in a given species list.
:with_observations_of_children, # Images with observations of children a given name.
:with_observations_of_name, # Images with observations of a given name.
],
:Location => [
:advanced_search, # Advanced search results.
:all, # All locations, alphabetically.
:by_user, # Locations created by a given user, alphabetically.
:by_editor, # Locations modified by a given user, alphabetically.
:by_rss_log, # Locations with RSS logs, in RSS order.
:in_set, # Locations in a given set.
:pattern_search, # Locations matching a pattern, alphabetically.
:with_descriptions, # Locations with descriptions, alphabetically.
:with_descriptions_by_author, # Locations with descriptions authored by a given user, alphabetically.
:with_descriptions_by_editor, # Locations with descriptions edited by a given user, alphabetically.
:with_descriptions_by_user, # Locations with descriptions created by a given user, alphabetically.
:with_descriptions_in_set, # Locations with descriptions in a given set, alphabetically.
:with_observations, # Locations with observations, alphabetically.
:with_observations_by_user, # Locations with observations by user.
:with_observations_in_set, # Locations with observations in a given set.
:with_observations_in_species_list, # Locations with observations in a given species list.
:with_observations_of_children, # Locations with observations of children of a given name.
:with_observations_of_name, # Locations with observations of a given name.
],
:LocationDescription => [
:all, # All location descriptions, alphabetically.
:by_author, # Location descriptions that list given user as an author, alphabetically.
:by_editor, # Location descriptions that list given user as an editor, alphabetically.
:by_user, # Location descriptions created by a given user, alphabetically.
:in_set, # Location descriptions in a given set.
],
:Name => [
:advanced_search, # Advanced search results.
:all, # All names, alphabetically.
:by_user, # Names created by a given user, alphabetically.
:by_editor, # Names modified by a given user, alphabetically.
:by_rss_log, # Names with RSS logs, in RSS order.
:in_set, # Names in a given set.
:of_children, # Names of children of a name.
:of_parents, # Names of parents of a name.
:pattern_search, # Names matching a pattern, alphabetically.
:with_descriptions, # Names with descriptions, alphabetically.
:with_descriptions_by_author, # Names with descriptions authored by a given user, alphabetically.
:with_descriptions_by_editor, # Names with descriptions edited by a given user, alphabetically.
:with_descriptions_by_user, # Names with descriptions created by a given user, alphabetically.
:with_descriptions_in_set, # Names with descriptions in a given set, alphabetically.
:with_observations, # Names with observations, alphabetically.
:with_observations_at_location, # Names with observations at a defined location.
:with_observations_at_where, # Names with observations at an undefined 'where'.
:with_observations_by_user, # Names with observations by user.
:with_observations_in_set, # Names with observations in a given set.
:with_observations_in_species_list, # Names with observations in a given species list.
],
:NameDescription => [
:all, # All name descriptions, alphabetically.
:by_author, # Name descriptions that list given user as an author, alphabetically.
:by_editor, # Name descriptions that list given user as an editor, alphabetically.
:by_user, # Name descriptions created by a given user, alphabetically.
:in_set, # Name descriptions in a given set.
],
:Observation => [
:advanced_search, # Advanced search results.
:all, # All observations, by date.
:at_location, # Observations at a location, by modified.
:at_where, # Observations at an undefined location, by modified.
:by_rss_log, # Observations with RSS log, in RSS order.
:by_user, # Observations created by user, by modified.
:in_set, # Observations in a given set.
:in_species_list, # Observations in a given species list, by modified.
:of_children, # Observations of children of a given name.
:of_name, # Observations with a given name.
:pattern_search, # Observations matching a pattern, by name.
],
:Project => [
:all, # All projects, by title.
:by_rss_log, # Projects with RSS logs, in RSS order.
:in_set, # Projects in a given set.
:pattern_search, # Projects matching a pattern, by title.
],
:RssLog => [
:all, # All RSS logs, most recent activity first.
:in_set, # RSS logs in a given set.
],
:SpeciesList => [
:all, # All species lists, alphabetically.
:at_location, # Species lists at a location, by modified.
:at_where, # Species lists at an undefined location, by modified.
:by_rss_log, # Species lists with RSS log, in RSS order
:by_user, # Species lists created by user, alphabetically.
:in_set, # Species lists in a given set.
:pattern_search, # Species lists matching a pattern, alphabetically.
],
:User => [
:all, # All users, by name.
:in_set, # Users in a given set.
:pattern_search, # Users matching login/name, alphabetically.
],
}
# Map each pair of tables to the foreign key name.
self.join_conditions = {
:comments => {
:location_descriptions => :target,
:locations => :target,
:name_descriptions => :target,
:names => :target,
:observations => :target,
:projects => :target,
:species_lists => :target,
:users => :user_id,
},
:images => {
:users => :user_id,
:licenses => :license_id,
},
:images_observations => {
:images => :image_id,
:observations => :observation_id,
},
:interests => {
:locations => :target,
:names => :target,
:observations => :target,
:users => :user_id,
},
:location_descriptions => {
:locations => :location_id,
:users => :user_id,
},
:location_descriptions_admins => {
:location_descriptions => :location_description_id,
:user_groups => :user_group_id,
},
:location_descriptions_authors => {
:location_descriptions => :location_description_id,
:users => :user_id,
},
:location_descriptions_editors => {
:location_descriptions => :location_description_id,
:users => :user_id,
},
:location_descriptions_readers => {
:location_descriptions => :location_description_id,
:user_groups => :user_group_id,
},
:location_descriptions_versions => {
:location_descriptions => :location_description_id,
},
:location_descriptions_writers => {
:location_descriptions => :location_description_id,
:user_groups => :user_group_id,
},
:locations => {
:licenses => :license_id,
:'location_descriptions.default' => :description_id,
:rss_logs => :rss_log_id,
:users => :user_id,
},
:locations_versions => {
:locations => :location_id,
},
:name_descriptions => {
:names => :name_id,
:users => :user_id,
},
:name_descriptions_admins => {
:name_descriptions => :name_description_id,
:user_groups => :user_group_id,
},
:name_descriptions_authors => {
:name_descriptions => :name_description_id,
:users => :user_id,
},
:name_descriptions_editors => {
:name_descriptions => :name_description_id,
:users => :user_id,
},
:name_descriptions_readers => {
:name_descriptions => :name_description_id,
:user_groups => :user_group_id,
},
:name_descriptions_versions => {
:name_descriptions => :name_description_id,
},
:name_descriptions_writers => {
:name_descriptions => :name_description_id,
:user_groups => :user_group_id,
},
:names => {
:licenses => :license_id,
:'name_descriptions.default' => :description_id,
:rss_logs => :rss_log_id,
:users => :user_id,
:'users.reviewer' => :reviewer_id,
},
:names_versions => {
:names => :name_id,
},
:namings => {
:names => :name_id,
:observations => :observation_id,
:users => :user_id,
},
:notifications => {
:names => :obj,
:users => :user_id,
},
:observations => {
:locations => :location_id,
:names => :name_id,
:rss_logs => :rss_log_id,
:users => :user_id,
:'images.thumb_image' => :thumb_image_id,
},
:observations_species_lists => {
:observations => :observation_id,
:species_lists => :species_list_id,
},
:projects => {
:users => :user_id,
:user_groups => :user_group_id,
:'user_groups.admin_group' => :admin_group_id,
},
:rss_logs => {
:locations => :location_id,
:names => :name_id,
:observations => :observation_id,
:species_lists => :species_list_id,
},
:species_lists => {
:locations => :location_id,
:rss_logs => :rss_log_id,
:users => :user_id,
},
:user_groups_users => {
:user_groups => :user_group_id,
:users => :user_id,
},
:users => {
:images => :image_id,
:licenses => :license_id,
:locations => :location_id,
},
:votes => {
:namings => :naming_id,
:observations => :observation_id,
:users => :user_id,
},
}
# Return the default order for this query.
def default_order
case model_symbol
when :Comment ; 'created'
when :Image ; 'created'
when :Location ; 'name'
when :LocationDescription ; 'name'
when :Name ; 'name'
when :NameDescription ; 'name'
when :Observation ; 'date'
when :Project ; 'title'
when :RssLog ; 'modified'
when :SpeciesList ; 'title'
when :User ; 'name'
end
end
##############################################################################
#
# :section: Titles
#
##############################################################################
# Holds the title, as a localization with args. The default is
# :query_title_{model}_{flavor}, passing in +params+ as args.
#
# self.title_args = {
# :tag => :app_advanced_search,
# :pattern => clean_pattern,
# }
#
attr_accessor :title_args
# Put together a localized title for this query. (Intended for use as title
# of the results index page.)
def title
initialize_query if !initialized?
if raw = title_args[:raw]
raw
else
title_args[:tag].to_sym.t(title_args)
end
end
##############################################################################
#
# :section: Coercion
#
##############################################################################
# Attempt to coerce a query for one model into a related query for another
# model. This is currently only defined for a very few specific cases. I
# have no idea how to generalize it. Returns a new Query in rare successful
# cases; returns +nil+ in all other cases.
def coerce(new_model, just_test=false)
old_model = self.model_symbol
old_flavor = self.flavor
new_model = new_model.to_s.to_sym
# Going from list_rss_logs to showing observation, name, etc.
if (old_model == :RssLog) and
(old_flavor == :all) and
(new_model.to_s.constantize.reflect_on_association(:rss_log) rescue false)
just_test or begin
params2 = params.dup
params2.delete(:type)
self.class.lookup(new_model, :by_rss_log, params2)
end
# Going from objects with observations to those observations themselves.
elsif ( (new_model == :Observation) and
[:Image, :Location, :Name].include?(old_model) and
old_flavor.to_s.match(/^with_observations/) ) or
( (new_model == :LocationDescription) and
(old_model == :Location) and
old_flavor.to_s.match(/^with_descriptions/) ) or
( (new_model == :NameDescription) and
(old_model == :Name) and
old_flavor.to_s.match(/^with_descriptions/) )
just_test or begin
if old_flavor.to_s.match(/^with_[a-z]+$/)
new_flavor = :all
else
new_flavor = old_flavor.to_s.sub(/^with_[a-z]+_/,'').to_sym
end
params2 = params.dup
if params2[:title]
params2[:title] = "raw " + title
elsif params2[:old_title]
# This is passed through from previous coerce.
params2[:title] = "raw " + params2[:old_title]
params2.delete(:old_title)
end
if params2[:old_by]
# This is passed through from previous coerce.
params2[:by] = params2[:old_by]
params2.delete(:old_by)
elsif params2[:by]
# Can't be sure old sort order will continue to work.
params2.delete(:by)
end
self.class.lookup(new_model, new_flavor, params2)
end
# Going from observations to objects with those observations.
elsif ( (old_model == :Observation) and
[:Image, :Location, :Name].include?(new_model) ) or
( (old_model == :LocationDescription) and
(new_model == :Location) ) or
( (old_model == :NameDescription) and
(new_model == :Name) )
just_test or begin
if old_model == :Observation
type1 = :observations
type2 = :observation
else
type1 = :descriptions
type2 = old_model.to_s.underscore.to_sym
end
if old_flavor == :all
new_flavor = :"with_#{type1}"
else
new_flavor = :"with_#{type1}_#{old_flavor}"
end
params2 = params.dup
if params2[:title]
# This can spiral out of control, but so be it.
params2[:title] = "raw " +
:"query_title_with_#{type1}s_in_set".
t(:type1 => title, :type => type2)
end
if params2[:by]
# Can't be sure old sort order will continue to work.
params2.delete(:by)
end
if old_flavor == :in_set
params2.delete(:title) if params2.has_key?(:title)
self.class.lookup(new_model, :"with_#{type1}_in_set",
params2.merge(:old_title => title, :old_by => params[:by]))
elsif old_flavor == :advanced_search || old_flavor == :pattern_search
params2.delete(:title) if params2.has_key?(:title)
self.class.lookup(new_model, :"with_#{type1}_in_set",
:ids => result_ids, :old_title => title, :old_by => params[:by])
elsif (new_model == :Location) and
(old_flavor == :at_location)
self.class.lookup(new_model, :in_set,
:ids => params2[:location])
elsif (new_model == :Name) and
(old_flavor == :of_name)
# TODO -- need 'synonyms' flavor
# params[:synonyms] == :all / :no / :exclusive
# params[:misspellings] == :either / :no / :only
nil
elsif allowed_model_flavors[new_model].include?(new_flavor)
self.class.lookup(new_model, new_flavor, params2)
end
end
# Let superclass handle anything else.
else
super
end
end
##############################################################################
#
# :section: Queries
#
##############################################################################
# Give query a default title before passing off to standard initializer.
def initialize_query
self.title_args = params.merge(
:tag => "query_title_#{flavor}".to_sym,
:type => model_string.underscore.to_sym
)
super
end
# Allow all queries to customize title.
def initialize_global
if args = params[:title]
for line in args
raise "Invalid syntax in :title parameter: '#{line}'" if line !~ / /
title_args[$`.to_sym] = $'
end
end
end
# ----------------------------
# Sort orders.
# ----------------------------
# Tell SQL how to sort results using the :by => :blah mechanism.
def initialize_order(by)
table = model.table_name
case by
when 'modified', 'created', 'last_login', 'num_views'
if model.column_names.include?(by)
"#{table}.#{by} DESC"
end
when 'date'
if model.column_names.include?('date')
"#{table}.date DESC"
elsif model.column_names.include?('when')
"#{table}.when DESC"
elsif model.column_names.include?('created')
"#{table}.created DESC"
end
when 'name'
if model == Image
self.join << {:images_observations => {:observations => :names}}
self.group = 'images.id'
'MIN(names.search_name) ASC, images.when DESC'
elsif model == Location
'locations.name ASC'
elsif model == LocationDescription
self.join << :locations
'locations.name ASC, location_descriptions.created ASC'
elsif model == Name
'names.text_name ASC, names.author ASC'
elsif model == NameDescription
self.join << :names
'names.text_name ASC, names.author ASC, name_descriptions.created ASC'
elsif model == Observation
self.join << :names
'names.text_name ASC, names.author ASC, observations.when DESC'
elsif model.column_names.include?('search_name')
"#{table}.search_name ASC"
elsif model.column_names.include?('name')
"#{table}.name ASC"
elsif model.column_names.include?('title')
"#{table}.title ASC"
end
when 'title', 'login', 'summary', 'copyright_holder', 'where'
if model.column_names.include?(by)
"#{table}.#{by} ASC"
end
when 'user'
if model.column_names.include?('user_id')
self.join << :users
'IF(users.name = "" OR users.name IS NULL, users.login, users.name) ASC'
end
when 'location'
if model.column_names.include?('location_id')
self.join << :locations
'locations.name ASC'
end
when 'rss_log'
if model.column_names.include?('rss_log_id')
self.join << :rss_logs
'rss_logs.modified DESC'
end
when 'confidence'
if model_symbol == :Image
self.join << {:images_observations => :observations}
"observations.vote_cache DESC"
elsif model_symbol == :Observation
"observations.vote_cache DESC"
end
when 'image_quality'
if model_symbol == :Image
"images.vote_cache DESC"
end
when 'thumbnail_quality'
if model_symbol == :Observation
self.join << :'images.thumb_image'
"images.vote_cache DESC, observations.vote_cache DESC"
end
when 'contribution'
if model_symbol == :User
'users.contribution DESC'
end
when 'original_name'
if model_symbol == :Image
"images.original_name ASC"
end
end
end
# ----------------------------
# Model customization.
# ----------------------------
def initialize_comment
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
initialize_model_do_objects_by_id(:users)
initialize_model_do_type_list(:types, :target_type, Comment.all_types)
initialize_model_do_search(:summary_has, :summary)
initialize_model_do_search(:content_has, :comment)
end
def initialize_image
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
initialize_model_do_date(:date, :when)
initialize_model_do_objects_by_id(:users)
initialize_model_do_objects_by_name(
Name, :names, 'observations.name_id',
:join => {:images_observations => :observations}
)
initialize_model_do_objects_by_name(
Name, :synonym_names, 'observations.name_id',
:filter => :synonyms,
:join => {:images_observations => :observations}
)
initialize_model_do_objects_by_name(
Name, :children_names, 'observations.name_id',
:filter => :all_children,
:join => {:images_observations => :observations}
)
initialize_model_do_locations('observations',
:join => {:images_observations => :observations}
)
initialize_model_do_objects_by_name(
SpeciesList, :species_lists, 'observations_species_lists.species_list_id',
:join => {:images_observations => {:observations => :observations_species_lists}}
)
if params[:has_observation]
self.join << :images_observations
end
initialize_model_do_image_size
initialize_model_do_image_types
initialize_model_do_boolean(:has_notes,
'LENGTH(COALESCE(images.notes,"")) > 0',
'LENGTH(COALESCE(images.notes,"")) = 0'
)
initialize_model_do_search(:notes_has, :notes)
initialize_model_do_search(:copyright_holder_has, :copyright_holder)
initialize_model_do_license
initialize_model_do_boolean(:has_votes,
'LENGTH(COALESCE(images.votes,"")) > 0',
'LENGTH(COALESCE(images.votes,"")) = 0'
)
initialize_model_do_range(:quality, :vote_cache)
initialize_model_do_range(:confidence, 'observations.vote_cache',
:join => {:images_observations => :observations}
)
initialize_model_do_boolean(:ok_for_export,
'images.ok_for_export IS TRUE',
'images.ok_for_export IS FALSE'
)
end
def initialize_location
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
initialize_model_do_objects_by_id(:users)
end
def initialize_location_description
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
initialize_model_do_objects_by_id(:users)
end
def initialize_name
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
initialize_model_do_objects_by_id(:users)
initialize_model_do_misspellings
initialize_model_do_deprecated
initialize_model_do_objects_by_name(
Name, :synonym_names, :id, :filter => :synonyms
)
initialize_model_do_objects_by_name(
Name, :children_names, :id, :filter => :all_children
)
initialize_model_do_locations('observations', :join => :observations)
initialize_model_do_objects_by_name(
SpeciesList, :species_lists,
'observations_species_lists.species_list_id',
:join => {:observations => :observations_species_lists}
)
initialize_model_do_rank
initialize_model_do_boolean(:is_deprecated,
'names.deprecated IS TRUE',
'names.deprecated IS FALSE'
)
initialize_model_do_boolean(:has_synonyms,
'names.synonym_id IS NOT NULL',
'names.synonym_id IS NULL'
)
initialize_model_do_boolean(:ok_for_export,
'names.ok_for_export IS TRUE',
'names.ok_for_export IS FALSE'
)
if !params[:text_name_has].blank?
initialize_model_do_search(:text_name_has, 'text_name')
end
initialize_model_do_boolean(:has_author,
'LENGTH(COALESCE(names.author,"")) > 0',
'LENGTH(COALESCE(names.author,"")) = 0'
)
if !params[:author_has].blank?
initialize_model_do_search(:author_has, 'author')
end
initialize_model_do_boolean(:has_citation,
'LENGTH(COALESCE(names.citation,"")) > 0',
'LENGTH(COALESCE(names.citation,"")) = 0'
)
if !params[:citation_has].blank?
initialize_model_do_search(:citation_has, 'citation')
end
initialize_model_do_boolean(:has_classification,
'LENGTH(COALESCE(names.classification,"")) > 0',
'LENGTH(COALESCE(names.classification,"")) = 0'
)
if !params[:classification_has].blank?
initialize_model_do_search(:classification_has, 'classification')
end
initialize_model_do_boolean(:has_notes,
'LENGTH(COALESCE(names.notes,"")) > 0',
'LENGTH(COALESCE(names.notes,"")) = 0'
)
if !params[:notes_has].blank?
initialize_model_do_search(:notes_has, 'notes')
end
if params[:has_comments]
self.join << :comments
end
if !params[:comments_has].blank?
initialize_model_do_search(:comments_has,
'CONCAT(comments.summary,comments.notes)')
self.join << :comments
end
initialize_model_do_boolean(:has_default_desc,
'names.description_id IS NOT NULL',
'names.description_id IS NULL'
)
if params[:join_desc] == :default
self.join << :'name_descriptions.default'
elsif (params[:join_desc] == :any) or
!params[:desc_type].blank? or
!params[:desc_project].blank? or
!params[:desc_creator].blank? or
!params[:desc_content].blank?
self.join << :name_descriptions
end
initialize_model_do_type_list(:desc_type,
'name_descriptions.source_type', Description.all_source_types
)
initialize_model_do_objects_by_name(
Project, :desc_project, 'name_descriptions.project_id'
)
initialize_model_do_objects_by_name(
User, :desc_creator, 'name_descriptions.user_id'
)
fields = NameDescription.all_note_fields
fields = fields.map {|f| "COALESCE(name_descriptions.#{f},'')"}
initialize_model_do_search(:desc_content, "CONCAT(#{fields.join(',')})")
end
def initialize_name_description
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
initialize_model_do_objects_by_id(:users)
end
def initialize_observation
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
initialize_model_do_date(:date, :when)
initialize_model_do_objects_by_id(:users)
initialize_model_do_objects_by_name(Name, :names)
initialize_model_do_objects_by_name(
Name, :synonym_names, :name_id, :filter => :synonyms
)
initialize_model_do_objects_by_name(
Name, :children_names, :name_id, :filter => :all_children
)
initialize_model_do_locations
initialize_model_do_objects_by_name(
SpeciesList, :species_lists,
'observations_species_lists.species_list_id',
:join => :observations_species_lists
)
initialize_model_do_range(:confidence, :vote_cache)
initialize_model_do_search(:notes_has, :notes)
initialize_model_do_boolean(:is_col_loc,
'observations.is_collection_location IS TRUE',
'observations.is_collection_location IS FALSE'
)
initialize_model_do_boolean(:has_specimen,
'observations.specimen IS TRUE',
'observations.specimen IS FALSE'
)
initialize_model_do_boolean(:has_location,
'observations.location_id IS NOT NULL',
'observations.location_id IS NULL'
)
if !params[:has_name].nil?
id = Name.unknown.id
initialize_model_do_boolean(:has_name,
"observations.name_id != #{id}",
"observations.name_id == #{id}")
end
initialize_model_do_boolean(:has_notes,
'LENGTH(COALESCE(observations.notes,"")) > 0',
'LENGTH(COALESCE(observations.notes,"")) = 0'
)
initialize_model_do_boolean(:has_images,
'observations.thumb_image_id IS NOT NULL',
'observations.thumb_image_id IS NULL'
)
initialize_model_do_boolean(:has_votes,
'observations.vote_cache IS NOT NULL',
'observations.vote_cache IS NULL'
)
if params[:has_comments]
self.join << :comments
end
if !params[:comments_has].blank?
initialize_model_do_search(:comments_has,
'CONCAT(comments.summary,comments.notes)')
self.join << :comments
end
if !params[:include_admin]
self.where << "observations.user_id != 0"
end
end
def initialize_project
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
initialize_model_do_objects_by_id(:users)
end
def initialize_rss_log
initialize_model_do_time(:modified)
end
def initialize_species_list
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
initialize_model_do_date(:date, :when)
initialize_model_do_objects_by_id(:users)
initialize_model_do_objects_by_name(Name, :names,
'observations.name_id',
:join => {:observations_species_lists => :observations}
)
initialize_model_do_objects_by_name(Name, :synonym_names,
'observations.name_id', :filter => :synonyms,
:join => {:observations_species_lists => :observations}
)
initialize_model_do_objects_by_name(Name, :children_names,
'observations.name_id', :filter => :all_children,
:join => {:observations_species_lists => :observations}
)
initialize_model_do_locations
initialize_model_do_search(:title_has, :title)
initialize_model_do_search(:notes_has, :notes)
initialize_model_do_boolean(:has_notes,
'LENGTH(COALESCE(species_lists.notes,"")) > 0',
'LENGTH(COALESCE(species_lists.notes,"")) = 0'
)
if params[:has_comments]
self.join << :comments
end
if !params[:comments_has].blank?
initialize_model_do_search(:comments_has,
'CONCAT(comments.summary,comments.notes)')
self.join << :comments
end
end
def initialize_user
initialize_model_do_time(:created)
initialize_model_do_time(:modified)
end
# -------------------------------
# Model customization helpers.
# -------------------------------
def initialize_model_do_boolean(arg, true_cond, false_cond)
if !params[arg].nil?
self.where << (params[arg] ? true_cond : false_cond)
end
end
def initialize_model_do_search(arg, col=nil)
if !params[arg].blank?
col = "#{model.table_name}.#{col}" if !col.to_s.match(/\./)
search = google_parse(params[arg])
self.where += google_conditions(search, col)
end
end
def initialize_model_do_range(arg, col, args={})
if params[arg].is_a?(Array)
min, max = params[arg]
self.where << "#{col} >= #{min}" if !min.blank?
self.where << "#{col} <= #{max}" if !max.blank?
if (join = args[:join]) and
(!min.blank? || !max.blank?)
self.join << join
end
end
end
def initialize_model_do_type_list(arg, col, vals)
if !params[arg].blank?
col = "#{model.table_name}.#{col}" if !col.to_s.match(/\./)
types = params[arg].to_s.strip_squeeze.split
types &= vals.map(&:to_s)
if types.any?
self.where << "#{col} IN ('#{types.join("','")}')"
end
end
end
def initialize_model_do_deprecated
case params[:deprecated] || :either
when :no ; self.where << 'names.deprecated IS FALSE'
when :only ; self.where << 'names.deprecated IS TRUE'
end
end
def initialize_model_do_misspellings
case params[:misspellings] || :no
when :no ; self.where << 'names.correct_spelling_id IS NULL'
when :only ; self.where << 'names.correct_spelling_id IS NOT NULL'
end
end
def initialize_model_do_objects_by_id(arg, col=nil)
if ids = params[arg]
col ||= "#{arg.to_s.sub(/s$/,'')}_id"
col = "#{model.table_name}.#{col}" if !col.to_s.match(/\./)
set = clean_id_set(ids)
self.where << "#{col} IN (#{set})"
end
end
def initialize_model_do_objects_by_name(model, arg, col=nil, args={})
names = params[arg]
if names && names.any?
col ||= arg.to_s.sub(/s?$/, '_id')
col = "#{self.model.table_name}.#{col}" if !col.to_s.match(/\./)
objs = []
for name in names
if name.to_s.match(/^\d+$/)
obj = model.safe_find(name)
objs << obj if obj
else
case model.name
when 'Location'
pattern = clean_pattern(Location.clean_name(name))
objs += model.all(:conditions => "name LIKE '%#{pattern}%'")
when 'Name'
objs += model.find_all_by_search_name(name)
objs += model.find_all_by_text_name(name) if objs.empty?
when 'Project', 'SpeciesList'
objs += model.find_all_by_title(name)
when 'User'
name.sub(/ *<.*>/, '')
objs += model.find_all_by_login(name)
else
raise("Forgot to tell initialize_model_do_objects_by_name how " +
"to find instances of #{model.name}!")
end
end
end
if filter = args[:filter]
objs = objs.uniq.map(&filter).flatten
end
if join = args[:join]
self.join << join
end
set = clean_id_set(objs.map(&:id).uniq)
self.where << "#{col} IN (#{set})"
end
end
def initialize_model_do_locations(table=model.table_name, args={})
locs = params[:locations]
if locs && locs.any?
loc_col = "#{table}.location_id"
initialize_model_do_objects_by_name(Location, :locations, loc_col, args)
str = self.where.pop
for name in locs
if name.match(/\D/)
pattern = clean_pattern(name)
str += " OR #{table}.where LIKE '%#{pattern}%'"
end
end
self.where << str
end
end
def initialize_model_do_rank
if !params[:rank].blank?
min, max = params[:rank]
all_ranks = Name.all_ranks
a = all_ranks.index(min) || 0
b = all_ranks.index(max) || (all_ranks.length - 1)
a, b = b, a if a > b
ranks = all_ranks[a..b].map {|r| "'#{r}'"}
self.where << "names.rank IN (#{ranks.join(',')})"
end
end
def initialize_model_do_image_size
if params[:size]
min, max = params[:size]
sizes = Image.all_sizes
pixels = Image.all_sizes_in_pixels
if min
size = pixels[sizes.index(min)]
self.where << "images.width >= #{size} OR images.height >= #{size}"
end
if max
size = pixels[sizes.index(max) + 1]
self.where << "images.width < #{size} AND images.height < #{size}"
end
end
end
def initialize_model_do_image_types
if !params[:content_types].blank?
exts = Image.all_extensions.map(&:to_s)
mimes = Image.all_content_types.map(&:to_s) - ['']
types = params[:types].to_s.strip_squeeze.split & exts
if types.any?
other = types.include?('raw')
types -= ['raw']
types = types.map {|x| mimes[exts.index(x)]}
str1 = "comments.target_type IN ('#{types.join("','")}')"
str2 = "comments.target_type NOT IN ('#{mimes.join("','")}')"
if types.empty?
self.where << str2
elsif other
self.where << "#{str1} OR #{str2}"
else
self.where << str1
end
end
end
end
def initialize_model_do_license
if !params[:license].blank?
license = find_cached_parameter_instance(License, :license)
self.where << "#{model.table_name}.license_id = #{license.id}"
end
end
# ----------------------------
# Date customization.
# ----------------------------
def initialize_model_do_date(arg=:date, col=arg)
col = "#{model.table_name}.#{col}" if !col.to_s.match(/\./)
if vals = params[arg]
initialize_model_do_date_half(true, vals[0], col)
initialize_model_do_date_half(false, vals[1], col)
end
end
def initialize_model_do_date_half(min, val, col)
dir = min ? '>' : '<'
if val.match(/^\d\d\d\d/)
y, m, d = val.split('-')
m ||= min ? 1 : 12
d ||= min ? 1 : 31
self.where << "#{col} #{dir}= '%04d-%02d-%02d'" % [y, m, d]
elsif val.match(/-/)
m, d = val.split('-')
self.where << "MONTH(#{col}) #{dir} #{m} OR " +
"(MONTH(#{col}) = #{m} AND " +
"DAY(#{col}) #{dir}= #{d})"
elsif !val.blank?
self.where << "MONTH(#{col}) #{dir}= #{val}"
end
end
def initialize_model_do_time(arg=:time, col=arg)
col = "#{model.table_name}.#{col}" if !col.to_s.match(/\./)
if vals = params[arg]
initialize_model_do_time_half(true, vals[0], col)
initialize_model_do_time_half(false, vals[1], col)
end
end
def initialize_model_do_time_half(min, val, col)
if !val.blank?
dir = min ? '>' : '<'
y, m, d, h, n, s = val.split('-')
m ||= min ? 1 : 12
d ||= min ? 1 : 31
h ||= min ? 0 : 24
n ||= min ? 0 : 60
s ||= min ? 0 : 60
self.where << "#{col} #{dir}= '%04d-%02d-%02d %02d:%02d:%02d'" %
[y, m, d, h, n, s]
end
end
def validate_date(arg, val)
if val.acts_like?(:date)
val = val.in_time_zone
'%04d-%02d-%02d' % [val.year, val.mon, val.day]
elsif val.to_s.match(/^\d\d\d\d(-\d\d?){0,2}$/i)
val
elsif val.to_s.match(/^\d\d?(-\d\d?)?$/i)
val
elsif val.blank? || val.to_s == '0'
nil
else
raise("Value for :#{arg} should be a date (YYYY-MM-DD or MM-DD), got: #{val.inspect}")
end
end
def validate_time(arg, val)
if val.acts_like?(:time)
val = val.in_time_zone
'%04d-%02d-%02d-%02d-%02d-%02d' %
[val.year, val.mon, val.day, val.hour, val.min, val.sec]
elsif val.to_s.match(/^\d\d\d\d(-\d\d?){0,5}$/i)
val
elsif val.blank? || val.to_s == '0'
nil
else
raise("Value for :#{arg} should be a time (YYYY-MM-DD-HH-MM-SS), got: #{val.inspect}")
end
end
# --------------------------------------------
# Queries that essentially have no filters.
# --------------------------------------------
def initialize_all
if (by = params[:by]) and
(by = :"sort_by_#{by}")
title_args[:tag] = :query_title_all_by
title_args[:order] = by.t
end
# Allow users to filter RSS logs for the object type they're interested in.
if model_symbol == :RssLog
x = params[:type] ||= 'all'
types = x.to_s.split
if !types.include?('all')
types &= RssLog.all_types
if types.empty?
self.where << 'FALSE'
else
self.where << types.map do |type|
"rss_logs.#{type}_id IS NOT NULL"
end.join(' OR ')
end
end
elsif params[:type]
raise "Can't use :type parameter in :#{model_symbol} :all queries!"
end
end
def initialize_by_rss_log
self.join << :rss_logs
params[:by] ||= 'rss_log'
end
# ----------------------------
# Get user contributions.
# ----------------------------
def initialize_by_user
user = find_cached_parameter_instance(User, :user)
title_args[:user] = user.legal_name
table = model.table_name
if model.column_names.include?('user_id')
self.where << "#{table}.user_id = '#{params[:user]}'"
else
raise "Can't figure out how to select #{model_string} by user_id!"
end
case model_symbol
when :Observation
params[:by] ||= 'modified'
when :Image
params[:by] ||= 'modified'
when :Location, :Name, :LocationDescription, :NameDescription
params[:by] ||= 'name'
when :SpeciesList
params[:by] ||= 'title'
when :Comment
params[:by] ||= 'created'
end
end
def initialize_for_target
type = params[:type].to_s.constantize rescue nil
if (!type.reflect_on_association(:comments) rescue true)
raise "The model #{params[:type].inspect} does not support comments!"
end
target = find_cached_parameter_instance(type, :target)
title_args[:object] = target.unique_format_name
self.where << "comments.target_id = '#{target.id}'"
self.where << "comments.target_type = '#{type.name}'"
params[:by] ||= 'created'
end
def initialize_for_user
user = find_cached_parameter_instance(User, :user)
title_args[:user] = user.legal_name
self.join << :observations
self.where << "observations.user_id = '#{params[:user]}'"
params[:by] ||= 'created'
end
def initialize_by_author
initialize_by_editor
end
def initialize_by_editor
user = find_cached_parameter_instance(User, :user)
title_args[:user] = user.legal_name
case model_symbol
when :Name, :Location
version_table = "#{model.table_name}_versions".to_sym
self.join << version_table
self.where << "#{version_table}.user_id = '#{params[:user]}'"
self.where << "#{model.table_name}.user_id != '#{params[:user]}'"
when :NameDescription, :LocationDescription
glue_table = "#{model.name.underscore}s_#{flavor}s".
sub('_by_', '_').to_sym
self.join << glue_table
self.where << "#{glue_table}.user_id = '#{params[:user]}'"
params[:by] ||= 'name'
else
raise "No editors or authors in #{model_string}!"
end
end
# -----------------------------------
# Various subsets of Observations.
# -----------------------------------
def initialize_at_location
location = find_cached_parameter_instance(Location, :location)
title_args[:location] = location.display_name
self.join << :names
self.where << "#{model.table_name}.location_id = '#{params[:location]}'"
params[:by] ||= 'name'
end
def initialize_at_where
title_args[:where] = params[:where]
pattern = clean_pattern(params[:location])
self.join << :names
self.where << "#{model.table_name}.where LIKE '%#{pattern}%'"
params[:by] ||= 'name'
end
def initialize_in_species_list
species_list = find_cached_parameter_instance(SpeciesList, :species_list)
title_args[:species_list] = species_list.format_name
self.join << :names
self.join << :observations_species_lists
self.where << "observations_species_lists.species_list_id = '#{params[:species_list]}'"
params[:by] ||= 'name'
end
# ----------------------------------
# Queryies dealing with synonyms.
# ----------------------------------
def initialize_of_name
name = find_cached_parameter_instance(Name, :name)
synonyms = params[:synonyms] || :no
nonconsensus = params[:nonconsensus] || :no
title_args[:tag] = :query_title_of_name
title_args[:tag] = :query_title_of_name_synonym if synonyms != :no
title_args[:tag] = :query_title_of_name_nonconsensus if nonconsensus != :no
title_args[:name] = name.display_name
if synonyms == :no
name_ids = [name.id] + name.misspelling_ids
elsif synonyms == :all
name_ids = name.synonym_ids
elsif synonyms == :exclusive
name_ids = name.synonym_ids - [name.id] - name.misspelling_ids
else
raise "Invalid synonym inclusion mode: '#{synonyms}'"
end
set = clean_id_set(name_ids)
if nonconsensus == :no
self.where << "observations.name_id IN (#{set}) AND " +
"COALESCE(observations.vote_cache,0) >= 0"
self.order = "COALESCE(observations.vote_cache,0) DESC, observations.when DESC"
elsif nonconsensus == :all
self.where << "namings.name_id IN (#{set})"
self.order = "COALESCE(namings.vote_cache,0) DESC, observations.when DESC"
elsif nonconsensus == :exclusive
self.where << "namings.name_id IN (#{set}) AND " +
"(observations.name_id NOT IN (#{set}) OR " +
"COALESCE(observations.vote_cache,0) < 0)"
self.order = "COALESCE(namings.vote_cache,0) DESC, observations.when DESC"
else
raise "Invalid nonconsensus inclusion mode: '#{nonconsensus}'"
end
# Different join conditions for different models.
if model_symbol == :Observation
if nonconsensus != :no
self.join << :namings
end
elsif model_symbol == :Location
if nonconsensus != :no
self.join << :observations
else
self.join << {:observations => :namings}
end
self.where << "observations.is_collection_location IS TRUE"
elsif model_symbol == :Image
if nonconsensus == :no
self.join << {:images_observations => :observations}
else
self.join << {:images_observations => {:observations => :namings}}
end
end
end
# --------------------------------------------
# Queries dealing with taxonomic hierarchy.
# --------------------------------------------
def initialize_of_children
name = find_cached_parameter_instance(Name, :name)
title_args[:name] = name.display_name
all = params[:all]
all = false if params[:all].nil?
params[:by] ||= 'name'
# If we have to rely on classification strings, just let Name do it, and
# create a pseudo-query based on ids returned by +name.children+.
if all || name.above_genus?
set = clean_id_set(name.children(all).map(&:id))
self.where << "names.id IN (#{set})"
# If at genus or below, we can deduce hierarchy purely by syntax.
else
self.where << "names.text_name LIKE '#{name.text_name} %'"
if !all
if name.rank == :Genus
self.where << "names.text_name NOT LIKE '#{name.text_name} % %'"
else
self.where << "names.text_name NOT LIKE '#{name.text_name} % % %'"
end
end
end
# Add appropriate joins.
if model_symbol == :Observation
self.join << :names
elsif model_symbol == :Image
self.join << {:images_observations => {:observations => :names}}
elsif model_symbol == :Location
self.join << {:observations => :names}
end
end
def initialize_of_parents
name = find_cached_parameter_instance(Name, :name)
title_args[:name] = name.display_name
all = params[:all] || false
set = clean_id_set(name.parents(all).map(&:id))
self.where << "names.id IN (#{set})"
params[:by] ||= 'name'
end
# ---------------------------------------------------------------------
# Coercable image/location/name queries based on observation-related
# conditions.
# ---------------------------------------------------------------------
def initialize_with_observations
if model_symbol == :Image
self.join << {:images_observations => :observations}
else
self.join << :observations
end
params[:by] ||= 'name'
end
def initialize_with_observations_at_location
location = find_cached_parameter_instance(Location, :location)
title_args[:location] = location.display_name
if model_symbol == :Image
self.join << {:images_observations => :observations}
else
self.join << :observations
end
self.where << "observations.location_id = '#{params[:location]}'"
self.where << 'observations.is_collection_location IS TRUE'
params[:by] ||= 'name'
end
def initialize_with_observations_at_where
location = params[:location]
title_args[:where] = location
if model_symbol == :Image
self.join << {:images_observations => :observations}
else
self.join << :observations
end
self.where << "observations.where LIKE '%#{clean_pattern(location)}%'"
self.where << 'observations.is_collection_location IS TRUE'
params[:by] ||= 'name'
end
def initialize_with_observations_by_user
user = find_cached_parameter_instance(User, :user)
title_args[:user] = user.legal_name
if model_symbol == :Image
self.join << {:images_observations => :observations}
else
self.join << :observations
end
self.where << "observations.user_id = '#{params[:user]}'"
if model_symbol == :Location
self.where << 'observations.is_collection_location IS TRUE'
end
params[:by] ||= 'name'
end
def initialize_with_observations_in_set
title_args[:observations] = params[:old_title] ||
:query_title_in_set.t(:type => :observation)
set = clean_id_set(params[:ids])
if model_symbol == :Image
self.join << {:images_observations => :observations}
else
self.join << :observations
end
self.where << "observations.id IN (#{set})"
if model_symbol == :Location
self.where << 'observations.is_collection_location IS TRUE'
end
params[:by] ||= 'name'
end
def initialize_with_observations_in_species_list
species_list = find_cached_parameter_instance(SpeciesList, :species_list)
title_args[:species_list] = species_list.format_name
if model_symbol == :Image
self.join << {:images_observations => {:observations => :observations_species_lists}}
else
self.join << {:observations => :observations_species_lists}
end
self.where << "observations_species_lists.species_list_id = '#{params[:species_list]}'"
if model_symbol == :Location
self.where << 'observations.is_collection_location IS TRUE'
end
params[:by] ||= 'name'
end
def initialize_with_observations_of_children
initialize_of_children
end
def initialize_with_observations_of_name
initialize_of_name
title_args[:tag] = title_args[:tag].to_s.sub('title', 'title_with_observations').to_sym
end
# ---------------------------------------------------------------
# Coercable location/name queries based on description-related
# conditions.
# ---------------------------------------------------------------
def initialize_with_descriptions
type = model.name.underscore
self.join << :"#{type}_descriptions"
params[:by] ||= 'name'
end
def initialize_with_descriptions_by_author
initialize_with_descriptions_by_editor
end
def initialize_with_descriptions_by_editor
type = model.name.underscore
glue = flavor.to_s.sub(/^.*_by_/, '')
desc_table = :"#{type}_descriptions"
glue_table = :"#{type}_descriptions_#{glue}s"
user = find_cached_parameter_instance(User, :user)
title_args[:user] = user.legal_name
self.join << { desc_table => glue_table }
self.where << "#{glue_table}.user_id = '#{params[:user]}'"
params[:by] ||= 'name'
end
def initialize_with_descriptions_by_user
type = model.name.underscore
desc_table = :"#{type}_descriptions"
user = find_cached_parameter_instance(User, :user)
title_args[:user] = user.legal_name
self.join << desc_table
self.where << "#{desc_table}.user_id = '#{params[:user]}'"
params[:by] ||= 'name'
end
# ----------------------------
# Pattern search.
# ----------------------------
def initialize_pattern_search
pattern = params[:pattern].to_s.strip_squeeze
clean = clean_pattern(pattern)
search = google_parse(pattern)
case model_symbol
when :Comment
self.where += google_conditions(search,
'CONCAT(comments.summary,COALESCE(comments.comment,""))')
when :Image
self.join << {:images_observations => {:observations =>
[:locations!, :names] }}
self.where += google_conditions(search,
'CONCAT(names.search_name,COALESCE(images.original_name,""),' +
'COALESCE(images.copyright_holder,""),COALESCE(images.notes,""),' +
'IF(locations.id,locations.name,observations.where))')
when :Location
self.join << :"location_descriptions.default!"
note_fields = LocationDescription.all_note_fields.map do |x|
"COALESCE(location_descriptions.#{x},'')"
end
self.where += google_conditions(search,
"CONCAT(locations.name,#{note_fields.join(',')})")
when :Name
self.join << :"name_descriptions.default!"
note_fields = NameDescription.all_note_fields.map do |x|
"COALESCE(name_descriptions.#{x},'')"
end
self.where += google_conditions(search,
"CONCAT(names.search_name,COALESCE(names.citation,'')," +
"COALESCE(names.notes,''),#{note_fields.join(',')})")
when :Observation
self.join << [:locations!, :names]
self.where += google_conditions(search,
'CONCAT(names.search_name,COALESCE(observations.notes,""),' +
'IF(locations.id,locations.name,observations.where))')
when :Project
self.where += google_conditions(search,
'CONCAT(projects.title,COALESCE(projects.summary,""))')
when :SpeciesList
self.join << :locations!
self.where += google_conditions(search,
'CONCAT(species_lists.title,COALESCE(species_lists.notes,""),' +
'IF(locations.id,locations.name,species_lists.where))')
when :User
self.where += google_conditions(search,
'CONCAT(users.login,users.name)')
else
raise "Forgot to tell me how to build a :#{flavor} query for #{model}!"
end
end
# ----------------------------
# Advanced search.
# ----------------------------
def initialize_advanced_search
name = google_parse(params[:name])
user = google_parse(params[:user].to_s.gsub(/ *<[^<>]*>/, ''))
location = google_parse(params[:location])
content = google_parse(params[:content])
# Force user to enter *something*.
if name.blank? and user.blank? and location.blank? and content.blank?
raise :runtime_no_conditions.t
end
# This case is a disaster. Perform it as an observation query, then
# coerce into images.
if (model_symbol == :Image) and !content.blank?
self.executor = lambda do |args|
args2 = args.dup
args2.delete(:select)
params2 = params.dup
params2.delete(:by)
ids = self.class.lookup(:Observation, flavor, params2).result_ids(args2)
ids = clean_id_set(ids)
args2 = args.dup
extend_join(args2) << :images_observations
extend_where(args2) << "images_observations.observation_id IN (#{ids})"
model.connection.select_rows(query(args2))
end
return
end
case model_symbol
when :Image
self.join << {:images_observations => {:observations => :users}} if !user.blank?
self.join << {:images_observations => {:observations => :names}} if !name.blank?
self.join << {:images_observations => {:observations => :locations!}} if !location.blank?
self.join << {:images_observations => :observations} if !content.blank?
when :Location
self.join << {:observations => :users} if !user.blank?
self.join << {:observations => :names} if !name.blank?
self.join << :observations if !content.blank?
when :Name
self.join << {:observations => :users} if !user.blank?
self.join << {:observations => :locations!} if !location.blank?
self.join << :observations if !content.blank?
when :Observation
self.join << :names if !name.blank?
self.join << :users if !user.blank?
self.join << :locations! if !location.blank?
end
# Name of mushroom...
if !name.blank?
self.where += google_conditions(name, 'names.search_name')
end
# Who observed the mushroom...
if !user.blank?
self.where += google_conditions(user, 'CONCAT(users.login,users.name)')
end
# Where the mushroom was seen...
if !location.blank?
if model_symbol == :Location
self.where += google_conditions(location, 'locations.name')
else
self.where += google_conditions(location,
'IF(locations.id,locations.name,observations.where)')
end
end
# Content of observation and comments...
if !content.blank?
# # This was the old query using left outer join to include comments.
# self.join << case model_symbol
# when :Image ; {:images_observations => {:observations => :comments!}}
# when :Location ; {:observations => :comments!}
# when :Name ; {:observations => :comments!}
# when :Observation ; :comments!
# end
# self.where += google_conditions(content,
# 'CONCAT(observations.notes,IF(comments.id,CONCAT(comments.summary,comments.comment),""))')
# Cannot do left outer join from observations to comments, because it
# will never return. Instead, break it into two queries, one without
# comments, and another with inner join on comments.
self.executor = lambda do |args|
args2 = args.dup
extend_where(args2)
args2[:where] += google_conditions(content, 'observations.notes')
results = model.connection.select_rows(query(args2))
args2 = args.dup
extend_join(args2) << case model_symbol
when :Image ; {:images_observations => {:observations => :comments}}
when :Location ; {:observations => :comments}
when :Name ; {:observations => :comments}
when :Observation ; :comments
end
extend_where(args2)
args2[:where] += google_conditions(content,
'CONCAT(observations.notes,comments.summary,comments.comment)')
results |= model.connection.select_rows(query(args2))
end
end
end
# ----------------------------
# Nested queries.
# ----------------------------
def initialize_inside_observation
obs = find_cached_parameter_instance(Observation, :observation)
title_args[:observation] = obs.unique_format_name
ids = []
ids << obs.thumb_image_id if obs.thumb_image_id
ids += obs.image_ids - [obs.thumb_image_id]
initialize_in_set(ids)
self.outer_id = params[:outer]
# Tell it to skip observations with no images!
self.tweak_outer_query = lambda do |outer|
extend_join(outer.params) << :images_observations
end
end
end