to_wa 0.2.0 → 0.3.0

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: ff02d9d70a9c571bba011ff3f651e559498cd6cd
4
- data.tar.gz: 92bba9fd29b6adfd9035a16e512729bc54b6b092
3
+ metadata.gz: 64fb0e5b6b7555f477aad99eeedfe5d18d083eda
4
+ data.tar.gz: a0647318b3e575773c68193c4c747a67280509fe
5
5
  SHA512:
6
- metadata.gz: 75b7af23c3c6cf5cfb46ebd4533c9bb9bec6705e7b480b059071125bb2993fc3e7b017f1a25e5d2446ce52dd74b5916fe4bbabd3444b7f5e0eb2d2e9b17473ad
7
- data.tar.gz: bea440ef0ff39602c6f265b8046ef359323f876b6b6b12a76beaabe5b8ae055953ca6b7306eca6cb27aa795845a60d6eae404de339bab05979a65e7f70fe8422
6
+ metadata.gz: a1648c143778d42f291f87aa8b2d56b5f9e8001a9fa564ad6e41422c2d15e4643ef8971398d68571e5234874ce9c817e2f25b2b84aa8ce26c9d01b1a93cabd26
7
+ data.tar.gz: d02ec68c2568671103d721ce2fb291660b569ea4fde6bd71f11d08d6495e063811543b0a3275b7e4a339bdef57f95f785606163820796373121258d969a995f1
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- to_wa (0.2.0)
4
+ to_wa (0.3.0)
5
5
  activerecord
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -7,16 +7,38 @@
7
7
  # Installation
8
8
 
9
9
  ```ruby
10
- gem 'ToWa'
10
+ gem 'to_wa'
11
11
  ```
12
12
 
13
13
  ```console
14
14
  bundle install
15
15
  ```
16
16
 
17
+ ## Use all columns and operators
18
+
19
+ ```ruby
20
+ class TestRecord < ActiveRecord::Base
21
+ extend ToWa
22
+
23
+ permit_all_to_wa_operators!
24
+ permit_all_to_wa_columns!
25
+
26
+ # read section: Use specific table columns as left and right
27
+ permit_all_to_wa_specified_columns!
28
+ end
29
+ ```
30
+
31
+ ## Or specify columns and operators
32
+
17
33
  ```ruby
18
34
  class TestRecord < ActiveRecord::Base
19
35
  extend ToWa
36
+
37
+ permit_to_wa_columns :a, :b, :x
38
+ permit_to_wa_operators :eq, :ne
39
+
40
+ # read section: Use specific table columns as left and right
41
+ permit_to_wa_specified_columns foo_records: [:a], bar_records: [:b]
20
42
  end
21
43
  ```
22
44
 
@@ -96,7 +118,6 @@ and|and|
96
118
  or|or|
97
119
  not|not|It must receive Array that includes only one data. `{ "not": [{ "name": "ToWa" }] }`
98
120
 
99
-
100
121
  # Usage
101
122
 
102
123
  (Ofcourse, `ActiveRecord::Relation` will be provided after `to_wa` without `to_sql`.)
