terrazine 0.0.2 → 0.0.3

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: f1a24d3310a23b60eb51584c7fdcec61a03c2b58
4
- data.tar.gz: 3be3456371d57ae8b67918d5d3b09a6447ff9194
3
+ metadata.gz: d761b9fb0ee69306c68a075791804e9394be557a
4
+ data.tar.gz: 7413cc5841e648c59fee3163ca2841273efecbc1
5
5
  SHA512:
6
- metadata.gz: c5a940bf57c92d76934211d9f6bbcca8ec9ed0bd8e42f6f788feb817309c6dcf3edb1d3f567718d1c847c051efc992a85e89ff985b86969d2ae3a114ab80a58a
7
- data.tar.gz: 92462094a13958703b45b35305b5cc1107db055119a0847cfd2a5a34bf59d63525be94d3309e4972985e24ad5926ab2b29addb916fd1e7732eebf2f7b457abed
6
+ metadata.gz: 15259c4c4f582b13fcbcbfeca042398d97cfe554abd96ae291f8e38c523e188a0b7954666c6940c4254ee92e8886071647551a6b52b759d9891f045f7d6c3113
7
+ data.tar.gz: 032c017527f3bdf2b44a7ee2a6b69c4ae5a62306cdc5a3f49fa43e2b224271d6668f75bf1d62419a32fc8fdf0e4fa5db1608260db0cc839a0105266a7ae3d2e5
data/Gemfile CHANGED
@@ -1 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
1
3
  gem 'pg-hstore', '1.2.0'
4
+
5
+ group :test do
6
+ gem 'pg', '1.0.0'
7
+ gem 'rspec'
8
+ end
data/README.md CHANGED
@@ -14,20 +14,51 @@ Get result and access any returned data rails like syntax.
14
14
 
15
15
  #### Realization
16
16
  This is my first gem and first close meeting with OOP... I would appreciate any help =)
