activegroonga 0.0.2 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. data/NEWS.ja.rdoc +4 -0
  2. data/NEWS.rdoc +4 -0
  3. data/README.ja.rdoc +1 -1
  4. data/README.rdoc +1 -1
  5. data/Rakefile +3 -4
  6. data/lib/active_groonga.rb +2 -0
  7. data/lib/active_groonga/base.rb +125 -99
  8. data/lib/active_groonga/column.rb +9 -0
  9. data/lib/active_groonga/dynamic_record_expression_builder.rb +40 -0
  10. data/lib/active_groonga/schema.rb +107 -204
  11. data/lib/active_groonga/schema_dumper.rb +40 -25
  12. data/lib/active_groonga/tasks/groonga.rake +2 -0
  13. data/lib/active_groonga/version.rb +1 -1
  14. data/rails_generators/index_table_groonga/USAGE +23 -0
  15. data/rails_generators/index_table_groonga/index_table_groonga_generator.rb +44 -0
  16. data/rails_generators/index_table_groonga/templates/migration.rb +12 -0
  17. data/rails_generators/migration_groonga/USAGE +29 -0
  18. data/rails_generators/migration_groonga/migration_groonga_generator.rb +19 -0
  19. data/rails_generators/migration_groonga/templates/migration.rb +11 -0
  20. data/test-unit/Rakefile +6 -1
  21. data/test-unit/lib/test/unit/autorunner.rb +26 -3
  22. data/test-unit/lib/test/unit/priority.rb +21 -1
  23. data/test-unit/lib/test/unit/testcase.rb +101 -36
  24. data/test-unit/lib/test/unit/ui/console/testrunner.rb +7 -4
  25. data/test-unit/test/{test_testcase.rb → test-testcase.rb} +30 -1
  26. data/test-unit/test/test_assertions.rb +1 -1
  27. data/test/active-groonga-test-utils.rb +25 -26
  28. data/test/test-base.rb +16 -6
  29. data/test/test-schema-dumper.rb +48 -0
  30. data/test/test-schema.rb +20 -4
  31. data/test/tmp/database/database.groonga +0 -0
  32. data/test/tmp/database/database.groonga.0000000 +0 -0
  33. data/test/tmp/database/database.groonga.0000100 +0 -0
  34. data/test/tmp/database/database.groonga.0000101 +0 -0
  35. metadata +19 -7
data/NEWS.ja.rdoc CHANGED
@@ -1,5 +1,9 @@
1
1
  = お知らせ
2
2
 
3
+ == 0.0.6: 2009-07-31
4
+
5
+ * Ruby/groonga 0.0.6対応
6
+
3
7
  == 0.0.2: 2009-06-05
4
8
 
5
9
  * Ruby/groonga 0.0.2対応
data/NEWS.rdoc CHANGED
@@ -1,5 +1,9 @@
1
1
  = NEWS
2
2
 
3
+ == 0.0.6: 2009-07-31
4
+
5
+ * Support Ruby/groonga 0.0.6.
6
+
3
7
  == 0.0.2: 2009-06-05
4
8
 
5
9
  * Support Ruby/groonga 0.0.2.
data/README.ja.rdoc CHANGED
@@ -20,7 +20,7 @@ Ruby/groongaをベースとしています。Ruby/groongaとgroongaに関
20
20
 
21
21
  == 作者
22
22
 
23
- Kouhei Sutou <kou@clear-code.com>
23
+ Kouhei Sutou <tt><kou@clear-code.com></tt>
24
24
 
25
25
  == ライセンス
26
26
 
data/README.rdoc CHANGED
@@ -20,7 +20,7 @@ URLs about Ruby/groonga and groonga.
20
20
 
21
21
  == Author
22
22
 
23
- Kouhei Sutou <kou@clear-code.com>
23
+ Kouhei Sutou <tt><kou@clear-code.com></tt>
24
24
 
25
25
  == License
26
26
 
data/Rakefile CHANGED
@@ -27,8 +27,6 @@ require 'hoe'
27
27
 
28
28
  ENV["NODOT"] = "yes"
29
29
 
30
- Hoe::Test::SUPPORTED_TEST_FRAMEWORKS[:testunit2] = "test/run-test.rb"
31
-
32
30
  base_dir = File.join(File.dirname(__FILE__))
33
31
  truncate_base_dir = Proc.new do |x|
