activerecord-hierarchical_query 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 031176cf523c47f50c3ca2a10b7c79129766b1e2
4
- data.tar.gz: 9abc865a1f8e2bcbc42f084611b1d588ab7f8879
3
+ metadata.gz: e317315d0b567e0880277e328380478df97fecd7
4
+ data.tar.gz: f1918a2d4c9a9e8a84f59b7af8fec42d6fb57dfa
5
5
  SHA512:
6
- metadata.gz: e5a632144805142246f4ba00cdf938b785660e5bac28063194782efbd446d901fc9154c79c8ee0c039d2d61e7afe4b10a58ff62a84115941653f5943cc137059
7
- data.tar.gz: 7931d4bf31d74b4759ab93d779efdac40ef0cdb10f3b1d583bc9120a48395eeb5c9409ee48790ff626303bf47a4129d924df98c7b2c0613f9b638696e4b5c5ea
6
+ metadata.gz: aad9047098ff944791fd13f5fed4a120e39195d3f9f4d08a17f5e734dfc8e7409e2a034acec7e0200df336c4a530003ebca1d68c2a494fac9629db3d253ace5f
7
+ data.tar.gz: 890c7ebfff9df375347f00c061ed213167fe8e09da1cf567996afc9ca5417f264215c270e987a9e8a16925284d0f9cbafc842c2f26d8c8fd142dfd29761e88d9
data/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
  [![Code Climate](https://codeclimate.com/github/take-five/activerecord-hierarchical_query.png)](https://codeclimate.com/github/take-five/activerecord-hierarchical_query)
5
5
  [![Coverage Status](https://coveralls.io/repos/take-five/activerecord-hierarchical_query/badge.png)](https://coveralls.io/r/take-five/activerecord-hierarchical_query)
6
6
  [![Dependency Status](https://gemnasium.com/take-five/activerecord-hierarchical_query.png)](https://gemnasium.com/take-five/activerecord-hierarchical_query)
7
+ [![Gem Version](https://badge.fury.io/rb/activerecord-hierarchical_query.png)](http://badge.fury.io/rb/activerecord-hierarchical_query)
7
8
 
8
9
  Create hierarchical queries using simple DSL, recursively traverse trees using single SQL query.
9
10
 
@@ -1,20 +1,34 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'active_support/core_ext/hash/keys'
4
+ require 'active_support/core_ext/string/inflections'
5
+
3
6
  module ActiveRecord
4
7
  module HierarchicalQuery
5
8
  module Adapters
6
9
  SUPPORTED_ADAPTERS = %w(PostgreSQL)
7
10
 
8
- autoload :PostgreSQL, 'active_record/hierarchical_query/adapters/postgresql'
11
+ ADAPTERS = Hash[
12
+ :PostgreSQL => :PostgreSQL,
13
+ :PostGIS => :PostgreSQL,
14
+ :OracleEnhanced => :Oracle
15
+ ].stringify_keys
16
+
17
+ def self.autoload(name, path = name.to_s.underscore)
18
+ super name, "active_record/hierarchical_query/adapters/#{path}"
19
+ end
20
+
21
+ autoload :PostgreSQL, 'postgresql'
22
+ autoload :Oracle
9
23
 
10
24
  def self.lookup(klass)
11
25
  name = klass.connection.adapter_name
12
26
 
13
- raise 'Your database does not support recursive queries' unless
14
- SUPPORTED_ADAPTERS.include?(name)
27
+ raise 'Your database %s does not support recursive queries' % name unless
28
+ ADAPTERS.key?(name)
15
29
 
16
- const_get(name)
30
+ const_get(ADAPTERS[name])
17
31
  end
18
32
  end # module Adapters
19
33
  end # module HierarchicalQuery
20
- end # module ActiveRecord
34
+ end # module ActiveRecord
@@ -0,0 +1,41 @@
1
+ # coding: utf-8
2
+
3
+ require 'active_record/hierarchical_query/visitors/visitor'
4
+
5
+ module ActiveRecord
6
+ module HierarchicalQuery
7
+ module Adapters
8
+ # @api private
9
+ class Abstract
10
+ attr_reader :query,
11
+ :table
12
+
13
+ delegate :klass, :to => :query
14
+
15
+ class << self
16
+ def visitors(*visitors_classes)
17
+ define_method :visitors do
18
+ @visitors ||= visitors_classes.map { |klass| klass.new(@query) }
19
+ end
20
+ end
21
+ end
22
+
23
+ # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
24
+ def initialize(query)
25
+ @query = query
26
+ @table = klass.arel_table
27
+ end
28
+
29
+ # @example
30
+ # visit(:recursive_term, recursive_term.arel)
31
+ def visit(kind, object)
32
+ method_name = "visit_#{kind}"
33
+
34
+ visitors.reduce(object) do |*, visitor|
35
+ visitor.send(method_name, object) if visitor.respond_to?(method_name)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,28 +1,18 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'active_record/hierarchical_query/cte/query'
3
+ require 'arel/nodes/postgresql'
4
+
5
+ require 'active_record/hierarchical_query/adapters/abstract'
6
+ require 'active_record/hierarchical_query/visitors/postgresql/cycle_detector'
7
+ require 'active_record/hierarchical_query/visitors/postgresql/orderings'
4
8
 
5
9
  module ActiveRecord
6
10
  module HierarchicalQuery
7
11
  module Adapters
8
12
  # @api private
9
- class PostgreSQL
10
- attr_reader :builder,
11
- :table
12
-
13
- delegate :klass, :to => :builder
14
- delegate :build_join, :to => :@query
15
-
16
- # @param [ActiveRecord::HierarchicalQuery::Builder] builder
17
- def initialize(builder)
18
- @builder = builder
19
- @table = klass.arel_table
20
- @query = CTE::Query.new(builder)
21
- end
22
-
23
- def prior
24
- @query.recursive_table
25
- end
13
+ class PostgreSQL < Abstract
14
+ visitors Visitors::PostgreSQL::CycleDetector,
15
+ Visitors::PostgreSQL::Orderings
26
16
  end # class PostgreSQL
27
17
  end # module Adapters
28
18
  end # module HierarchicalQuery
@@ -2,7 +2,8 @@
2
2
 
3
3
  require 'active_support/core_ext/array/extract_options'
4
4
 
5
- require 'active_record/hierarchical_query/adapters'
5
+ require 'active_record/hierarchical_query/cte/query'
6
+ require 'active_record/hierarchical_query/join_builder'
6
7
 
7
8
  module ActiveRecord
8
9
  module HierarchicalQuery
@@ -22,7 +23,7 @@ module ActiveRecord
22
23
 
23
24
  def initialize(klass)
24
25
  @klass = klass
25
- @adapter = Adapters.lookup(@klass).new(self)
26
+ @query = CTE::Query.new(self)
26
27
 
27
28
  @start_with_value = nil
28
29
  @connect_by_value = nil
@@ -239,7 +240,7 @@ module ActiveRecord
239
240
  #
240
241
  # @return [Arel::Table]
241
242
  def prior
242
- @adapter.prior
243
+ @query.recursive_table
243
244
  end
244
245
  alias_method :previous, :prior
245
246
 
@@ -269,7 +270,7 @@ module ActiveRecord
269
270
 
270
271
  table_alias = join_options.fetch(:as, "#{table.name}__recursive")
271
272
 
272
- @adapter.build_join(relation, table_alias)
273
+ JoinBuilder.new(@query, relation, table_alias).build
273
274
  end
274
275
 
275
276
  private
@@ -1,15 +1,13 @@
1
1
  # coding: utf-8
2
2
 
3
- require 'arel/nodes/postgresql'
4
-
5
3
  module ActiveRecord
6
4
  module HierarchicalQuery
7
5
  module CTE
8
6
  class NonRecursiveTerm
9
- DISALLOWED_CLAUSES = :order, :limit, :offset
7
+ DISALLOWED_CLAUSES = :order, :limit, :offset, :group, :having
10
8
 
11
9
  attr_reader :query
12
- delegate :builder, :orderings, :cycle_detector, :to => :query
10
+ delegate :builder, :adapter, :to => :query
13
11
  delegate :start_with_value, :klass, :to => :builder
14
12
 
15
13
  # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
@@ -19,25 +17,16 @@ module ActiveRecord
19
17
 
20
18
  def arel
21
19
  arel = scope.select(query.columns)
22
- .select(ordering_column)
23
20
  .except(*DISALLOWED_CLAUSES)
24
21
  .arel
25
22
 
26
- cycle_detector.visit_non_recursive(arel)
23
+ adapter.visit(:non_recursive, arel)
27
24
  end
28
25
 
29
26
  private
30
27
  def scope
31
28
  start_with_value || klass.__send__(HierarchicalQuery::DELEGATOR_SCOPE)
32
29
  end
33
-
34
- def ordering_column
35
- if orderings.any?
36
- Arel::Nodes::PostgresArray.new([orderings.row_number_expression]).as(orderings.column_name)
37
- else
38
- []
39
- end
40
- end
41
30
  end # class NonRecursiveTerm
42
31
  end
43
32
  end
@@ -1,37 +1,30 @@
1
1
  # coding: utf-8
2
2
 
3
+ require 'active_record/hierarchical_query/adapters'
3
4
  require 'active_record/hierarchical_query/cte/columns'
4
- require 'active_record/hierarchical_query/cte/cycle_detector'
5
- require 'active_record/hierarchical_query/cte/join_builder'
6
- require 'active_record/hierarchical_query/cte/orderings'
7
5
  require 'active_record/hierarchical_query/cte/union_term'
8
6
 
9
7
  module ActiveRecord
10
8
  module HierarchicalQuery
11
9
  module CTE
10
+ # CTE query builder
12
11
  class Query
13
12
  attr_reader :builder,
14
- :columns,
15
- :orderings,
16
- :cycle_detector
13
+ :adapter,
14
+ :columns
17
15
 
18
16
  delegate :klass, :table, :to => :builder
19
17
 
20
18
  # @param [ActiveRecord::HierarchicalQuery::Builder] builder
21
19
  def initialize(builder)
22
20
  @builder = builder
23
- @orderings = Orderings.new(builder)
21
+ @adapter = Adapters.lookup(klass).new(self)
24
22
  @columns = Columns.new(self)
25
- @cycle_detector = CycleDetector.new(self)
26
- end
27
-
28
- def build_join(relation, subquery_alias)
29
- JoinBuilder.new(self, relation, subquery_alias).build
30
23
  end
31
24
 
32
25
  # @return [Arel::SelectManager]
33
26
  def arel
34
- apply_ordering { build_arel }
27
+ adapter.visit(:cte, build_arel)
35
28
  end
36
29
 
37
30
  # @return [Arel::Table]
@@ -43,10 +36,6 @@ module ActiveRecord
43
36
  builder.connect_by_value[recursive_table, table]
44
37
  end
45
38
 
46
- def order_clause
47
- recursive_table[orderings.column_name].asc
48
- end
49
-
50
39
  private
51
40
  # "categories__recursive" AS (
52
41
  # SELECT ... FROM "categories"
@@ -62,16 +51,6 @@ module ActiveRecord
62
51
  UnionTerm.new(self)
63
52
  end
64
53
 
65
- def apply_ordering
66
- arel = yield
67
-
68
- if should_order?
69
- apply_ordering_to(arel)
70
- else
71
- arel
72
- end
73
- end
74
-
75
54
  def build_arel
76
55
  Arel::SelectManager.new(table.engine).
77
56
  with(:recursive, with_query).
@@ -80,14 +59,6 @@ module ActiveRecord
80
59
  take(builder.limit_value).
81
60
  skip(builder.offset_value)
82
61
  end
83
-
84
- def should_order?
85
- orderings.any? && builder.limit_value || builder.offset_value
86
- end
87
-
88
- def apply_ordering_to(select_manager)
89
- select_manager.order(order_clause)
90
- end
91
62
  end
92
63
  end
93
64
  end
@@ -5,10 +5,9 @@ module ActiveRecord
5
5
  # @return [ActiveRecord::HierarchicalQuery::CTE::Query]
6
6
  attr_reader :query
7
7
 
8
- delegate :orderings,
9
- :cycle_detector,
10
- :recursive_table,
8
+ delegate :recursive_table,
11
9
  :join_conditions,
10
+ :adapter,
12
11
  :to => :query
13
12
 
14
13
  # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
@@ -18,29 +17,16 @@ module ActiveRecord
18
17
 
19
18
  def arel
20
19
  arel = scope.select(query.columns)
21
- .select(ordering_column)
22
20
  .arel
23
21
  .join(recursive_table).on(join_conditions)
24
22
 
25
- cycle_detector.visit_recursive(arel)
23
+ adapter.visit(:recursive, arel)
26
24
  end
27
25
 
28
26
  private
29
27
  def scope
30
28
  query.builder.child_scope_value
31
29
  end
32
-
33
- def ordering_column
34
- if orderings.any?
35
- Arel::Nodes::ArrayConcat.new(parent_ordering_column, orderings.row_number_expression)
36
- else
37
- []
38
- end
39
- end
40
-
41
- def parent_ordering_column
42
- recursive_table[orderings.column_name]
43
- end
44
30
  end
45
31
  end
46
32
  end
@@ -0,0 +1,47 @@
1
+ module ActiveRecord
2
+ module HierarchicalQuery
3
+ class JoinBuilder
4
+ delegate :adapter, :to => :@query
5
+
6
+ # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
7
+ # @param [ActiveRecord::Relation] join_to
8
+ # @param [#to_s] subquery_alias
9
+ def initialize(query, join_to, subquery_alias)
10
+ @query = query
11
+ @relation = join_to
12
+ @alias = Arel::Table.new(subquery_alias, ActiveRecord::Base)
13
+ end
14
+
15
+ def build
16
+ relation = @relation.joins(inner_join.to_sql)
17
+
18
+ adapter.visit(:joined_relation, relation)
19
+ end
20
+
21
+ private
22
+ def inner_join
23
+ Arel::Nodes::InnerJoin.new(aliased_subquery, constraint)
24
+ end
25
+
26
+ def primary_key
27
+ @relation.table[@relation.klass.primary_key]
28
+ end
29
+
30
+ def foreign_key
31
+ @alias[@query.klass.primary_key]
32
+ end
33
+
34
+ def constraint
35
+ Arel::Nodes::On.new(primary_key.eq(foreign_key))
36
+ end
37
+
38
+ def subquery
39
+ Arel::Nodes::Grouping.new(@query.arel.ast)
40
+ end
41
+
42
+ def aliased_subquery
43
+ Arel::Nodes::As.new(subquery, @alias)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module HierarchicalQuery
3
- VERSION = '0.0.1'
3
+ VERSION = '0.0.2'
4
4
  end
5
5
  end
@@ -0,0 +1,87 @@
1
+ module ActiveRecord
2
+ module HierarchicalQuery
3
+ module Visitors
4
+ class Orderings < Visitor
5
+ ORDERING_COLUMN_ALIAS = '__order_column'
6
+
7
+ delegate :recursive_table, :to => :query
8
+
9
+ def visit_joined_relation(relation)
10
+ visit(relation) do
11
+ relation.order(order_clause)
12
+ end
13
+ end
14
+
15
+ def visit_cte(select_manager)
16
+ if should_order_cte?
17
+ select_manager.order(order_clause)
18
+ else
19
+ select_manager
20
+ end
21
+ end
22
+
23
+ protected
24
+ def visit(object)
25
+ if orderings.any?
26
+ yield
27
+ else
28
+ object
29
+ end
30
+ end
31
+
32
+ def order_clause
33
+ recursive_table[column_name].asc
34
+ end
35
+
36
+ def should_order_cte?
37
+ orderings.any? && builder.limit_value || builder.offset_value
38
+ end
39
+
40
+ def orderings
41
+ @orderings ||= builder.order_values.each_with_object([]) do |value, orderings|
42
+ orderings.concat Array.wrap(as_orderings(value))
43
+ end
44
+ end
45
+
46
+ def column_name
47
+ ORDERING_COLUMN_ALIAS
48
+ end
49
+
50
+ def as_orderings(value)
51
+ case value
52
+ when Arel::Nodes::Ordering
53
+ value
54
+
55
+ when Arel::Nodes::Node, Arel::Attributes::Attribute
56
+ value.asc
57
+
58
+ when Symbol
59
+ table[value].asc
60
+
61
+ when Hash
62
+ value.map { |field, dir| table[field].send(dir) }
63
+
64
+ when String
65
+ value.split(',').map do |expr|
66
+ string_as_ordering(expr)
67
+ end
68
+
69
+ else
70
+ raise 'Unknown expression in ORDER BY SIBLINGS clause'
71
+ end
72
+ end
73
+
74
+ def string_as_ordering(expr)
75
+ expr.strip!
76
+
77
+ if expr.gsub!(/\bdesc\z/i, '')
78
+ Arel.sql(expr).desc
79
+ else
80
+ expr.gsub!(/\basc\z/i, '')
81
+ Arel.sql(expr).asc
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,49 @@
1
+ module ActiveRecord
2
+ module HierarchicalQuery
3
+ module Visitors
4
+ module PostgreSQL
5
+ class CycleDetector < Visitor
6
+ COLUMN_NAME = '__path'.freeze
7
+
8
+ def visit_non_recursive(arel)
9
+ if enabled?
10
+ arel.project Arel::Nodes::PostgresArray.new([primary_key]).as(column_name)
11
+ end
12
+
13
+ arel
14
+ end
15
+
16
+ def visit_recursive(arel)
17
+ if enabled?
18
+ arel.project Arel::Nodes::ArrayConcat.new(parent_column, primary_key)
19
+ arel.constraints << Arel::Nodes::Not.new(primary_key.eq(any(parent_column)))
20
+ end
21
+
22
+ arel
23
+ end
24
+
25
+ private
26
+ def enabled?
27
+ builder.nocycle_value
28
+ end
29
+
30
+ def column_name
31
+ COLUMN_NAME
32
+ end
33
+
34
+ def parent_column
35
+ query.recursive_table[column_name]
36
+ end
37
+
38
+ def primary_key
39
+ table[klass.primary_key]
40
+ end
41
+
42
+ def any(argument)
43
+ Arel::Nodes::NamedFunction.new('ANY', [argument])
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,70 @@
1
+ require 'active_record/hierarchical_query/visitors/orderings'
2
+
3
+ module ActiveRecord
4
+ module HierarchicalQuery
5
+ module Visitors
6
+ module PostgreSQL
7
+ class Orderings < Visitors::Orderings
8
+ NATURAL_SORT_TYPES = Set[
9
+ :integer, :float, :decimal,
10
+ :datetime, :timestamp, :time, :date,
11
+ :boolean, :itet, :cidr, :ltree
12
+ ]
13
+
14
+ delegate :first, :to => :orderings
15
+
16
+ def visit_non_recursive(arel)
17
+ project(arel) do
18
+ Arel::Nodes::PostgresArray.new([row_number_expression]).as(column_name)
19
+ end
20
+ end
21
+
22
+ def visit_recursive(arel)
23
+ project(arel) do
24
+ Arel::Nodes::ArrayConcat.new(recursive_table[column_name], row_number_expression)
25
+ end
26
+ end
27
+
28
+ private
29
+ def project(arel)
30
+ visit(arel) { arel.project(yield) }
31
+ end
32
+
33
+ def row_number_expression
34
+ if raw_ordering?
35
+ order_attribute
36
+ else
37
+ Arel.sql("ROW_NUMBER() OVER (ORDER BY #{orderings.map(&:to_sql).join(', ')})")
38
+ end
39
+ end
40
+
41
+ def raw_ordering?
42
+ ordered_by_attribute? &&
43
+ (column = order_column) &&
44
+ NATURAL_SORT_TYPES.include?(column.type)
45
+ end
46
+
47
+ def ordered_by_attribute?
48
+ orderings.one? && first.ascending? && order_attribute.is_a?(Arel::Attributes::Attribute)
49
+ end
50
+
51
+ def order_attribute
52
+ first.expr
53
+ end
54
+
55
+ def order_column
56
+ table = order_attribute.relation
57
+
58
+ if table.engine == ActiveRecord::Base
59
+ columns = table.engine.connection_pool.columns_hash[table.name]
60
+ else
61
+ columns = table.engine.columns_hash
62
+ end
63
+
64
+ columns[order_attribute.name.to_s]
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module HierarchicalQuery
3
+ module Visitors
4
+ # @api private
5
+ class Visitor
6
+ attr_reader :query
7
+
8
+ delegate :builder, :to => :query
9
+ delegate :klass, :table, :to => :builder
10
+
11
+ def initialize(query)
12
+ @query = query
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,4 +1,5 @@
1
- adapter: postgresql
2
- database: hierarchical_query_test
3
- username: postgres
4
- min_messages: ERROR
1
+ pg:
2
+ adapter: postgresql
3
+ database: hierarchical_query_test
4
+ username: postgres
5
+ min_messages: ERROR
data/spec/database.yml CHANGED
@@ -1,4 +1,13 @@
1
- adapter: postgresql
2
- database: hierarchical_query_test
3
- username: vagrant
4
- min_messages: ERROR
1
+ pg:
2
+ adapter: postgresql
3
+ database: hierarchical_query_test
4
+ username: vagrant
5
+ min_messages: ERROR
6
+
7
+ oracle:
8
+ adapter: oracle_enhanced
9
+ host: localhost
10
+ port: 1521
11
+ database: xe
12
+ username: system
13
+ password: manager
data/spec/spec_helper.rb CHANGED
@@ -2,17 +2,20 @@
2
2
  require 'pathname'
3
3
  require 'logger'
4
4
 
5
- SPEC_ROOT = Pathname.new(File.dirname(__FILE__))
5
+ ENV['TZ'] = 'UTC'
6
+ ENV['DB'] ||= 'pg'
6
7
 
7
- require 'bundler/setup'
8
+ SPEC_ROOT = Pathname.new(File.dirname(__FILE__))
8
9
 
9
- Bundler.setup(:default, ENV['TRAVIS'] ? :travis : :local)
10
+ require 'bundler'
11
+ Bundler.setup(:default, ENV['TRAVIS'] ? :travis : :local, ENV['DB'].to_sym)
10
12
 
11
13
  require 'rspec'
12
14
  require 'database_cleaner'
13
15
  require 'active_record'
14
16
 
15
- ActiveRecord::Base.establish_connection(YAML.load(SPEC_ROOT.join('database.yml').read))
17
+ ActiveRecord::Base.configurations = YAML.load(SPEC_ROOT.join('database.yml').read)
18
+ ActiveRecord::Base.establish_connection(ENV['DB'].to_sym)
16
19
  ActiveRecord::Base.logger = Logger.new(ENV['DEBUG'] ? $stderr : '/dev/null')
17
20
  ActiveRecord::Base.logger.formatter = proc do |severity, datetime, progname, msg|
18
21
  "#{datetime.strftime('%H:%M:%S.%L')}: #{msg}\n"
@@ -35,11 +38,6 @@ RSpec.configure do |config|
35
38
  # --seed 1234
36
39
  config.order = 'random'
37
40
 
38
- config.before(:suite) do
39
- DatabaseCleaner.strategy = :transaction
40
- DatabaseCleaner.clean_with(:truncation)
41
- end
42
-
43
41
  config.around(:each) do |example|
44
42
  DatabaseCleaner.start
45
43
  example.run
metadata CHANGED
@@ -1,111 +1,97 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-hierarchical_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexei Mikhailov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-11 00:00:00.000000000 Z
11
+ date: 2014-05-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 3.1.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 3.1.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.5'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.5'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: 10.1.1
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: 10.1.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ~>
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
61
  version: 2.14.1
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ~>
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 2.14.1
69
- - !ruby/object:Gem::Dependency
70
- name: pg
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ~>
74
- - !ruby/object:Gem::Version
75
- version: 0.17.1
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ~>
81
- - !ruby/object:Gem::Version
82
- version: 0.17.1
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: database_cleaner
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
- - - ~>
73
+ - - "~>"
88
74
  - !ruby/object:Gem::Version
89
75
  version: 1.2.0
90
76
  type: :development
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
- - - ~>
80
+ - - "~>"
95
81
  - !ruby/object:Gem::Version
96
82
  version: 1.2.0
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: simplecov
99
85
  requirement: !ruby/object:Gem::Requirement
100
86
  requirements:
101
- - - ~>
87
+ - - "~>"
102
88
  - !ruby/object:Gem::Version
103
89
  version: 0.8.2
104
90
  type: :development
105
91
  prerelease: false
106
92
  version_requirements: !ruby/object:Gem::Requirement
107
93
  requirements:
108
- - - ~>
94
+ - - "~>"
109
95
  - !ruby/object:Gem::Version
110
96
  version: 0.8.2
111
97
  description:
@@ -115,19 +101,24 @@ executables: []
115
101
  extensions: []
116
102
  extra_rdoc_files: []
117
103
  files:
104
+ - LICENSE.txt
105
+ - README.md
118
106
  - lib/active_record/hierarchical_query.rb
119
107
  - lib/active_record/hierarchical_query/adapters.rb
108
+ - lib/active_record/hierarchical_query/adapters/abstract.rb
120
109
  - lib/active_record/hierarchical_query/adapters/postgresql.rb
121
110
  - lib/active_record/hierarchical_query/builder.rb
122
111
  - lib/active_record/hierarchical_query/cte/columns.rb
123
- - lib/active_record/hierarchical_query/cte/cycle_detector.rb
124
- - lib/active_record/hierarchical_query/cte/join_builder.rb
125
112
  - lib/active_record/hierarchical_query/cte/non_recursive_term.rb
126
- - lib/active_record/hierarchical_query/cte/orderings.rb
127
113
  - lib/active_record/hierarchical_query/cte/query.rb
128
114
  - lib/active_record/hierarchical_query/cte/recursive_term.rb
129
115
  - lib/active_record/hierarchical_query/cte/union_term.rb
116
+ - lib/active_record/hierarchical_query/join_builder.rb
130
117
  - lib/active_record/hierarchical_query/version.rb
118
+ - lib/active_record/hierarchical_query/visitors/orderings.rb
119
+ - lib/active_record/hierarchical_query/visitors/postgresql/cycle_detector.rb
120
+ - lib/active_record/hierarchical_query/visitors/postgresql/orderings.rb
121
+ - lib/active_record/hierarchical_query/visitors/visitor.rb
131
122
  - lib/arel/nodes/postgresql.rb
132
123
  - spec/active_record/hierarchical_query_spec.rb
133
124
  - spec/database.travis.yml
@@ -135,8 +126,6 @@ files:
135
126
  - spec/schema.rb
136
127
  - spec/spec_helper.rb
137
128
  - spec/support/models.rb
138
- - README.md
139
- - LICENSE.txt
140
129
  homepage: https://github.com/take-five/activerecord-hierarchical_query
141
130
  licenses:
142
131
  - MIT
@@ -147,17 +136,17 @@ require_paths:
147
136
  - lib
148
137
  required_ruby_version: !ruby/object:Gem::Requirement
149
138
  requirements:
150
- - - '>='
139
+ - - ">="
151
140
  - !ruby/object:Gem::Version
152
141
  version: '0'
153
142
  required_rubygems_version: !ruby/object:Gem::Requirement
154
143
  requirements:
155
- - - '>='
144
+ - - ">="
156
145
  - !ruby/object:Gem::Version
157
146
  version: '0'
158
147
  requirements: []
159
148
  rubyforge_project:
160
- rubygems_version: 2.1.10
149
+ rubygems_version: 2.2.2
161
150
  signing_key:
162
151
  specification_version: 4
163
152
  summary: Recursively traverse trees using a single SQL query
@@ -1,63 +0,0 @@
1
- # coding: utf-8
2
-
3
- module ActiveRecord
4
- module HierarchicalQuery
5
- module CTE
6
- class CycleDetector
7
- COLUMN_NAME = '__path'.freeze
8
-
9
- attr_reader :query
10
-
11
- delegate :builder, :to => :query
12
- delegate :klass, :table, :to => :builder
13
-
14
- # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
15
- def initialize(query)
16
- @query = query
17
- end
18
-
19
- def column_name
20
- COLUMN_NAME
21
- end
22
-
23
- def visit_non_recursive(arel)
24
- visit arel do
25
- arel.project Arel::Nodes::PostgresArray.new([primary_key]).as(column_name)
26
- end
27
- end
28
-
29
- def visit_recursive(arel)
30
- visit arel do
31
- arel.project Arel::Nodes::ArrayConcat.new(parent_column, primary_key)
32
- arel.constraints << Arel::Nodes::Not.new(primary_key.eq(any(parent_column)))
33
- end
34
- end
35
-
36
- private
37
- def enabled?
38
- builder.nocycle_value
39
- end
40
-
41
- def parent_column
42
- query.recursive_table[column_name]
43
- end
44
-
45
- def primary_key
46
- table[klass.primary_key]
47
- end
48
-
49
- def visit(object)
50
- if enabled?
51
- yield
52
- end
53
-
54
- object
55
- end
56
-
57
- def any(argument)
58
- Arel::Nodes::NamedFunction.new('ANY', [argument])
59
- end
60
- end
61
- end
62
- end
63
- end
@@ -1,55 +0,0 @@
1
- module ActiveRecord
2
- module HierarchicalQuery
3
- module CTE
4
- class JoinBuilder
5
- # @param [ActiveRecord::HierarchicalQuery::CTE::Query] query
6
- # @param [ActiveRecord::Relation] join_to
7
- # @param [#to_s] subquery_alias
8
- def initialize(query, join_to, subquery_alias)
9
- @query = query
10
- @relation = join_to
11
- @alias = Arel::Table.new(subquery_alias, ActiveRecord::Base)
12
- end
13
-
14
- def build
15
- apply_ordering { @relation.joins(inner_join.to_sql) }
16
- end
17
-
18
- private
19
- def inner_join
20
- Arel::Nodes::InnerJoin.new(aliased_subquery, constraint)
21
- end
22
-
23
- def primary_key
24
- @relation.table[@relation.klass.primary_key]
25
- end
26
-
27
- def foreign_key
28
- @alias[@query.klass.primary_key]
29
- end
30
-
31
- def constraint
32
- Arel::Nodes::On.new(primary_key.eq(foreign_key))
33
- end
34
-
35
- def subquery
36
- Arel::Nodes::Grouping.new(@query.arel.ast)
37
- end
38
-
39
- def aliased_subquery
40
- Arel::Nodes::As.new(subquery, @alias)
41
- end
42
-
43
- def apply_ordering
44
- scope = yield
45
-
46
- if @query.orderings.any?
47
- scope.order(@query.order_clause)
48
- else
49
- scope
50
- end
51
- end
52
- end
53
- end
54
- end
55
- end
@@ -1,108 +0,0 @@
1
- require 'set'
2
- require 'active_support/core_ext/array/wrap'
3
-
4
- module ActiveRecord
5
- module HierarchicalQuery
6
- module CTE
7
- class Orderings
8
- include Enumerable
9
-
10
- ORDERING_COLUMN_ALIAS = '__order_column'
11
-
12
- NATURAL_SORT_TYPES = Set[
13
- :integer, :float, :decimal,
14
- :datetime, :timestamp, :time, :date,
15
- :boolean, :itet, :cidr, :ltree
16
- ]
17
-
18
- delegate :table, :to => :@builder
19
- delegate :each, :to => :arel_nodes
20
-
21
- # @param [ActiveRecord::HierarchicalQuery::Builder] builder
22
- def initialize(builder)
23
- @builder = builder
24
- end
25
-
26
- def arel_nodes
27
- @arel_nodes ||= @builder.order_values.each_with_object([]) do |value, orderings|
28
- orderings.concat Array.wrap(as_orderings(value))
29
- end
30
- end
31
-
32
- def row_number_expression
33
- if raw_ordering?
34
- order_attribute
35
- else
36
- Arel.sql("ROW_NUMBER() OVER (ORDER BY #{arel_nodes.map(&:to_sql).join(', ')})")
37
- end
38
- end
39
-
40
- def column_name
41
- ORDERING_COLUMN_ALIAS
42
- end
43
-
44
- private
45
- def as_orderings(value)
46
- case value
47
- when Arel::Nodes::Ordering
48
- value
49
-
50
- when Arel::Nodes::Node, Arel::Attributes::Attribute
51
- value.asc
52
-
53
- when Symbol
54
- table[value].asc
55
-
56
- when Hash
57
- value.map { |field, dir| table[field].send(dir) }
58
-
59
- when String
60
- value.split(',').map do |expr|
61
- string_as_ordering(expr)
62
- end
63
-
64
- else
65
- raise 'Unknown expression in ORDER BY SIBLINGS clause'
66
- end
67
- end
68
-
69
- def string_as_ordering(expr)
70
- expr.strip!
71
-
72
- if expr.gsub!(/\s+desc\z/i, '')
73
- Arel.sql(expr).desc
74
- else
75
- expr.gsub!(/\s+asc\z/i, '')
76
- Arel.sql(expr).asc
77
- end
78
- end
79
-
80
- def raw_ordering?
81
- ordered_by_attribute? &&
82
- (column = order_column) &&
83
- NATURAL_SORT_TYPES.include?(column.type)
84
- end
85
-
86
- def ordered_by_attribute?
87
- arel_nodes.one? && first.ascending? && order_attribute.is_a?(Arel::Attributes::Attribute)
88
- end
89
-
90
- def order_attribute
91
- first.expr
92
- end
93
-
94
- def order_column
95
- table = order_attribute.relation
96
-
97
- if table.engine == ActiveRecord::Base
98
- columns = table.engine.connection_pool.columns_hash[table.name]
99
- else
100
- columns = table.engine.columns_hash
101
- end
102
-
103
- columns[order_attribute.name.to_s]
104
- end
105
- end # class Orderings
106
- end
107
- end
108
- end