fastapi 0.1.27 → 0.2.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.
@@ -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