torque-postgresql 0.2.16 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +76 -3
  3. data/lib/torque-postgresql.rb +1 -0
  4. data/lib/torque/postgresql.rb +6 -0
  5. data/lib/torque/postgresql/adapter.rb +2 -4
  6. data/lib/torque/postgresql/adapter/database_statements.rb +23 -9
  7. data/lib/torque/postgresql/adapter/oid.rb +12 -1
  8. data/lib/torque/postgresql/adapter/oid/box.rb +28 -0
  9. data/lib/torque/postgresql/adapter/oid/circle.rb +37 -0
  10. data/lib/torque/postgresql/adapter/oid/enum.rb +9 -5
  11. data/lib/torque/postgresql/adapter/oid/enum_set.rb +44 -0
  12. data/lib/torque/postgresql/adapter/oid/line.rb +59 -0
  13. data/lib/torque/postgresql/adapter/oid/range.rb +52 -0
  14. data/lib/torque/postgresql/adapter/oid/segment.rb +73 -0
  15. data/lib/torque/postgresql/adapter/quoting.rb +21 -0
  16. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -0
  17. data/lib/torque/postgresql/adapter/schema_dumper.rb +10 -1
  18. data/lib/torque/postgresql/arel.rb +3 -0
  19. data/lib/torque/postgresql/arel/infix_operation.rb +42 -0
  20. data/lib/torque/postgresql/arel/nodes.rb +32 -0
  21. data/lib/torque/postgresql/arel/operations.rb +18 -0
  22. data/lib/torque/postgresql/arel/visitors.rb +28 -2
  23. data/lib/torque/postgresql/associations.rb +8 -0
  24. data/lib/torque/postgresql/associations/association.rb +30 -0
  25. data/lib/torque/postgresql/associations/association_scope.rb +116 -0
  26. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +117 -0
  27. data/lib/torque/postgresql/associations/builder.rb +2 -0
  28. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +121 -0
  29. data/lib/torque/postgresql/associations/builder/has_many.rb +15 -0
  30. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +15 -0
  31. data/lib/torque/postgresql/associations/preloader.rb +25 -0
  32. data/lib/torque/postgresql/associations/preloader/association.rb +64 -0
  33. data/lib/torque/postgresql/attributes.rb +2 -0
  34. data/lib/torque/postgresql/attributes/builder.rb +1 -0
  35. data/lib/torque/postgresql/attributes/builder/enum.rb +23 -15
  36. data/lib/torque/postgresql/attributes/builder/period.rb +452 -0
  37. data/lib/torque/postgresql/attributes/enum.rb +11 -8
  38. data/lib/torque/postgresql/attributes/enum_set.rb +256 -0
  39. data/lib/torque/postgresql/attributes/lazy.rb +1 -1
  40. data/lib/torque/postgresql/attributes/period.rb +31 -0
  41. data/lib/torque/postgresql/attributes/type_map.rb +3 -5
  42. data/lib/torque/postgresql/autosave_association.rb +40 -0
  43. data/lib/torque/postgresql/auxiliary_statement.rb +201 -198
  44. data/lib/torque/postgresql/auxiliary_statement/settings.rb +20 -12
  45. data/lib/torque/postgresql/base.rb +161 -2
  46. data/lib/torque/postgresql/config.rb +91 -9
  47. data/lib/torque/postgresql/geometry_builder.rb +92 -0
  48. data/lib/torque/postgresql/i18n.rb +1 -1
  49. data/lib/torque/postgresql/railtie.rb +18 -5
  50. data/lib/torque/postgresql/reflection.rb +21 -0
  51. data/lib/torque/postgresql/reflection/abstract_reflection.rb +109 -0
  52. data/lib/torque/postgresql/reflection/association_reflection.rb +30 -0
  53. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +44 -0
  54. data/lib/torque/postgresql/reflection/has_many_reflection.rb +13 -0
  55. data/lib/torque/postgresql/reflection/runtime_reflection.rb +12 -0
  56. data/lib/torque/postgresql/reflection/through_reflection.rb +11 -0
  57. data/lib/torque/postgresql/relation.rb +11 -10
  58. data/lib/torque/postgresql/relation/auxiliary_statement.rb +11 -18
  59. data/lib/torque/postgresql/relation/inheritance.rb +2 -2
  60. data/lib/torque/postgresql/relation/merger.rb +11 -7
  61. data/lib/torque/postgresql/schema_cache.rb +1 -1
  62. data/lib/torque/postgresql/version.rb +1 -1
  63. data/lib/torque/range.rb +40 -0
  64. metadata +41 -9