@@ -135,6 +156,28 @@ TestRecord.to_wa(
135
156
  #=> "SELECT `test_records`.* FROM `test_records` WHERE ((`test_records`.`name` = 'ToWa' OR `test_records`.`name` = 'to_wa') AND `test_records`.`gender` = 'male')"
136
157
  ```
137
158
 
159
+ ## Use specific table columns as left and right
160
+
161
+ ```ruby
162
+ class TestRecord < ActiveRecord::Base
163
+ extend ToWa
164
+ has_many :users
165
+ end
166
+
167
+ class User < ActiveRecord::Base
168
+ end
169
+
170
+ TestRecord.left_outer_joins(:users).to_wa(
171
+ {
172
+ '>': [
173
+ { 'col': ['users', 'left_arm_length'] },
174
+ { 'col': ['users', 'right_arm_length'] }
175
+ ]
176
+ }
177
+ ).to_sql
178
+ #=> "SELECT `test_records`.* FROM `test_records` LEFT OUTER JOIN `users` ON `users`.`test_record_id` = `test_records`.`id` WHERE (`users`.`left_arm_length` > `users`.`right_arm_length`)"
179
+ ```
180
+
138
181
  ## Working with other query
139
182
 
140
183
  ```ruby
@@ -153,4 +196,4 @@ a.to_sql
153
196
  #=> "`test_records`.`name` = 'ToWa'"
154
197
  TestRecord.where(a).to_sql
155
198
  #=> "SELECT `test_records`.* FROM `test_records` WHERE `test_records`.`name` = 'ToWa'"
156
- ```
199
+ ```
@@ -1,6 +1,12 @@
1
1
  class ToWaTestMigration < ActiveRecord::Migration[5.1]
2
2
  class << self
3
3
  def up
4
+ create_table(:users) do |t|
5
+ t.integer :left_arm_length
6
+ t.integer :right_arm_length
7
+ t.references :test_record
8
+ end
9
+
4
10
  create_table(:test_records) do |t|
5
11
  t.string :a
6
12
  t.string :b
@@ -8,15 +14,23 @@ class ToWaTestMigration < ActiveRecord::Migration[5.1]
8
14
  t.integer :x
9
15
  t.integer :y
10
16
  t.integer :z
17
+ t.string :denied_column
11
18
  end
12
19
  rescue
13
20
  nil
14
21
  end
15
22
 
16
23
  def down
17
- drop_table(:test_records)
18
- rescue
19
- nil
24
+ begin
25
+ drop_table(:users)
26
+ rescue
27
+ nil
28
+ end
29
+ begin
30
+ drop_table(:test_records)
31
+ rescue
32
+ nil
33
+ end
20
34
  end
21
35
  end
22
36
  end
@@ -11,4 +11,5 @@ rescue ActiveRecord::StatementInvalid => e
11
11
  end
12
12
 
13
13
  ActiveRecord::Base.establish_connection(ToWaTestConfiguration::FULL_SET)
14
+ ToWaTestMigration.down
14
15
  ToWaTestMigration.up
@@ -1,21 +1,29 @@
1
1
  require 'to_wa/version'
2
- require 'active_record'
2
+ require 'to_wa/exceptions'
3
+ require 'to_wa/configuration'
4
+ require 'to_wa/easy_hash_access'
5
+ require 'to_wa/builder'
6
+ require 'to_wa/core'
3
7
 
4
8
  module ToWa
5
- require 'to_wa/builder'
6
- require 'to_wa/core'
9
+ include ::ToWa::Configuration
7
10
 
8
- def to_wa(o)
9
- ::ToWa::Core.to_wa(self, o)
10
- end
11
-
12
- def to_wa_raw(arel_table, o)
13
- ToWa(arel_table, o)
11
+ def to_wa(ex)
12
+ where(
13
+ ::ToWa::Builder.new(
14
+ arel_table: arel_table,
15
+ restricted: true,
16
+ permitted_columns: permitted_to_wa_columns,
17
+ permitted_operators: permitted_to_wa_operators,
18
+ permitted_specified_columns: permitted_to_wa_specified_columns,
19
+ ex: ex,
20
+ ).execute!,
21
+ )
14
22
  end
15
23
  end
16
24
 
17
25
  module Kernel
18
- def ToWa(arel_table, o) # rubocop:disable Naming/MethodName
19
- ::ToWa::Core.to_wa_raw(arel_table, o)
26
+ def ToWa(arel_table, ex) # rubocop:disable Naming/MethodName
27
+ ::ToWa::Builder.new(restricted: false, arel_table: arel_table, ex: ex).execute!
20
28
  end
21
29
  end
@@ -1,56 +1,134 @@
1
1
  require 'json'
2
+ require 'active_record'
2
3
 
4
+ # rubocop:disable Metrics/ClassLength
3
5
  module ToWa
4
- module Builder
6
+ class Builder
7
+ using ::ToWa::EasyHashAccess
5
8
  include ActiveRecord::Sanitization
6
9
 
7
- def to_wa(ar, ex)
8
- ar.where(to_wa_raw(ar.arel_table, ex))
10
+ # rubocop:disable Metrics/ParameterLists
11
+ def initialize(
12
+ restricted:,
13
+ ex:,
14
+ arel_table:,
15
+ permitted_columns: Set.new,
16
+ permitted_operators: Set.new,
17
+ permitted_specified_columns: {}
18
+ )
19
+ @restricted = restricted
20
+ @arel_table = arel_table
21
+ @initial_ex = ex.is_a?(String) ? JSON.parse(ex) : ex
22
+ @permitted_columns = permitted_columns
23
+ @permitted_operators = permitted_operators
24
+ @permitted_specified_columns = permitted_specified_columns
9
25
  end
26
+ # rubocop:enable Metrics/ParameterLists
10
27
 
11
- def to_wa_raw(arel_table = nil, ex)
12
- decide(arel_table, ex.is_a?(String) ? JSON.parse(ex) : ex)
28
+ def self.like(v)
29
+ sanitize_sql_like(v)
30
+ end
31
+
32
+ def execute!
33
+ decide(@initial_ex)
34
+ end
35
+
36
+ private
37
+
38
+ def restricted?
39
+ @restricted
40
+ end
41
+
42
+ def permitted_columns?(v)
43
+ return true unless restricted?
44
+ @permitted_columns.include?(v)
45
+ end
46
+
47
+ def permitted_operators?(v)
48
+ return true unless restricted?
49
+ @permitted_operators.include?(v)
13
50
  end
14
51
 
15
- def decide(arel_table, ex)
16
- op = ::ToWa::Core::OPERATORS[ex.keys.first.to_s]
17
- values = ex.values.first
52
+ def decide(ex)
53
+ op = detect_op!(ex.k)
54
+ lr_or_exes = ex.v
18
55
 
19
56
  case op
20
57
  when 'not'
21
- decide(arel_table, values.first).not
58
+ decide(lr_or_exes.first).not
22
59
  when 'and', 'or'
23
- add_logic(arel_table, op, values)
60
+ add_logic(op, lr_or_exes)
61
+ when 'between'
62
+ between_to_gteq_and_lteq(*lr_or_exes)
24
63
  else
25
- add_comparison(arel_table, op, *values)
64
+ add_comparison(op, *lr_or_exes)
26
65
  end
27
66
  end
28
67
 
29
- def add_comparison(arel_table, op, table, value)
30
- normalized =
31
- case op
32
- when 'between'
33
- value.first..value.second
34
- when 'matches'
35
- "%#{ToWa::Builder.like(value)}%"
36
- else
37
- value
38
- end
68
+ def detect_op!(op)
69
+ sop = op.to_s
70
+ raise ::ToWa::DeniedOperator, sop unless permitted_operators?(sop)
71
+ ::ToWa::Core::OPERATORS[sop]
72
+ end
39
73
 
40
- arel_table[table].send(op, normalized)
74
+ def add_comparison(op, left, right)
75
+ normalize_value(left) { normalize_left_table(left) }.send(op, normalize_right(op, right))
41
76
  end
42
77
 
43
- def add_logic(arel_table, op, exes)
44
- logics =
45
- exes.inject(nil) do |a, ex|
46
- a.nil? ? decide(arel_table, ex) : a.send(op, decide(arel_table, ex))
47
- end
78
+ def between_to_gteq_and_lteq(left, right)
79
+ decide({
80
+ 'and' => [
81
+ { '>=' => [left, right.first] },
82
+ { '<=' => [left, right.second] },
83
+ ],
84
+ })
85
+ end
48
86
 
49
- (exes.size == 1) ? logics : Arel::Nodes::Grouping.new(logics)
87
+ def normalize_left_table(left)
88
+ raise ::ToWa::DeniedColumn, left unless permitted_columns?(left)
89
+ @arel_table[left]
50
90
  end
51
91
 
52
- def self.like(v)
53
- sanitize_sql_like(v)
92
+ def normalize_value(o, &block)
93
+ case
94
+ when o.is_a?(Hash) && o.k.to_s == 'col'
95
+ specify_columns(*o.v.map(&:to_s))
96
+ when block_given?
97
+ block&.call
98
+ else
99
+ o
100
+ end
101
+ end
102
+
103
+ def all_specified_columns_allowed?
104
+ @permitted_specified_columns == ::ToWa::AllSpecifiedColumnsAllowance
105
+ end
106
+
107
+ def permitted_specified_columns?(table, column)
108
+ return true unless restricted?
109
+ return true if all_specified_columns_allowed?
110
+
111
+ @permitted_specified_columns[table]&.include?(column)
112
+ end
113
+
114
+ def specify_columns(table, column)
115
+ raise ::ToWa::DeniedColumn, [table, column] unless permitted_specified_columns?(table, column)
116
+
117
+ Arel::Table.new(table)[column]
118
+ end
119
+
120
+ def normalize_right(op, right)
121
+ if op == 'matches'
122
+ "%#{ToWa::Builder.like(right)}%"
123
+ else
124
+ normalize_value(right)
125
+ end
126
+ end
127
+
128
+ def add_logic(op, exes)
129
+ logic = exes[1..-1].inject(decide(exes.first)) { |a, ex| a.send(op, decide(ex)) }
130
+ Arel::Nodes::Grouping.new(logic)
54
131
  end
55
132
  end
56
133
  end
134
+ # rubocop:enable Metrics/ClassLength
@@ -0,0 +1,45 @@
1
+ require 'json'
2
+
3
+ module ToWa
4
+ module Configuration
5
+ def permit_to_wa_columns(*columns)
6
+ @permitted_to_wa_columns = Set.new(columns.map(&:to_s))
7
+ end
8
+
9
+ def permit_all_to_wa_columns!
10
+ @permitted_to_wa_columns = Set.new(self.column_names)
11
+ end
12
+
13
+ def permit_to_wa_specified_columns(hash)
14
+ @permitted_to_wa_specified_columns =
15
+ JSON.parse(hash.to_json).inject({}) { |a, (k, v)| a.merge!(k => Set.new(Array(v))) }
16
+ end
17
+
18
+ def permit_all_to_wa_specified_columns!
19
+ @permitted_to_wa_specified_columns = ::ToWa::AllSpecifiedColumnsAllowance
20
+ end
21
+
22
+ def permit_to_wa_operators(*operators)
23
+ @permitted_to_wa_operators = Set.new(operators.map(&:to_s))
24
+ end
25
+
26
+ def permit_all_to_wa_operators!
27
+ @permitted_to_wa_operators = ToWa::Core::OPERATORS
28
+ end
29
+
30
+ def permitted_to_wa_columns
31
+ @permitted_to_wa_columns ||= Set.new
32
+ end
33
+
34
+ def permitted_to_wa_specified_columns
35
+ @permitted_to_wa_specified_columns ||= {}
36
+ end
37
+
38
+ def permitted_to_wa_operators
39
+ @permitted_to_wa_operators ||= Set.new
40
+ end
41
+ end
42
+
43
+ module AllSpecifiedColumnsAllowance
44
+ end
45
+ end
@@ -1,7 +1,5 @@
1
1
  module ToWa
2
2
  module Core
3
- extend ::ToWa::Builder
4
-
5
3
  COMPARISON = {
6
4
  '==' => 'eq',
7
5
  '=' => 'eq',
@@ -0,0 +1,13 @@
1
+ module ToWa
2
+ module EasyHashAccess
3
+ refine Hash do
4
+ def k
5
+ keys.first
6
+ end
7
+
8
+ def v
9
+ values.first
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ module ToWa
2
+ class DeniedOperator < StandardError
3
+ end
4
+
5
+ class DeniedColumn < StandardError
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module ToWa
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: to_wa
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - mmmpa
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-01-20 00:00:00.000000000 Z
11
+ date: 2018-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -131,7 +131,10 @@ files:
131
131
  - db/preparation.rb
132
132
  - lib/to_wa.rb
133
133
  - lib/to_wa/builder.rb
134
+ - lib/to_wa/configuration.rb
134
135
  - lib/to_wa/core.rb
136
+ - lib/to_wa/easy_hash_access.rb
137
+ - lib/to_wa/exceptions.rb
135
138
  - lib/to_wa/version.rb
136
139
  - to_wa.gemspec
137
140
  homepage: http://mmmpa.net