17
- And sorry for my English =(
17
+ And sorry for my English =(
18
+
19
+ #### Readiness
20
+ Terrazine is not finished yet. Now it has allmost full SELECT builder, but with some limitations like:
21
+ - awfull where syntax
22
+ - bad join syntax
23
+ - not all SQL functions supported
24
+
25
+ And now it supports only Postgresql.
18
26
 
19
27
  ## Detailed description
20
28
 
21
29
  ### Usage
22
- Describe whole data structure, or create `Constructor` instance and combine parts of data by it instance methods. Then send result to `Terrazine.send_request(structure||constructor, params = {})` and it will return you `Terrazine::Result` instance. (description will be soon)
30
+ #### Initialization
31
+ Add this line to the Gemfile
32
+ ```ruby
33
+ gem 'terrazine', '0.0.2'
34
+ ```
35
+ After server initialization set `Terrazine.config`. Now config accepts only `:connection` option. In the bright future will be added `:adapter` option support.
36
+ In rails you can set config with [after_initialize](https://apidock.com/rails/Rails/Configuration/after_initialize) and it will looks like:
37
+
38
+ UPD: On production, rails closing `PG::Connection` from `after_initialize`, as fast fix connection now can be `Proc` object which must return `PG::Connection`. Later i'll try to find better solution
39
+ ```ruby
40
+ # file config/application.rb
41
+ module Name
42
+ class Application < Rails::Application
43
+ # ....
44
+ config.after_initialize do
45
+ Terrazine.config connection: -> { ActiveRecord::Base.connection.raw_connection }
46
+ end
47
+ # ....
48
+ end
49
+ end
50
+ ```
51
+ #### Workflow
52
+ - Describe whole data structure, or create `Constructor` instance and combine parts of data by it instance methods.
53
+ - Send result to `Terrazine.send_request(structure||constructor, params = {})`
54
+ - Rejoice at the `::Result`
23
55
 
24
56
  ### Constructor
25
57
  You can create Constructor instance by calling `Terrazine.new_constructor`. It optional accepts data structure.
26
-
27
58
  ```ruby
28
59
  constructor = Terrazine.new_constructor
29
60
  constructor_2 = Terrazine.new_constructor from: :calls
30
- ```
61
+ ```
31
62
  #### Instance methods
32
63
  Instance methods write or combine data inside constructor instance.
33
64
  Not finished methods - just rewrites structure without combination with existing data.
@@ -42,90 +73,209 @@ Not finished methods - just rewrites structure without combination with existing
42
73
  - [x] build_sql
43
74
 
44
75
  ### Data Structures
76
+ You can take a look on more detailed examples in `spec/constructor_spec.rb`
45
77
 
46
- #### Select
47
- Accepts
78
+ #### Common patterns
79
+ ###### SQL Function
80
+ Structure:
81
+ - `Array`
82
+ - first element - `Symbol` that begins from _ - `:_nullif`
83
+ - arguments
84
+ - [columns](#columns)
85
+
86
+ ```ruby
87
+ [:_count, [:_nullif, :row, [:_params, 'mrgl']]] # TODO: param?
88
+ # => ['COUNT(NULLIF(row, $1))', ['mrgl']]
89
+ ```
90
+ [Detailed Functions description](#sql-functions).
91
+ ###### Columns
92
+ Possible structures:
93
+ - `String`
94
+ - if it locted in the `Hash` with table alias, table alias will be added to it
95
+ - if there is no table alias it will be returned to the builder as it is.
96
+ - `Symbol` - just parsed to string
97
+ - `Hash`
98
+ - key - table alias||name
99
+ - value - [columns](#columns)
100
+ - [SQL function](#sql-function)
101
+ - `Array` - holder of any possible structures
102
+ ```ruby
103
+ ['name', {u: ['role', 'u.phone, m.rating', :field]}]
104
+ # => 'name, u.role, u.phone, m.rating, u.field'
105
+ ```
106
+ [Detailed Select description](#select)
107
+ ###### Tables
108
+ Possible structures:
48
109
  - `String` || `Symbol`
49
- - `Hash` represents column alias - 'AS' (if key begins from `_`) OR table alias that will join to the values table prefix OR another data structure(present keyword `:select`).
50
- - Another `Constructor` or `Hash` representing data structure
51
- - `Array` can contain all of the above structures OR in case of first symbol/string begins from `_` it will represent SQL function
110
+ - [SQL function](#sql-function)
111
+ - `Array`
112
+ - if there is no `Array` inside it will be joined `structure.join ' '`
113
+ - otherwise it will be recursive mapped
52
114
  ```ruby
115
+ ['users u', [:_values, ...], [:masters, :m]]
116
+ 'users u, (VALUES...), masters m'
117
+ ```
118
+ ###### Conditions
119
+ Not finished yet...
120
+ Column can be described as `:u__name => 'u.name'` or `:name`
121
+ Possible structures:
122
+ - `String` passes as it is
123
+ - `Hash` represent sql `=` or `IN` if value is `Array`. TODO: `IS` in case of `nil` or `false`
124
+ - `Symbol` - column name
125
+ - `Array` - only as value! will be placed in querry params `($1)`.
126
+ - `String` - will be placed in querry params
127
+ - `Array`
128
+ - first element - `Symbol` operator representation, by default `:and`
129
+ - `eq` - `=`
130
+ - `or`, `and`
131
+ - `in`
132
+ - `not`
133
+ - `like`, `ilike`
134
+ - `reg` - `~`, `reg_i` - `~*`, `reg_f` - `!~`, `reg_fi` - `!~*`
135
+ - arguments
136
+ - `Array` - holder of any possible structures
137
+ ```ruby
138
+ [[:not, 'z = 13'],
139
+ [:or, 'mrgl = 2', 'rgl = 22'],
140
+ [:or, 'rgl = 12', 'zgl = lol']]
141
+ # => 'NOT z = 13 AND (mrgl = 2 OR rgl = 22) AND (rgl = 12 OR zgl = lol)'
142
+ [{ role: 'manager', id: [0, 1, 153] },
143
+ [:not, [:like, :u__name, 'Aeonax']]]
144
+ #=> 'role = $1 AND id IN ($2) AND NOT u.name LIKE $3', ['manager', [0, 1, 153], 'Aeonax']
145
+ ```
146
+ ###### Sub Querry
147
+ Possible structures:
148
+ - `Constructor` instance
149
+ - `Hash` with `:select` value
150
+
151
+ #### Select
152
+ Possible structures:
153
+ - [columns](#columns)
154
+ - [sub querry](#sub-query)
155
+ - [SQL function](#sql-function)
156
+ - `Array` with combination of possible structures.
157
+ ```ruby
158
+ # String
53
159
  constructor.select "name, email"
160
+ # Symbol
54
161
  constructor.select :birthdate
162
+ # Array as columns
163
+ constructor.select [:phone, 'role']
164
+ # Array as SQL function
165
+ constructor.select [:_nullif, :row, :value]
166
+ # Hash with column alias(`AS`) as key and any available for `select` value
167
+ constructor.select _missed_calls_count:
168
+ { select: [:_count, [:_nullif, :connected, :true]],
169
+ from: [:calls, :c],
170
+ where: ['c.client_id = u.id',
171
+ ['direction = ?', 0]]}
172
+ # Hash with table alias as key and any available for `select` values
55
173
  constructor.select m: [:common_rating, :work_rating, { _master_id: :id }]
56
- constructor.select { _missed_calls_count: { select: [:_count, [:_nullif, :connected, :true]],
57
- from: [:calls, :c],
58
- where: ['c.client_id = u.id',
59
- ['direction = ?', 0]]} }
174
+ # You can take a look of resulted data structure. In future, perhaps, Constructor will be more complicated and it will merge hashes...
60
175
  constructor.structure
61
- # => { select: ['name, email', :birthdate,
62
- # { m: [:common_rating, :work_rating, { _master_id: :id }] },
176
+ # => { select: ['name, email', :birthdate, :phone, 'role',
177
+ # [:_nullif, :row, :value],
63
178
  # { _missed_calls_count: { select: [:_count, [:_nullif, :connected, :true]],
64
179
  # from: [:calls, :c],
65
180
  # where: ['c.client_id = u.id',
66
- # ['direction = ?', 0]]} }] }
181
+ # ['direction = ?', 0]]} }] },
182
+ # { m: [:common_rating, :work_rating, { _master_id: :id }] }
67
183
 
68
184
  constructor.build_sql
69
- # => ['SELECT name, email, birthdate, m.common_rating, m.work_rating, m.id AS master_id,
185
+ # => ['SELECT name, email, birthdate, phone, role, NULLIF(row, value), m.common_rating, m.work_rating, m.id AS master_id,
70
186
  # (SELECT COUNT(NULLIF(connected, TRUE))
71
187
  # FROM calls c
72
188
  # WHERE c.client_id = u.id AND direction = $1) AS missed_calls_count',
73
189
  # 0]
74
- ```
190
+ ```
191
+
192
+ ##### Distinct Select
193
+ To specify distinct select you should add to your data structure `:distinct` value:
194
+ - `true`
195
+ - [columns](#columns)
196
+ Or with `Constructor` instance methods:
197
+ - `.distinct`
198
+ - distinct structure - optional
199
+ - `.distinct_select`
200
+ - [select](#select) structure
201
+ - distinct structure - optional
202
+ In constructor methods `distinct: true` passed by default
203
+ ```ruby
204
+ # as data
205
+ distinct: true, select: true
206
+ # => 'SELECT DISTINCT * '
207
+ # OR via constructor
208
+ constructor.distinct_select([:id, :name]).build_sql # => 'SELECT DISTINCT id, name'
209
+ # OR
210
+ constructor.distinct_select([:id, :name], :phone).build_sql # => 'SELECT DISTINCT ON(phone) id, name '
211
+ ```
75
212
 
76
213
  #### From
77
- Accepts
78
- - `String` || `Symbol`
79
- - `Array` can contains table_name and table_alias OR `VALUES` OR both
214
+ Possible structures:
215
+ - [table representation](#tables)
216
+ - [SQL functions](#sql-functions)
217
+ - `Array` with combination of possible structures.
80
218
  ```ruby
81
- from 'table_name table_alias' || :table_name
82
- from [:table_name, :table_alias]
83
- from [[:table_name, :table_alias], [:_values, [1, 2], :values_name, [*values_column_names]]]
84
- from [:mrgl, [:_values, [1, 2], :rgl, [:zgl, :gl]]]
85
- ```
219
+ from: 'table_name table_alias' || :table_name
220
+ from: [:table_name, :table_alias]
221
+ # => 'FROM table_name table_alias '
222
+ from: [:_values, [1, 2], :rgl, [:zgl, :gl]]
223
+ # => 'FROM (VALUES(1, 2)) AS rgl (zgl, gl)'
224
+ from: [[:table_name, :table_alias], [:_values, [1, 2], :values_name, [*values_column_names]]]
225
+ # => 'FROM table_name table_alias, (VALUES(1, 2)) AS values_name (v_c_1, v_c_2)'
226
+ ```
86
227
  I do not like the `from` syntax, but how it can be made more convenient...?
87
228
 
88
229
  #### Join
89
- Accpets
90
- - `String`
91
- - `Array`:
92
- First element same as `from` first element - table name or `Array` of table_name and table_alias, then `Hash` with keys:
93
- - on - conditions(description will be bellow)
94
- - options - optional contains `Symbol` or `String` of join type... rename to type?
95
-
96
- `Array` can be nested
97
- ```ruby
98
- join 'users u ON u.id = m.user_id'
99
- join ['users u ON u.id = m.user_id',
100
- 'skills s ON u.id = s.user_id']
101
- join [[:user, :u], { on: 'rgl = 123' }]
102
- join [[[:user, :u], { option: :full, on: [:or, 'mrgl = 2', 'rgl = 22'] }],
103
- [:master, { on: ['z = 12', 'mrgl = 12'] }]]
104
- ```
230
+ Possible structures:
231
+ - `String` - just passed in to `JOIN #{structure} `
232
+ - `Array` with values(same order):
233
+ - [table representation](#tables)
234
+ - `Hash`
235
+ - `:on` - [conditions](#conditions)
236
+ - `:option` - optional - contains `Symbol` or `String` of join type... rename to type?
237
+ - `Array` with combination of possible structures.
238
+ ```ruby
239
+ join: 'users u ON u.id = m.user_id'
240
+ join: ['users u ON u.id = m.user_id',
241
+ 'skills s ON u.id = s.user_id']
242
+ join: [[:user, :u], { on: 'rgl = 123' }]
243
+ # => 'JOIN users u ON rgl = 123'
244
+ join: [[[:user, :u], { option: :full, on: [:or, 'mrgl = 2', 'rgl = 22'] }],
245
+ [:master, { on: ['z = 12', 'mrgl = 12'] }]]
246
+ # => 'FULL JOIN user u ON mrgl = 2 OR rgl = 22 JOIN master ON z = 12 AND mrgl = 12'
247
+ ```
105
248
 
106
- #### Conditions
107
- Current conditions implementation is sux... -_- Soon i'll change it.
108
- Now it accepts `String` or `Array`.
109
- First element of array is `Symbol` representation of join condition - `:or || :and` or by default `:and`.
110
- ```ruby
111
- conditions 'mrgl = 12'
112
- conditions ['z = 12', 'mrgl = 12']
113
- conditions ['NOT z = 13', [:or, 'mrgl = 2', 'rgl = 22']]
114
- conditions [:or, ['NOT z = 13', [:or, 'mrgl = 2', 'rgl = 22']],
115
- [:or, 'rgl = 12', 'zgl = lol']]
116
- conditions [['NOT z = 13',
117
- [:or, 'mrgl = 2', 'rgl = 22']],
118
- [:or, 'rgl = 12', 'zgl = lol']]
119
- # => 'NOT z = 13 AND (mrgl = 2 OR rgl = 22) AND (rgl = 12 OR zgl = lol)'
120
- ```
249
+ #### Order
250
+ Possible structures:
251
+ - `String`, `Symbol` just insert it in `"ORDER BY #{structure} "`
252
+ - [SQL function](#sql-function)
253
+ - `Hash`
254
+ - key - previsious possible structures.
255
+ - value - options representation
256
+ - `Symbol` - `:last || :first || :asc || :desc`
257
+ - `String` - `'<' || '>'` or smthng else that passed in to `USING`
258
+ - `Array` - with symbols inside
259
+ - `Array` - any possible structures
260
+ ```ruby
261
+ order: 'z.amount DESC' || :name
262
+ # => 'ORDER BY z.amount DESC ' || 'ORDER BY name '
263
+ order: [:name, [:_case ...], { amount: [:first, :desc] }]
264
+ # => 'ORDER BY name, CASE ..., amount DESC NULLS FIRST '
265
+ ```
121
266
 
122
267
  #### With
123
268
  ```ruby
124
- with [:alias_name, { select: true, from: :users}]
125
- with [[:alias_name, { select: true, from: :users}],
126
- [:alias_name_2, { select: {u: [:name, :email]},
269
+ with: [:alias_name, { select: true, from: :users}]
270
+ with: [[:alias_name, { select: true, from: :users}],
271
+ [:alias_name_2, { select: {u: [:name, :email]},
127
272
  from: :rgl}]]
128
- ```
273
+ # => 'WITH alias_name (SELECT * FROM users ), alias_name_2 (...) '
274
+ # OR
275
+ with name: { select: true },
276
+ another_name: { select: :mrgl }
277
+ # => 'WITH name AS (SELECT * ), another_name AS (SELECT mrgl ) '
278
+ ```
129
279
 
130
280
  #### Union
131
281
  ```ruby
@@ -133,8 +283,27 @@ union: [{ select: true, from: [:o_list, [:_values, [1], :al, [:master]]] },
133
283
  { select: true, from: [:co_list, [:_values, [0, :FALSE, :TRUE, 0],
134
284
  :al, [:rating, :rejected,
135
285
  :payment, :master]]] }]
286
+ 'SELECT ... UNION SELECT ...'
287
+ ```
288
+
289
+ #### SQL Functions
290
+ ##### Params
291
+ Pass argument as params to adapter
292
+ ```ruby
293
+ [:_values, [:_params, 'mrgl', true, 'rgl'], :z, [:f_1, :f_2, :f_3]]
294
+ ['(VALUES($1, $2, $3) AS z (f_1, f_2, f_3))', ['mrgl', true, 'rgl']]
136
295
  ```
137
296
 
297
+ ##### Values
298
+ Second and third arguments are nesessary right now, but in furure i'll do them optional.
299
+ Arguments:
300
+ - array of values, can be nested
301
+ - `AS` name
302
+ - column names
303
+ ```ruby
304
+ [:_values, [{u: [:name, :phone]}, :role, [:_params, 'rgl']], :z, [:n, :p, :r, :m]]
305
+ # => '(VALUES(u.name, u.phone, role, $1) AS z (n, p, r, m))'
306
+ ```
138
307
  ### Result representation
139
308
  #### ::Row
140
309
  Result row - allow accessing data by field name via method - `row.name # => "mrgl"` or get hash representation with `row.to_h`
@@ -156,7 +325,7 @@ After initialize `PG::Result` cleared
156
325
  - `:presenter_options`
157
326
 
158
327
  #### ::Presenter
159
- Used in `result.present(options = {})` - it represents data as `Hash` or `Array`. options are merged with `result.options[:presenter_options]`
328
+ Used in `result.present(options = {})` for data representation as `Hash` or `Array`. Options are merged with `result.options[:presenter_options]`
160
329
  Data will be presented as `Array` if `rows > 1` or `options[:array]` present.
161
330
  ##### Available options
162
331
  - `array` - if querry returns only one row, but on client you await for array of data.
@@ -164,19 +333,38 @@ Data will be presented as `Array` if `rows > 1` or `options[:array]` present.
164
333
  - `Proc` - it will call proc with row as argument, and! then pass it to modifier_presentation again
165
334
  - `::Result` - it will call `modifier.present`
166
335
  - any else will be returned without changes
336
+ - `delete` - (will be soon) - Symbol, String or Array representing keys that must be deleted from result data.
167
337
 
168
338
  ## TODO:
169
- - [ ] Parse data like arrays, booleans, nil to SQL
170
- - [ ] Relocate functions builder in to class, finally I found how it can be done nice=))
171
- - [ ] meditate about structure supporting another databases(now supports only postgress)
339
+ Except this todo's there is a lot commented todo's inside project.-_-
340
+ - [x] Parse data like arrays, booleans, nil to SQL. (:_params function -\_-)
341
+ - [x] Relocate functions builder in to class, finally I found how it can be done nice=))
172
342
  - [ ] should I bother with extra spaces?
343
+ - [ ] logger in config
344
+ - [ ] Insert
345
+ - [ ] Update
346
+ - [ ] Delete
173
347
 
174
348
  ### Tests
349
+ - [ ] Normal structure!!!!
175
350
  - [ ] Constructor + Builder
176
351
  - [ ] Result
177
352
  - [ ] Request
178
353
 
179
- ### Think of a better data structure for
354
+ ### Meditate
355
+ - [ ] builder structure... another possibility to split it?
180
356
  - [ ] from
181
357
  - [ ] join !!!
182
- - [ ] where !!!!!! Support for rails like syntax with hash?
358
+ - [x] where !!!!!! Supporting rails like syntax with hash?
359
+ - [ ] supporting another databases
360
+
361
+ ## Updates:
362
+ #### 0.0.3
363
+ - Expand predicates syntax
364
+ - added support of multiple rows for `VALUES`
365
+ - `ORDER` structure
366
+ - scary tests-_-
367
+
368
+ ## Contact
369
+ You can write me your suggestions for improving the syntax, wishes, things that you think are missing here.
370
+ My [email](mailto:aeonax.liar@gmail.com), [Ruby On Rails slack](https://rubyonrails-link.slack.com/messages/D8W1WSRAP)
@@ -0,0 +1,11 @@
1
+ machine:
2
+ ruby:
3
+ version: 2.3.1
4
+
5
+ checkout:
6
+ post:
7
+ - bundle
8
+
9
+ test:
10
+ override:
11
+ - bundle exec rspec
@@ -28,12 +28,19 @@ module Terrazine
28
28
  Constructor.new structure
29
29
  end
30
30
 
31
- def self.build_sql(structure)
31
+ def self.build_sql(structure, options = {})
32
32
  case structure
33
- when Hash
34
- new_constructor(structure).build_sql
33
+ when Hash # , Array
34
+ new_constructor(structure).build_sql options
35
35
  when Constructor
36
- structure.build_sql
36
+ structure.build_sql options
37
+ when Array
38
+ # TODO!!!!!
39
+ if structure.first.is_a?(String) && structure.second.is_a?(Array)
40
+ structure
41
+ else
42
+ new_constructor(structure).build_sql options
43
+ end
37
44
  when String
38
45
  structure
39
46
  else