@@ -3,7 +3,7 @@ module Torque
3
3
  module I18n
4
4
 
5
5
  # Adds extra suport to localize durations
6
- # This is a temporary solution, since 3600.seconds do not translate into
6
+ # This is a temporary solution, since 3600.seconds does not translate into
7
7
  # 1 hour
8
8
  def localize(locale, object, format = :default, options = {})
9
9
  return super unless object.is_a?(ActiveSupport::Duration)
@@ -5,23 +5,36 @@ module Torque
5
5
 
6
6
  # Get information from the running rails app
7
7
  initializer 'torque-postgresql' do |app|
8
- Torque::PostgreSQL.config.eager_load = app.config.eager_load
8
+ torque_config = Torque::PostgreSQL.config
9
+ torque_config.eager_load = app.config.eager_load
9
10
 
10
11
  # Include enum on ActiveRecord::Base so it can have the correct enum
11
12
  # initializer
12
13
  Torque::PostgreSQL::Attributes::Enum.include_on(ActiveRecord::Base)
14
+ Torque::PostgreSQL::Attributes::EnumSet.include_on(ActiveRecord::Base)
15
+ Torque::PostgreSQL::Attributes::Period.include_on(ActiveRecord::Base)
13
16
 
14
- # Define a method to find yet to define constants
15
- Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:const_missing) do |name|
17
+ # Setup belongs_to_many association
18
+ ActiveRecord::Base.belongs_to_many_required_by_default = torque_config.associations
19
+ .belongs_to_many_required_by_default
20
+
21
+ # Define a method to find enumaerators based on the namespace
22
+ torque_config.enum.namespace.define_singleton_method(:const_missing) do |name|
16
23
  Torque::PostgreSQL::Attributes::Enum.lookup(name)
17
24
  end
18
25
 
19
26
  # Define a helper method to get a sample value
20
- Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:sample) do |name|
27
+ torque_config.enum.namespace.define_singleton_method(:sample) do |name|
21
28
  Torque::PostgreSQL::Attributes::Enum.lookup(name).sample
22
29
  end
23
- end
24
30
 
31
+ # Define the exposed constant for auxiliary statements
32
+ if torque_config.auxiliary_statement.exposed_class.present?
33
+ *ns, name = torque_config.auxiliary_statement.exposed_class.split('::')
34
+ base = ns.present? ? Object.const_get(ns.join('::')) : Object
35
+ base.const_set(name, Torque::PostgreSQL::AuxiliaryStatement)
36
+ end
37
+ end
25
38
  end
26
39
  end
27
40
  end
