fastapi 0.1.27 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,91 @@
1
+ module FastAPI
2
+ class Comparison
3
+
4
+ attr_reader :sql
5
+ alias_method :to_s, :sql
6
+
7
+ @@scalar_input = {
8
+ is: '__FIELD__ = __VALUE__',
9
+ not: '__FIELD__ <> __VALUE__',
10
+ gt: '__FIELD__ > __VALUE__',
11
+ gte: '__FIELD__ >= __VALUE__',
12
+ lt: '__FIELD__ < __VALUE__',
13
+ lte: '__FIELD__ <= __VALUE__',
14
+ like: '__FIELD__ LIKE \'%\' || __VALUE__ || \'%\'',
15
+ ilike: '__FIELD__ ILIKE \'%\' || __VALUE__ || \'%\'',
16
+ not_like: 'NOT (__FIELD__ LIKE \'%\' || __VALUE__ || \'%\')',
17
+ not_ilike: 'NOT (__FIELD__ ILIKE \'%\' || __VALUE__ || \'%\')',
18
+ null: '__FIELD__ IS NULL',
19
+ not_null: '__FIELD__ IS NOT NULL'
20
+ }.with_indifferent_access
21
+
22
+ @@multi_input = {
23
+ in: '__FIELD__ IN (__VALUES__)',
24
+ not_in: '__FIELD__ NOT IN (__VALUES__)',
25
+ subset: '__FIELD__ <@ ARRAY[__VALUES__]::__TYPE__[]',
26
+ not_subset: 'NOT __FIELD__ <@ ARRAY[__VALUES__]::__TYPE__[]',
27
+ contains: '__FIELD__ @> ARRAY[__VALUES__]::__TYPE__[]',
28
+ not_contains: 'NOT __FIELD__ @> ARRAY[__VALUES__]::__TYPE__[]',
29
+ intersects: '__FIELD__ && ARRAY[__VALUES__]::__TYPE__[]',
30
+ not_intersects: 'NOT __FIELD__ && ARRAY[__VALUES__]::__TYPE__[]'
31
+ }.with_indifferent_access
32
+
33
+ @@booleans = {
34
+ t: true,
35
+ true: true,
36
+ f: false,
37
+ false: false
38
+ }.with_indifferent_access
39
+
40
+ @@types = Hash.new('text').merge({
41
+ boolean: 'boolean',
42
+ integer: 'integer',
43
+ float: 'float',
44
+ string: 'varchar'
45
+ }).with_indifferent_access
46
+
47
+ def self.valid_comparator?(comparator)
48
+ @@scalar_input.key?(comparator) || @@multi_input.key?(comparator)
49
+ end
50
+
51
+ def self.invalid_comparator?(comparator)
52
+ !valid_comparator?(comparator)
53
+ end
54
+
55
+ def initialize(comparator, value, field, type)
56
+ key = prepare_comparator(comparator, value)
57
+ val = prepare_value(value, type)
58
+ if clause = @@scalar_input[key]
59
+ @sql = scalar_sql(clause, field, val)
60
+ elsif clause = @@multi_input[key]
61
+ @sql = multi_sql(clause, val, field, type)
62
+ else
63
+ raise ArgumentError.new("Invalid comparator: #{key}")
64
+ end
65
+ end
66
+
67
+ private
68
+ def prepare_comparator(comparator, value)
69
+
70
+ if value.nil? and comparator == 'is'
71
+ comparator = :null
72
+ end
73
+
74
+ return comparator
75
+
76
+ end
77
+
78
+ def prepare_value(value, type)
79
+ type == :boolean && !!value != value ? @@booleans[value] : value
80
+ end
81
+
82
+ def scalar_sql(clause, field, value)
83
+ clause.sub('__FIELD__', field).sub('__VALUE__', ActiveRecord::Base.connection.quote(value))
84
+ end
85
+
86
+ def multi_sql(clause, value, field, type)
87
+ values = [*value].map { |v| ActiveRecord::Base.connection.quote(v) }.join(',')
88
+ [clause.sub('__FIELD__', field).sub('__VALUES__', values).sub('__TYPE__', @@types[type])].compact.join
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,32 @@
1
+ module FastAPI
2
+ module Conversions
3
+
4
+ def self.convert_type(val, type, field = nil)
5
+ if val && is_array(field)
6
+ Oj.load(val).map { |inner_value| convert_value(inner_value, type) }
7
+ else
8
+ convert_value(val, type)
9
+ end
10
+ end
11
+
12
+ private
13
+ def self.is_array(field)
14
+ field && field.respond_to?('array') && field.array
15
+ end
16
+
17
+ def self.convert_value(val, type)
18
+ if val
19
+ case type
20
+ when :integer
21
+ val.to_i
22
+ when :float
23
+ val.to_f
24
+ when :boolean
25
+ { 't' => true, 'f' => false }[val]
26
+ else
27
+ val
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,6 +1,4 @@
1
- require 'active_record'
2
-
3
- module FastAPIExtension
1
+ module Extension
4
2
 
