activegroonga 0.0.2 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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