@@ -0,0 +1,21 @@
1
+ require_relative 'reflection/abstract_reflection'
2
+ require_relative 'reflection/association_reflection'
3
+ require_relative 'reflection/belongs_to_many_reflection'
4
+ require_relative 'reflection/has_many_reflection'
5
+ require_relative 'reflection/runtime_reflection'
6
+ require_relative 'reflection/through_reflection'
7
+
8
+ module Torque
9
+ module PostgreSQL
10
+ module Reflection
11
+
12
+ def create(macro, name, scope, options, ar)
13
+ return super unless macro.eql?(:belongs_to_many)
14
+ BelongsToManyReflection.new(name, scope, options, ar)
15
+ end
16
+
17
+ end
18
+
19
+ ::ActiveRecord::Reflection.singleton_class.prepend(Reflection)
20
+ end
21
+ end
@@ -0,0 +1,109 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module AbstractReflection
5
+ AREL_ATTR = ::Arel::Attributes::Attribute
6
+
7
+ ARR_NO_CAST = 'bigint'.freeze
8
+ ARR_CAST = 'bigint[]'.freeze
9
+
10
+ # Check if the foreign key actually exists
11
+ def connected_through_array?
12
+ false
13
+ end
14
+
15
+ # Monkey patching for rais 5.0
16
+ def torque_join_keys
17
+ method(:join_keys).arity.eql?(0) ? join_keys : join_keys(klass)
18
+ end
19
+
20
+ # Manually build the join constraint
21
+ def build_join_constraint(table, foreign_table)
22
+ klass_attr = table[torque_join_keys.key]
23
+ source_attr = foreign_table[torque_join_keys.foreign_key]
24
+
25
+ result = build_id_constraint(klass_attr, source_attr)
26
+ result = table.create_and([result, klass.send(:type_condition, table)]) \
27
+ if klass.finder_needs_type_condition?
28
+
29
+ result
30
+ end
31
+
32
+ # Build the id constraint checking if both types are perfect matching
33
+ def build_id_constraint(klass_attr, source_attr)
34
+ return klass_attr.eq(source_attr) unless connected_through_array?
35
+
36
+ # Klass and key are associated with the reflection Class
37
+ klass_type = klass.columns_hash[torque_join_keys.key]
38
+ # active_record and foreign_key are associated with the source Class
39
+ source_type = active_record.columns_hash[torque_join_keys.foreign_key]
40
+
41
+ # If both are attributes but the left side is not an array, and the
42
+ # right side is, use the ANY operation
43
+ any_operation = arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
44
+ return klass_attr.eq(any_operation) if any_operation
45
+
46
+ # If the left side is not an array, just use the IN condition
47
+ return klass_attr.in(source_attr) unless klass_type.try(:array)
48
+
49
+ # Decide if should apply a cast to ensure same type comparision
50
+ should_cast = klass_type.type.eql?(:integer) && source_type.type.eql?(:integer)
51
+ should_cast &= !klass_type.sql_type.eql?(source_type.sql_type)
52
+ should_cast |= !(klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR))
53
+
54
+ # Apply necessary transformations to values
55
+ klass_attr = cast_constraint_to_array(klass_type, klass_attr, should_cast)
56
+ source_attr = cast_constraint_to_array(source_type, source_attr, should_cast)
57
+
58
+ # Return the overlap condition
59
+ klass_attr.overlaps(source_attr)
60
+ end
61
+
62
+ private
63
+
64
+ # Prepare a value for an array constraint overlap condition
65
+ def cast_constraint_to_array(type, value, should_cast)
66
+ base_ready = type.try(:array) && value.is_a?(AREL_ATTR)
67
+ return value if base_ready && (type.sql_type.eql?(ARR_NO_CAST) || !should_cast)
68
+
69
+ value = ::Arel::Nodes.build_quoted(Array.wrap(value)) unless base_ready
70
+ value = value.cast(ARR_CAST) if should_cast
71
+ value
72
+ end
73
+
74
+ # Check if it's possible to turn both attributes into an ANY condition
75
+ def arel_array_to_any(klass_attr, source_attr, klass_type, source_type)
76
+ return unless !klass_type.try(:array) && source_type.try(:array) &&
77
+ klass_attr.is_a?(AREL_ATTR) && source_attr.is_a?(AREL_ATTR)
78
+
79
+ ::Arel::Nodes::NamedFunction.new('ANY', [source_attr])
80
+ end
81
+
82
+ # returns either +nil+ or the inverse association name that it finds.
83
+ def automatic_inverse_of
84
+ return super unless connected_through_array?
85
+ if can_find_inverse_of_automatically?(self)
86
+ inverse_name = options[:as] || active_record.name.demodulize
87
+ inverse_name = ActiveSupport::Inflector.underscore(inverse_name)
88
+ inverse_name = ActiveSupport::Inflector.pluralize(inverse_name)
89
+ inverse_name = inverse_name.to_sym
90
+
91
+ begin
92
+ reflection = klass._reflect_on_association(inverse_name)
93
+ rescue NameError
94
+ # Give up: we couldn't compute the klass type so we won't be able
95
+ # to find any associations either.
96
+ reflection = false
97
+ end
98
+
99
+ return inverse_name if reflection.connected_through_array? &&
100
+ valid_inverse_reflection?(reflection)
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ ::ActiveRecord::Reflection::AbstractReflection.prepend(AbstractReflection)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,30 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module AssociationReflection
5
+
6
+ def initialize(name, scope, options, active_record)
7
+ super
8
+
9
+ raise ArgumentError, <<-MSG.squish if options[:array] && options[:polymorphic]
10
+ Associations can't be connected through an array at the same time they are
11
+ polymorphic. Please choose one of the options.
12
+ MSG
13
+ end
14
+
15
+ private
16
+
17
+ # Check if the foreign key should be pluralized
18
+ def derive_foreign_key
19
+ result = super
20
+ result = ActiveSupport::Inflector.pluralize(result) \
21
+ if collection? && connected_through_array?
22
+ result
23
+ end
24
+
25
+ end
26
+
27
+ ::ActiveRecord::Reflection::AssociationReflection.prepend(AssociationReflection)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,44 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ class BelongsToManyReflection < ::ActiveRecord::Reflection::AssociationReflection
5
+ def macro
6
+ :belongs_to_many
7
+ end
8
+
9
+ def connected_through_array?
10
+ true
11
+ end
12
+
13
+ def collection?
14
+ true
15
+ end
16
+
17
+ def association_class
18
+ Associations::BelongsToManyAssociation
19
+ end
20
+
21
+ def association_foreign_key
22
+ @association_foreign_key ||= foreign_key
23
+ end
24
+
25
+ def active_record_primary_key
26
+ @active_record_primary_key ||= options[:primary_key] || derive_primary_key
27
+ end
28
+
29
+ private
30
+
31
+ def derive_foreign_key
32
+ klass.primary_key
33
+ end
34
+
35
+ def derive_primary_key
36
+ ActiveSupport::Inflector.pluralize(klass.name.foreign_key)
37
+ end
38
+ end
39
+
40
+ ::ActiveRecord::Reflection::AssociationReflection::VALID_AUTOMATIC_INVERSE_MACROS.push(:belongs_to_many)
41
+ ::ActiveRecord::Reflection.const_set(:BelongsToManyReflection, BelongsToManyReflection)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module HasManyReflection
5
+ def connected_through_array?
6
+ options[:array]
7
+ end
8
+ end
9
+
10
+ ::ActiveRecord::Reflection::HasManyReflection.include(HasManyReflection)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module RuntimeReflection
5
+ delegate :klass, :active_record, :connected_through_array?, :macro, :name,
6
+ :build_id_constraint, to: :@reflection
7
+ end
8
+
9
+ ::ActiveRecord::Reflection::RuntimeReflection.include(RuntimeReflection)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ module Torque
2
+ module PostgreSQL
3
+ module Reflection
4
+ module ThroughReflection
5
+ delegate :build_id_constraint, :connected_through_array?, to: :source_reflection
6
+ end
7
+
8
+ ::ActiveRecord::Reflection::ThroughReflection.include(ThroughReflection)
9
+ end
10
+ end
11
+ end
@@ -14,9 +14,14 @@ module Torque
14
14
  include Inheritance