5
3
  extend ActiveSupport::Concern
6
4
 
@@ -68,9 +66,9 @@ module FastAPIExtension
68
66
  end
69
67
 
70
68
  def fastapi
71
- FastAPI.new(self)
69
+ FastAPI::Wrapper.new(self)
72
70
  end
73
71
  end
74
72
  end
75
73
 
76
- ActiveRecord::Base.send(:include, FastAPIExtension)
74
+ ActiveRecord::Base.send(:include, Extension)
@@ -0,0 +1,166 @@
1
+ require 'forwardable'
2
+
3
+ module FastAPI
4
+ class SQL
5
+ extend Forwardable
6
+
7
+ def_delegator :@sql, :[]
8
+
9
+ def initialize(filters, offset, count, klazz, whitelist, safe = false)
10
+
11
+ results = filter_fields(klazz, whitelist)
12
+ models, belongs, has_many, fields = results.values_at(:models, :belongs, :has_many, :fields)
13
+
14
+ model_name = klazz.to_s.tableize.singularize
15
+ table_name = klazz.to_s.tableize
16
+
17
+ # Base fields
18
+ field_list = generate_field_list(klazz, fields, table_name)
19
+
20
+ # Belongs fields
21
+ joins = parse_belongs(belongs, field_list, table_name)
22
+
23
+ # Many fields (1 to many)
24
+ parse_manys(has_many, filters, field_list, model_name, table_name)
25
+
26
+ filter_string = filters[:main].size > 0 ? "WHERE #{filters[:main].join(' AND ')}" : nil
27
+ order_string = filters[:main_order] ? "ORDER BY #{filters[:main_order]}" : nil
28
+
29
+ @sql = {
30
+ query: [
31
+ "SELECT #{field_list.join(', ')}",
32
+ "FROM #{table_name}",
33
+ joins.join(' '),
34
+ filter_string,
35
+ order_string,
36
+ "LIMIT #{count}",
37
+ "OFFSET #{offset}"
38
+ ].compact.join(' '),
39
+ count_query: [
40
+ "SELECT COUNT(id) FROM #{table_name}",
41
+ filter_string
42
+ ].compact.join(' '),
43
+ models: models
44
+ }
45
+ end
46
+
47
+ private
48
+ def filter_fields(klazz, whitelist)
49
+ skeleton = { models: {}, belongs: [], has_many: [], fields: [] }
50
+ (klazz.fastapi_fields + whitelist).each_with_object(skeleton) do |field, results|
51
+
52
+ if klazz.reflect_on_all_associations(:belongs_to).map(&:name).include?(field)
53
+
54
+ class_name = klazz.reflect_on_association(field).options[:class_name]
55
+ model = constantize_model(class_name, field)
56
+
57
+ results[:models][field] = model
58
+ results[:belongs] << { model: model, alias: field, type: :belongs_to }
59
+
60
+ elsif klazz.reflect_on_all_associations(:has_one).map(&:name).include?(field)
61
+
62
+ class_name = klazz.reflect_on_association(field).options[:class_name]
63
+ model = constantize_model(class_name, field)
64
+
65
+ results[:models][field] = model
66
+ results[:belongs] << { model: model, alias: field, type: :has_one }
67
+
68
+ elsif klazz.reflect_on_all_associations(:has_many).map(&:name).include?(field)
69
+
70
+ model = field.to_s.singularize.classify.constantize
71
+
72
+ results[:models][field] = model
73
+ results[:has_many] << model
74
+
75
+ elsif klazz.column_names.include?(field.to_s)
76
+ results[:fields] << field
77
+ end
78
+
79
+ end
80
+ end
81
+
82
+ def generate_field_list(klazz, fields, table)
83
+ fields.each_with_object([]) do |field, list|
84
+ if klazz.columns_hash[field.to_s].array
85
+ list << "ARRAY_TO_JSON(#{table}.#{field}) AS #{field}"
86
+ else
87
+ list << "#{table}.#{field} AS #{field}"
88
+ end
89
+ end
90
+ end
91
+
92
+ def parse_belongs(belongs, field_list, model_table_name)
93
+ belongs.each_with_object([]) do |model_data, join_list|
94
+
95
+ table_name = model_data[:model].to_s.tableize
96
+ table_alias = model_data[:alias].to_s.pluralize
97
+
98
+ field_name = model_data[:alias].to_s
99
+ singular_table_name = model_table_name.singularize
100
+
101
+ model_data[:model].fastapi_fields_sub.each do |field|
102
+ if model_data[:model].columns_hash[field.to_s].array
103
+ field_list << "ARRAY_TO_JSON(#{table_alias}.#{field}) AS #{field_name}__#{field}"
104
+ else
105
+ field_list << "#{table_alias}.#{field} AS #{field_name}__#{field}"
106
+ end
107
+ end
108
+
109
+ # fields
110
+ if model_data[:type] == :belongs_to
111
+ # joins
112
+ join_list << "LEFT JOIN #{table_name} AS #{table_alias} " \
113
+ "ON #{table_alias}.id = #{model_table_name}.#{field_name}_id"
114
+ elsif model_data[:type] == :has_one
115
+ join_list << "LEFT JOIN #{table_name} AS #{table_alias} " \
116
+ "ON #{table_alias}.#{singular_table_name}_id = #{model_table_name}.id"
117
+ end
118
+ end
119
+ end
120
+
121
+ def parse_manys(has_many, filters, field_list, model_name, table_name)
122
+ has_many.each do |model|
123
+
124
+ model_string_table = model.to_s.tableize
125
+ model_symbol = model_string_table.to_sym
126
+
127
+ model_fields = model.fastapi_fields_sub.each_with_object([]) do |field, m_fields|
128
+ m_fields << "__#{model_string_table}.#{field}"
129
+ end
130
+
131
+ if filters[:has_many].has_key?(model_symbol)
132
+
133
+ if not filters[:has_many][model_symbol].blank?
134
+ has_many_filters = "AND #{filters[:has_many][model_symbol].join(' AND ')}"
135
+ else
136
+ has_many_filters = nil
137
+ end
138
+
139
+ if not filters[:has_many_order][model_symbol].blank?
140
+ has_many_order = "ORDER BY #{filters[:has_many_order][model_symbol]}"
141
+ else
142
+ has_many_order = nil
143
+ end
144
+
145
+ end
146
+
147
+ field_list << [
148
+ "ARRAY_TO_JSON(ARRAY(SELECT ROW(#{model_fields.join(', ')})",
149
+ "FROM #{model_string_table}",
150
+ "AS __#{model_string_table}",
151
+ "WHERE __#{model_string_table}.#{model_name}_id IS NOT NULL",
152
+ "AND __#{model_string_table}.#{model_name}_id",
153
+ "= #{table_name}.id",
154
+ has_many_filters,
155
+ has_many_order,
156
+ ")) AS __many__#{model_string_table}"
157
+ ].compact.join(' ')
158
+
159
+ end
160
+ end
161
+
162
+ def constantize_model(class_name, field)
163
+ (class_name ? class_name : field.to_s.classify).constantize
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,7 @@
1
+ module FastAPI
2
+ module Utilities
3
+ def clamp(value, min, max)
4
+ [min, value, max].sort[1]
5
+ end
6
+ end
7
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.27
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Horwood
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-05-01 00:00:00.000000000 Z
12
+ date: 2015-06-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -147,7 +147,11 @@ extensions: []
147
147
  extra_rdoc_files: []
148
148
  files:
149
149
  - lib/fastapi.rb
150
- - lib/fastapi/active_record_extension.rb
150
+ - lib/fastapi/comparison.rb
151
+ - lib/fastapi/conversions.rb
152
+ - lib/fastapi/extension.rb
153
+ - lib/fastapi/sql.rb
154
+ - lib/fastapi/utilities.rb
151
155
  homepage: https://github.com/thestorefront/FastAPI
152
156
  licenses:
153
157
  - MIT