34
32
  x.gsub(/^#{Regexp.escape(base_dir + File::SEPARATOR)}/, '')
@@ -87,6 +85,7 @@ ENV["VERSION"] ||= guess_version
87
85
  version = ENV["VERSION"]
88
86
  project = nil
89
87
  Hoe.spec('activegroonga') do |_project|
88
+ Hoe::Test::SUPPORTED_TEST_FRAMEWORKS[:testunit2] = "test/run-test.rb"
90
89
  project = _project
91
90
  project.version = version
92
91
  project.rubyforge_name = 'groonga'
@@ -116,14 +115,14 @@ Hoe.spec('activegroonga') do |_project|
116
115
  description = cleanup_white_space(entries[entries.index("Description") + 1])
117
116
  project.summary, project.description, = description.split(/\n\n+/, 3)
118
117
 
119
- project.need_tar = false
120
118
  project.remote_rdoc_dir = "active_groonga"
121
119
  end
122
120
 
123
121
  project.spec.dependencies.delete_if {|dependency| dependency.name == "hoe"}
124
122
 
125
123
  ObjectSpace.each_object(Rake::RDocTask) do |rdoc_task|
126
- t_option_index = rdoc_task.options.index("-t")
124
+ options = rdoc_task.options
125
+ t_option_index = options.index("-t") || options.index("--title")
127
126
  rdoc_task.options[t_option_index, 2] = nil
128
127
  rdoc_task.title = "ActiveGroonga - #{version}"
129
128
  rdoc_task.rdoc_files = Dir.glob("lib/**/*.rb")
@@ -46,6 +46,8 @@ module ActiveGroonga
46
46
  autoload :Callbacks, 'active_groonga/callbacks'
47
47
  autoload :Column, 'active_groonga/column'
48
48
  autoload :Dirty, 'active_groonga/dirty'
49
+ autoload :DynamicRecordExpressionBuilder,
50
+ 'active_groonga/dynamic_record_expression_builder'
49
51
  autoload :Migration, 'active_groonga/migration'
50
52
  autoload :Migrator, 'active_groonga/migration'
51
53
  autoload :NamedScope, 'active_groonga/named_scope'
@@ -480,8 +480,9 @@ module ActiveGroonga
480
480
  defined?(@abstract_class) && @abstract_class == true
481
481
  end
482
482
 
483
- def find(*args)
483
+ def find(*args, &block)
484
484
  options = args.extract_options!
485
+ options = options.merge(:expression => block) if block
485
486
  validate_find_options(options)
486
487
  set_readonly_option!(options)
487
488
 
@@ -528,11 +529,11 @@ module ActiveGroonga
528
529
  end
529
530
 
530
531
  def groonga_table_name(name=nil)
531
- "<table:#{name || table_name}>"
532
+ (name || table_name).to_s
532
533
  end
533
534
 
534
535
  def groonga_metadata_table_name(name)
535
- "<metadata:#{name}>"
536
+ "meta-#{name}"
536
537
  end
537
538
 
538
539
  # Defines an "attribute" method (like +inheritance_column+ or
@@ -588,11 +589,12 @@ module ActiveGroonga
588
589
  unless File.exist?(database_directory)
589
590
  FileUtils.mkdir_p(database_directory)
590
591
  end
592
+ database_directory = File.expand_path(database_directory)
591
593
  database_file = File.join(database_directory, "database.groonga")
592
594
  if File.exist?(database_file)
593
- @@database = Groonga::Database.new(database_file)
595
+ Groonga::Database.new(database_file)
594
596
  else
595
- @@database = Groonga::Database.create(:path => database_file)
597
+ Groonga::Database.create(:path => database_file)
596
598
  end
597
599
  self.database_directory = database_directory
598
600
  end
@@ -610,14 +612,26 @@ module ActiveGroonga
610
612
  directory
611
613
  end
612
614
 
615
+ def index_columns_directory(table_name, target_table_name)
616
+ directory = File.join(columns_directory(table_name), target_table_name)
617
+ FileUtils.mkdir_p(directory) unless File.exist?(directory)
618
+ directory
619
+ end
620
+
613
621
  def metadata_directory
614
622
  directory = File.join(database_directory, "metadata")
615
623
  FileUtils.mkdir_p(directory) unless File.exist?(directory)
616
624
  directory
617
625
  end
618
626
 
619
- def count
620
- table.size
627
+ def count(expression=nil)
628
+ if expression
629
+ table.select do |record|
630
+ expression.call(DynamicRecordExpressionBuilder.new(record))
631
+ end.size
632
+ else
633
+ table.size
634
+ end
621
635
  end
622
636
 
623
637
  private
@@ -627,56 +641,39 @@ module ActiveGroonga
627
641
  end
628
642
 
629
643
  def find_every(options)
630
- limit = options[:limit] ||= 0
631
- conditions = (options[:conditions] || {}).stringify_keys
644
+ expression = options[:expression]
632
645
  include_associations = merge_includes(scope(:find, :include), options[:include])
633
646
 
634
647
  if include_associations.any? && references_eager_loaded_tables?(options)
635
648
  records = find_with_associations(options)
636
649
  else
637
- records = []
638
- target_records = []
639
- original_table = table
640
- index_records = nil
641
- Schema.indexes(table_name).each do |index_definition|
642
- if conditions.has_key?(index_definition.column)
643
- index_column_name =
644
- "#{index_definition.table}/#{index_definition.column}"
645
- index = Schema.index_table.column(index_column_name)
646
- key = conditions.delete(index_definition.column)
647
- index_records = index.search(key, :result => index_records)
648
- end
649
- end
650
- if index_records
651
- sorted_records = index_records.sort([
652
- :key => ".:score",
653
- :order => :descending,
654
- ],
655
- :limit => limit)
656
- limit = sorted_records.size
657
- target_records = sorted_records.records(:order => :ascending).collect do |record|
658
- index_record_id = record.value.unpack("i")[0]
659
- index_record = Groonga::Record.new(index_records, index_record_id)
660
- target_record = index_record.key
661
- target_record.instance_variable_set("@score", index_record.score)
662
- def target_record.score
663
- @score
664
- end
665
- target_record
650
+ if expression
651
+ records = table.select do |record|
652
+ expression.call(DynamicRecordExpressionBuilder.new(record))
666
653
  end
667
654
  else
668
- target_records = original_table.records
669
- limit = target_records.size if limit.zero?
655
+ records = table.select
670
656
  end
671
- target_records.each_with_index do |record, i|
672
- break if records.size >= limit
673
- unless conditions.all? do |name, value|
674
- record[name] == value or
675
- (record.reference_column?(name) and record[name].id == value)
676
- end
677
- next
657
+ sort_options = {}
658
+ limit = options[:limit]
659
+ offset = options[:offset]
660
+ offset = Integer(offset) unless offset.nil?
661
+ if limit and offset.nil?
662
+ sort_options[:limit] = limit
663
+ end
664
+ records = records.sort([:key => ".:score", :order => :descending],
665
+ sort_options)
666
+ if offset
667
+ in_target = false
668
+ _records, records = records, []
669
+ _records.each_with_index do |record, i|
670
+ break if limit and limit <= records.size
671
+ in_target = i >= offset unless in_target
672
+ records << record if in_target
678
673
  end
679
- records << instantiate(record)
674
+ end
675
+ records = records.collect do |record|
676
+ instantiate(record, record.key.id, record.table.domain)
680
677
  end
681
678
  if include_associations.any?
682
679
  preload_associations(records, include_associations)
@@ -751,7 +748,7 @@ module ActiveGroonga
751
748
  end
752
749
  end
753
750
 
754
- VALID_FIND_OPTIONS = [:conditions, :readonly, :limit]
751
+ VALID_FIND_OPTIONS = [:expression, :readonly, :limit, :offset]
755
752
  def validate_find_options(options)
756
753
  options.assert_valid_keys(VALID_FIND_OPTIONS)
757
754
  end
@@ -778,41 +775,35 @@ module ActiveGroonga
778
775
  # Finder methods must instantiate through this method to work with the
779
776
  # single-table inheritance model that makes it possible to create
780
777
  # objects of different types from the same table.
781
- def instantiate(record)
782
- object =
783
- if subclass_name = record[inheritance_column]
784
- # No type given.
785
- if subclass_name.empty?
786
- allocate
778
+ def instantiate(record, id=nil, table=nil)
779
+ id ||= record.id
780
+ table ||= record.table
787
781
 
788
- else
789
- # Ignore type if no column is present since it was probably
790
- # pulled in from a sloppy join.
791
- unless columns_hash.include?(inheritance_column)
792
- allocate
793
-
794
- else
795
- begin
796
- compute_type(subclass_name).allocate
797
- rescue NameError
798
- raise SubclassNotFound,
799
- "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
800
- "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
801
- "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
802
- "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
803
- end
804
- end
805
- end
806
- else
807
- allocate
782
+ subclass_name = nil
783
+ if record.have_column?(inheritance_column)
784
+ subclass_name = record[inheritance_column]
785
+ end
786
+
787
+ if subclass_name.blank? or !columns_hash.include?(inheritance_column)
788
+ object = allocate
789
+ else
790
+ begin
791
+ object = compute_type(subclass_name).allocate
792
+ rescue NameError
793
+ raise SubclassNotFound,
794
+ "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
795
+ "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
796
+ "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
797
+ "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
808
798
  end
799
+ end
809
800
 
810
- object.instance_variable_set("@id", record.id)
801
+ object.instance_variable_set("@id", id)
811
802
  object.instance_variable_set("@score", record.score)
812
803
  attributes = {}
813
- record.table.columns.each do |column|
814
- _, column_name = column.name.split(/\A#{record.table.name}\./, 2)
815
- attributes[column_name] = column[record.id]
804
+ table.columns.each do |column|
805
+ column_name = column.local_name
806
+ attributes[column_name] = record[".#{column_name}"]
816
807
  end
817
808
  object.instance_variable_set("@attributes", attributes)
818
809
  object.instance_variable_set("@attributes_cache", Hash.new)
@@ -829,15 +820,18 @@ module ActiveGroonga
829
820
  end
830
821
 
831
822
  # Enables dynamic finders like <tt>find_by_user_name(user_name)</tt> and <tt>find_by_user_name_and_password(user_name, password)</tt>
832
- # that are turned into <tt>find(:first, :conditions => ["user_name = ?", user_name])</tt> and
833
- # <tt>find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt> respectively. Also works for
834
- # <tt>find(:all)</tt> by using <tt>find_all_by_amount(50)</tt> that is turned into <tt>find(:all, :conditions => ["amount = ?", 50])</tt>.
823
+ # that are turned into <tt>find(:first) {|record| record["user_name"] == user_name}</tt> and
824
+ # <tt>find(:first) {|record| (record["user_name"] ==
825
+ # user_name) & (record["password"] == password)}</tt> respectively. Also works for
826
+ # <tt>find(:all)</tt> by using
827
+ # <tt>find_all_by_amount(50)</tt> that is turned into
828
+ # <tt>find(:all) {|record| record["amount"] == 50}</tt>.
835
829
  #
836
830
  # It's even possible to use all the additional parameters to +find+. For example, the full interface for +find_all_by_amount+
837
831
  # is actually <tt>find_all_by_amount(amount, options)</tt>.
838
832
  #
839
833
  # Also enables dynamic scopes like scoped_by_user_name(user_name) and scoped_by_user_name_and_password(user_name, password) that
840
- # are turned into scoped(:conditions => ["user_name = ?", user_name]) and scoped(:conditions => ["user_name = ? AND password = ?", user_name, password])
834
+ # 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)})
841
835
  # respectively.
842
836
  #
843
837
  # Each dynamic finder, scope or initializer/creator is also defined in the class after it is first invoked, so that future
@@ -851,15 +845,15 @@ module ActiveGroonga
851
845
  bang = match.bang?
852
846
  # def self.find_by_login_and_activated(*args)
853
847
  # options = args.extract_options!
854
- # attributes = construct_attributes_from_arguments(
848
+ # expression = construct_expression_from_arguments(
855
849
  # [:login,:activated],
856
850
  # args
857
851
  # )
858
- # finder_options = { :conditions => attributes }
852
+ # finder_options = { :expression => expression }
859
853
  # validate_find_options(options)
860
854
  # set_readonly_option!(options)
861
855
  #
862
- # if options[:conditions]
856
+ # if options[:expression]
863
857
  # with_scope(:find => finder_options) do
864
858
  # find(:first, options)
865
859
  # end
@@ -870,15 +864,15 @@ module ActiveGroonga
870
864
  self.class_eval <<-EOC, __FILE__, __LINE__
871
865
  def self.#{method_id}(*args)
872
866
  options = args.extract_options!
873
- attributes = construct_attributes_from_arguments(
867
+ expression = construct_expression_from_arguments(
874
868
  [:#{attribute_names.join(',:')}],
875
869
  args
876
870
  )
877
- finder_options = { :conditions => attributes }
871
+ finder_options = {:expression => expression}
878
872
  validate_find_options(options)
879
873
  set_readonly_option!(options)
880
874
 
881
- #{'result = ' if bang}if options[:conditions]
875
+ #{'result = ' if bang}if options[:expression]
882
876
  with_scope(:find => finder_options) do
883
877
  find(:#{finder}, options)
884
878
  end
@@ -897,12 +891,13 @@ module ActiveGroonga
897
891
  # if args[0].is_a?(Hash)
898
892
  # guard_protected_attributes = true
899
893
  # attributes = args[0].with_indifferent_access
900
- # find_attributes = attributes.slice(*[:user_id])
894
+ # find_expression = attributes.slice(*[:user_id])
901
895
  # else
902
- # find_attributes = attributes = construct_attributes_from_arguments([:user_id], args)
896
+ # attributes = construct_attributes_from_arguments([:user_id], args)
897
+ # find_expression = construct_expression_from_arguments([:user_id], args)
903
898
  # end
904
899
  #
905
- # options = { :conditions => find_attributes }
900
+ # options = { :expression => find_expression }
906
901
  # set_readonly_option!(options)
907
902
  #
908
903
  # record = find(:first, options)
@@ -928,7 +923,8 @@ module ActiveGroonga
928
923
  find_attributes = attributes = construct_attributes_from_arguments([:#{attribute_names.join(',:')}], args)
929
924
  end
930
925
 
931
- options = { :conditions => find_attributes }
926
+ find_expression = construct_expression_from_attributes(find_attributes)
927
+ options = { :expression => find_expression }
932
928
  set_readonly_option!(options)
933
929
 
934
930
  record = find(:first, options)
@@ -952,11 +948,11 @@ module ActiveGroonga
952
948
  self.class_eval <<-EOC, __FILE__, __LINE__
953
949
  def self.#{method_id}(*args) # def self.scoped_by_user_name_and_password(*args)
954
950
  options = args.extract_options! # options = args.extract_options!
955
- attributes = construct_attributes_from_arguments( # attributes = construct_attributes_from_arguments(
951
+ expression = construct_expression_from_arguments( # expression = construct_expression_from_arguments(
956
952
  [:#{attribute_names.join(',:')}], args # [:user_name, :password], args
957
953
  ) # )
958
954
  #
959
- scoped(:conditions => attributes) # scoped(:conditions => attributes)
955
+ scoped(:expression => expression) # scoped(:expression => expression)
960
956
  end # end
961
957
  EOC
962
958
  send(method_id, *arguments)
@@ -968,11 +964,43 @@ module ActiveGroonga
968
964
 
969
965
  def construct_attributes_from_arguments(attribute_names, arguments)
970
966
  attributes = {}
971
- attribute_names.each_with_index { |name, idx| attributes[name] = arguments[idx] }
967
+ attribute_names.each_with_index do |name, i|
968
+ attributes[name] = arguments[i]
969
+ end
972
970
  attributes
973
971
  end
974
972
 
975
- # Similar in purpose to +expand_hash_conditions_for_aggregates+.
973
+ def construct_expression_from_attributes(attributes)
974
+ Proc.new do |record|
975
+ builder = nil
976
+ attributes.each do |name, value|
977
+ expression = (record[name] == value)
978
+ if builder
979
+ builder = builder & expression
980
+ else
981
+ builder = expression
982
+ end
983
+ end
984
+ builder
985
+ end
986
+ end
987
+
988
+ def construct_expression_from_arguments(attribute_names, arguments)
989
+ Proc.new do |record|
990
+ builder = nil
991
+ attribute_names.each_with_index do |name, i|
992
+ expression = (record[name] == arguments[i])
993
+ if builder
994
+ builder = builder & expression
995
+ else
996
+ builder = expression
997
+ end
998
+ end
999
+ builder
1000
+ end
1001
+ end
1002
+
1003
+ # Similar in purpose to +expand_hash_expression_for_aggregates+.
976
1004
  def expand_attribute_names_for_aggregates(attribute_names)
977
1005
  expanded_attribute_names = []
978
1006
  attribute_names.each do |attribute_name|
@@ -1381,7 +1409,6 @@ module ActiveGroonga
1381
1409
  def update(attribute_names=@attributes.keys)
1382
1410
  attribute_names = remove_readonly_attributes(attribute_names)
1383
1411
  table = self.class.table
1384
- indexes = Schema.indexes(table)
1385
1412
  quoted_attributes = attributes_with_quotes(false, attribute_names)
1386
1413
  quoted_attributes.each do |name, value|
1387
1414
  column = table.column(name)
@@ -1395,7 +1422,6 @@ module ActiveGroonga
1395
1422
  def create
1396
1423
  table = self.class.table
1397
1424
  record = table.add
1398
- indexes = Schema.indexes(table)
1399
1425
  quoted_attributes = attributes_with_quotes
1400
1426
  quoted_attributes.each do |name, value|
1401
1427
  record[name] = value