15
15
 
16
16
  SINGLE_VALUE_METHODS = [:itself_only]
17
- MULTI_VALUE_METHODS = [:distinct_on, :auxiliary_statements, :cast_records]
17
+ MULTI_VALUE_METHODS = [:distinct_on, :auxiliary_statements, :cast_records, :select_extra]
18
18
  VALUE_METHODS = SINGLE_VALUE_METHODS + MULTI_VALUE_METHODS
19
19
 
20
+ # :nodoc:
21
+ def select_extra_values; get_value(:select_extra); end
22
+ # :nodoc:
23
+ def select_extra_values=(value); set_value(:select_extra, value); end
24
+
20
25
  # Resolve column name when calculating models, allowing the column name to
21
26
  # be more complex while keeping the query selection quality
22
27
  def calculate(operation, column_name)
@@ -41,7 +46,7 @@ module Torque
41
46
  Array.wrap(list).map do |item|
42
47
  case item
43
48
  when String
44
- ::Arel::Nodes::SqlLiteral.new(klass.send(:sanitize_sql, item.to_s))
49
+ ::Arel.sql(klass.send(:sanitize_sql, item.to_s))
45
50
  when Symbol
46
51
  base ? base.arel_attribute(item) : klass.arel_attribute(item)
47
52
  when Array
@@ -72,13 +77,9 @@ module Torque
72
77
 
73
78
  private
74
79
 
75
- def dynamic_selection
76
- @dynamic_selection ||= []
77
- end
78
-
79
80
  def build_arel(*)
80
81
  arel = super
81
- arel.project(*dynamic_selection) if select_values.blank? && dynamic_selection.any?
82
+ arel.project(*select_extra_values) if select_values.blank?
82
83
  arel
83
84
  end
84
85
 
@@ -134,9 +135,9 @@ module Torque
134
135
  warn_level = $VERBOSE
135
136
  $VERBOSE = nil
136
137
 
137
- ActiveRecord::Relation::SINGLE_VALUE_METHODS += Relation::SINGLE_VALUE_METHODS
138
- ActiveRecord::Relation::MULTI_VALUE_METHODS += Relation::MULTI_VALUE_METHODS
139
- ActiveRecord::Relation::VALUE_METHODS += Relation::VALUE_METHODS
138
+ ActiveRecord::Relation::SINGLE_VALUE_METHODS += Relation::SINGLE_VALUE_METHODS
139
+ ActiveRecord::Relation::MULTI_VALUE_METHODS += Relation::MULTI_VALUE_METHODS
140
+ ActiveRecord::Relation::VALUE_METHODS += Relation::VALUE_METHODS
140
141
  ActiveRecord::QueryMethods::VALID_UNSCOPING_VALUES += [:cast_records, :itself_only,
141
142
  :distinct_on, :auxiliary_statements]
