lib/active_groonga/base.rb in activegroonga-0.0.2 vs lib/active_groonga/base.rb in activegroonga-0.0.6
- old
+ new
@@ -478,12 +478,13 @@
# B descends from A, then B.base_class will return B.
def abstract_class?
defined?(@abstract_class) && @abstract_class == true
end
- def find(*args)
+ def find(*args, &block)
options = args.extract_options!
+ options = options.merge(:expression => block) if block
validate_find_options(options)
set_readonly_option!(options)
case args.first
when :first
@@ -526,15 +527,15 @@
def table
context[groonga_table_name]
end
def groonga_table_name(name=nil)
- "<table:#{name || table_name}>"
+ (name || table_name).to_s
end
def groonga_metadata_table_name(name)
- "<metadata:#{name}>"
+ "meta-#{name}"
end
# Defines an "attribute" method (like +inheritance_column+ or
# +table_name+). A new (class) method will be created with the
# given name. If a value is specified, the new method will
@@ -586,15 +587,16 @@
Groonga::Context.default = nil
Groonga::Context.default_options = {:encoding => spec[:encoding]}
unless File.exist?(database_directory)
FileUtils.mkdir_p(database_directory)
end
+ database_directory = File.expand_path(database_directory)
database_file = File.join(database_directory, "database.groonga")
if File.exist?(database_file)
- @@database = Groonga::Database.new(database_file)
+ Groonga::Database.new(database_file)
else
- @@database = Groonga::Database.create(:path => database_file)
+ Groonga::Database.create(:path => database_file)
end
self.database_directory = database_directory
end
end
@@ -608,78 +610,73 @@
directory = File.join(tables_directory, table_name.to_s, "columns")
FileUtils.mkdir_p(directory) unless File.exist?(directory)
directory
end
+ def index_columns_directory(table_name, target_table_name)
+ directory = File.join(columns_directory(table_name), target_table_name)
+ FileUtils.mkdir_p(directory) unless File.exist?(directory)
+ directory
+ end
+
def metadata_directory
directory = File.join(database_directory, "metadata")
FileUtils.mkdir_p(directory) unless File.exist?(directory)
directory
end
- def count
- table.size
+ def count(expression=nil)
+ if expression
+ table.select do |record|
+ expression.call(DynamicRecordExpressionBuilder.new(record))
+ end.size
+ else
+ table.size
+ end
end
private
def find_initial(options)
options.update(:limit => 1)
find_every(options).first
end
def find_every(options)
- limit = options[:limit] ||= 0
- conditions = (options[:conditions] || {}).stringify_keys
+ expression = options[:expression]
include_associations = merge_includes(scope(:find, :include), options[:include])
if include_associations.any? && references_eager_loaded_tables?(options)
records = find_with_associations(options)
else
- records = []
- target_records = []
- original_table = table
- index_records = nil
- Schema.indexes(table_name).each do |index_definition|
- if conditions.has_key?(index_definition.column)
- index_column_name =
- "#{index_definition.table}/#{index_definition.column}"
- index = Schema.index_table.column(index_column_name)
- key = conditions.delete(index_definition.column)
- index_records = index.search(key, :result => index_records)
+ if expression
+ records = table.select do |record|
+ expression.call(DynamicRecordExpressionBuilder.new(record))
end
- end
- if index_records
- sorted_records = index_records.sort([
- :key => ".:score",
- :order => :descending,
- ],
- :limit => limit)
- limit = sorted_records.size
- target_records = sorted_records.records(:order => :ascending).collect do |record|
- index_record_id = record.value.unpack("i")[0]
- index_record = Groonga::Record.new(index_records, index_record_id)
- target_record = index_record.key
- target_record.instance_variable_set("@score", index_record.score)
- def target_record.score
- @score
- end
- target_record
- end
else
- target_records = original_table.records
- limit = target_records.size if limit.zero?
+ records = table.select
end
- target_records.each_with_index do |record, i|
- break if records.size >= limit
- unless conditions.all? do |name, value|
- record[name] == value or
- (record.reference_column?(name) and record[name].id == value)
- end
- next
+ sort_options = {}
+ limit = options[:limit]
+ offset = options[:offset]
+ offset = Integer(offset) unless offset.nil?
+ if limit and offset.nil?
+ sort_options[:limit] = limit
+ end
+ records = records.sort([:key => ".:score", :order => :descending],
+ sort_options)
+ if offset
+ in_target = false
+ _records, records = records, []
+ _records.each_with_index do |record, i|
+ break if limit and limit <= records.size
+ in_target = i >= offset unless in_target
+ records << record if in_target
end
- records << instantiate(record)
end
+ records = records.collect do |record|
+ instantiate(record, record.key.id, record.table.domain)
+ end
if include_associations.any?
preload_associations(records, include_associations)
end
end
@@ -749,11 +746,11 @@
else
[o]
end
end
- VALID_FIND_OPTIONS = [:conditions, :readonly, :limit]
+ VALID_FIND_OPTIONS = [:expression, :readonly, :limit, :offset]
def validate_find_options(options)
options.assert_valid_keys(VALID_FIND_OPTIONS)
end
def set_readonly_option!(options) #:nodoc:
@@ -776,45 +773,39 @@
end
# Finder methods must instantiate through this method to work with the
# single-table inheritance model that makes it possible to create
# objects of different types from the same table.
- def instantiate(record)
- object =
- if subclass_name = record[inheritance_column]
- # No type given.
- if subclass_name.empty?
- allocate
+ def instantiate(record, id=nil, table=nil)
+ id ||= record.id
+ table ||= record.table
- else
- # Ignore type if no column is present since it was probably
- # pulled in from a sloppy join.
- unless columns_hash.include?(inheritance_column)
- allocate
+ subclass_name = nil
+ if record.have_column?(inheritance_column)
+ subclass_name = record[inheritance_column]
+ end
- else
- begin
- compute_type(subclass_name).allocate
- rescue NameError
- raise SubclassNotFound,
- "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
- "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
- end
- end
- end
- else
- allocate
+ if subclass_name.blank? or !columns_hash.include?(inheritance_column)
+ object = allocate
+ else
+ begin
+ object = compute_type(subclass_name).allocate
+ rescue NameError
+ raise SubclassNotFound,
+ "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
+ "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
end
+ end
- object.instance_variable_set("@id", record.id)
+ object.instance_variable_set("@id", id)
object.instance_variable_set("@score", record.score)
attributes = {}
- record.table.columns.each do |column|
- _, column_name = column.name.split(/\A#{record.table.name}\./, 2)
- attributes[column_name] = column[record.id]
+ table.columns.each do |column|
+ column_name = column.local_name
+ attributes[column_name] = record[".#{column_name}"]
end
object.instance_variable_set("@attributes", attributes)
object.instance_variable_set("@attributes_cache", Hash.new)
if object.respond_to_without_attributes?(:after_find)
@@ -827,19 +818,22 @@
object
end
# Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
- # that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and
- # <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for
- # <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>.
+ # that are turned into <tt>find(:first) {|record| record["user_name"] == user_name}</tt> and
+ # <tt>find(:first) {|record| (record["user_name"] ==
+ # user_name) & (record["password"] == password)}</tt> respectively. Also works for
+ # <tt>find(:all)</tt> by using
+ # <tt>find_all_by_amount(50)</tt> that is turned into
+ # <tt>find(:all) {|record| record["amount"] == 50}</tt>.
#
# It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
# is actually <tt>find_all_by_amount(amount, options)</tt>.
#
# Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
- # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
+ # are turned into scoped(:expression => Proc.new {|record| record["user_name"] == user_name}) and scoped(:expression => Proc.new {|record| (record["user_name"] == user_name) & (record["password"] == password)})
# respectively.
#
# Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
# attempts to use it do not run through method_missing.
def method_missing(method_id, *arguments, &block)
@@ -849,38 +843,38 @@
if match.finder?
finder = match.finder
bang = match.bang?
# def self.find_by_login_and_activated(*args)
# options = args.extract_options!
- # attributes = construct_attributes_from_arguments(
+ # expression = construct_expression_from_arguments(
# [:login,:activated],
# args
# )
- # finder_options = { :conditions => attributes }
+ # finder_options = { :expression => expression }
# validate_find_options(options)
# set_readonly_option!(options)
#
- # if options[:conditions]
+ # if options[:expression]
# with_scope(:find => finder_options) do
# find(:first, options)
# end
# else
# find(:first, options.merge(finder_options))
# end
# end
self.class_eval <<-EOC, __FILE__, __LINE__
def self.#{method_id}(*args)
options = args.extract_options!
- attributes = construct_attributes_from_arguments(
+ expression = construct_expression_from_arguments(
[:#{attribute_names.join(',:')}],
args
)
- finder_options = { :conditions => attributes }
+ finder_options = {:expression => expression}
validate_find_options(options)
set_readonly_option!(options)
- #{'result = ' if bang}if options[:conditions]
+ #{'result = ' if bang}if options[:expression]
with_scope(:find => finder_options) do
find(:#{finder}, options)
end
else
find(:#{finder}, options.merge(finder_options))
@@ -895,16 +889,17 @@
# guard_protected_attributes = false
#
# if args[0].is_a?(Hash)
# guard_protected_attributes = true
# attributes = args[0].with_indifferent_access
- # find_attributes = attributes.slice(*[:user_id])
+ # find_expression = attributes.slice(*[:user_id])
# else
- # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args)
+ # attributes = construct_attributes_from_arguments([:user_id], args)
+ # find_expression = construct_expression_from_arguments([:user_id], args)
# end
#
- # options = { :conditions => find_attributes }
+ # options = { :expression => find_expression }
# set_readonly_option!(options)
#
# record = find(:first, options)
#
# if record.nil?
@@ -926,11 +921,12 @@
find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
else
find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
end
- options = { :conditions => find_attributes }
+ find_expression = construct_expression_from_attributes(find_attributes)
+ options = { :expression => find_expression }
set_readonly_option!(options)
record = find(:first, options)
if record.nil?
@@ -950,15 +946,15 @@
super unless all_attributes_exists?(attribute_names)
if match.scope?
self.class_eval <<-EOC, __FILE__, __LINE__
def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
options = args.extract_options! # options = args.extract_options!
- attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
+ expression = construct_expression_from_arguments( # expression = construct_expression_from_arguments(
[:#{attribute_names.join(',:')}], args # [:user_name, :password], args
) # )
#
- scoped(:conditions => attributes) # scoped(:conditions => attributes)
+ scoped(:expression => expression) # scoped(:expression => expression)
end # end
EOC
send(method_id, *arguments)
end
else
@@ -966,15 +962,47 @@
end
end
def construct_attributes_from_arguments(attribute_names, arguments)
attributes = {}
- attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
+ attribute_names.each_with_index do |name, i|
+ attributes[name] = arguments[i]
+ end
attributes
end
- # Similar in purpose to +expand_hash_conditions_for_aggregates+.
+ def construct_expression_from_attributes(attributes)
+ Proc.new do |record|
+ builder = nil
+ attributes.each do |name, value|
+ expression = (record[name] == value)
+ if builder
+ builder = builder & expression
+ else
+ builder = expression
+ end
+ end
+ builder
+ end
+ end
+
+ def construct_expression_from_arguments(attribute_names, arguments)
+ Proc.new do |record|
+ builder = nil
+ attribute_names.each_with_index do |name, i|
+ expression = (record[name] == arguments[i])
+ if builder
+ builder = builder & expression
+ else
+ builder = expression
+ end
+ end
+ builder
+ end
+ end
+
+ # Similar in purpose to +expand_hash_expression_for_aggregates+.
def expand_attribute_names_for_aggregates(attribute_names)
expanded_attribute_names = []
attribute_names.each do |attribute_name|
unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
aggregate_mapping(aggregation).each do |field_attr, aggregate_attr|
@@ -1379,11 +1407,10 @@
# Updates the associated record with values matching those of the instance attributes.
# Returns the number of affected rows.
def update(attribute_names=@attributes.keys)
attribute_names = remove_readonly_attributes(attribute_names)
table = self.class.table
- indexes = Schema.indexes(table)
quoted_attributes = attributes_with_quotes(false, attribute_names)
quoted_attributes.each do |name, value|
column = table.column(name)
next if column.nil?
column[id] = value
@@ -1393,10 +1420,9 @@
# Creates a record with values matching those of the instance attributes
# and returns its id.
def create
table = self.class.table
record = table.add
- indexes = Schema.indexes(table)
quoted_attributes = attributes_with_quotes
quoted_attributes.each do |name, value|
record[name] = value
end
self.id = record.id