142
143
 
@@ -17,11 +17,11 @@ module Torque
17
17
  def with!(*args)
18
18
  options = args.extract_options!
19
19
  args.each do |table|
20
- instance = table.is_a?(PostgreSQL::AuxiliaryStatement) \
21
- ? table.class.new(options) \
20
+ instance = table.is_a?(Class) && table < PostgreSQL::AuxiliaryStatement \
21
+ ? table.new(options) \
22
22
  : PostgreSQL::AuxiliaryStatement.instantiate(table, self, options)
23
- instance.ensure_dependencies!(self)
24
- self.auxiliary_statements_values |= [instance]
23
+
24
+ self.auxiliary_statements_values += [instance]
25
25
  end
26
26
 
27
27
  self
@@ -47,30 +47,23 @@ module Torque
47
47
 
48
48
  # Hook arel build to add the distinct on clause
49
49
  def build_arel(*)
50
- arel = super
51
- build_auxiliary_statements(arel)
52
- arel
50
+ subqueries = build_auxiliary_statements
51
+ return super if subqueries.nil?
52
+ super.with(subqueries)
53
53
  end
54
54
 
55
55
  # Build all necessary data for auxiliary statements
56
- def build_auxiliary_statements(arel)
56
+ def build_auxiliary_statements
57
57
  return unless self.auxiliary_statements_values.present?
58
-
59
- columns = []
60
- subqueries = self.auxiliary_statements_values.map do |klass|
61
- columns << klass.columns
62
- klass.build_arel(arel, self)
58
+ self.auxiliary_statements_values.map do |klass|
59
+ klass.build(self)
63
60
  end
64
-
65
- columns.flatten!
66
- arel.with(subqueries.flatten)
67
- dynamic_selection.concat(columns) if columns.any?
68
61
  end
69
62
 
70
63
  # Throw an error showing that an auxiliary statement of the given
71
64
  # table name isn't defined
72
65
  def auxiliary_statement_error(name)
73
- raise ArgumentError, <<-MSG.strip
66
+ raise ArgumentError, <<-MSG.squish
74
67
  There's no '#{name}' auxiliary statement defined for #{self.class.name}.
75
68
  MSG
76
69
  end
@@ -76,7 +76,7 @@ module Torque
76
76
  end
77
77
 
78
78
  columns.push(build_auto_caster_marker(arel, self.cast_records_value))
79
- dynamic_selection.concat(columns) if columns.any?
79
+ self.select_extra_values += columns if columns.any?
80
80
  end
81
81
 
82
82
  # Build as many left outer join as necessary for each dependent table
@@ -106,7 +106,7 @@ module Torque
106
106
 
107
107
  table = ::Arel::Table.new(type_attribute.camelize.underscore)
108
108
  column = table[type_attribute].in(types)
109
- ::Arel::Nodes::SqlLiteral.new(column.to_sql).as(auto_cast_attribute)
109
+ ::Arel.sql(column.to_sql).as(auto_cast_attribute)
110
110
  end
111
111
 
112
112
  end
@@ -6,7 +6,7 @@ module Torque
6
6
  def merge # :nodoc:
7
7
  super
8
8
 
9
- merge_dynamic_selection
9
+ merge_select_extra
10
10
  merge_distinct_on
11
11
  merge_auxiliary_statements
12
12
  merge_inheritance
@@ -16,9 +16,10 @@ module Torque
16
16
 
17
17
  private
18
18
 
19
- # Merge dynamic selection columns
20
- def merge_dynamic_selection
21
- relation.send(:dynamic_selection).concat(other.send(:dynamic_selection))
19
+ # Merge extra select columns
20
+ def merge_select_extra
21
+ relation.select_extra_values.concat(other.select_extra_values).uniq! \
22
+ if other.select_extra_values.present?
22
23
  end
23
24
 
24
25
  # Merge distinct on columns
@@ -41,9 +42,12 @@ module Torque
41
42
 
42
43
  # Merge settings related to inheritance tables
43
44
  def merge_inheritance
44
- relation.itself_only_value = true if other.itself_only_value
45
- relation.cast_records_value.concat(other.cast_records_value).uniq! \
46
- if other.cast_records_value.present?
45
+ relation.itself_only_value = true if other.itself_only_value.present?
46
+
47
+ if other.cast_records_value.present?
48
+ relation.cast_records_value += other.cast_records_value
49
+ relation.cast_records_value.uniq!
50
+ end
47
51
  end
48
52
 
49
53
  end