tiun 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tiun/model.rb ADDED
@@ -0,0 +1,108 @@
1
+ require 'tiun/version'
2
+
3
+ module Tiun::Model
4
+ # +tiun+ sets up tiuner for the model. This exports corresponding record fields to default
5
+ # dashboard method, which loads the react component. When the model has been set up, the method
6
+ # returns the compiled data of the model.
7
+ #
8
+ # Examples:
9
+ #
10
+ # tiun
11
+ #
12
+ # Model.tiun
13
+ #
14
+ def tiun
15
+ ## return self.instance_variable_get(:@tiun) if self.instance_variables.include?(:@tiun)
16
+
17
+ ## self.instance_variable_set(:@tiun, { fields: _tiun_parse_model})
18
+
19
+ ## tiuns = self.class.instance_variables.include?(:@tiun) && self.class.instance_variable_get(:@tiuns) || []
20
+ ## tiuns << self
21
+ ## self.class.instance_variable_set(:@tiuns, tiuns)
22
+ #
23
+ # belongs_to, has_many, has_one
24
+ end
25
+
26
+ # +tiuns+ returns lists of the tiuned models. In case the list is absent returns blank
27
+ # +Array+.
28
+ #
29
+ # Examples:
30
+ #
31
+ # Model.tiuns # => [ Model ]
32
+ #
33
+ # ActiveRecord::Base.tiuns # => []
34
+ #
35
+ def tiuns
36
+ self.class.instance_variable_get(:@tiuns) || []
37
+ end
38
+
39
+ # {"id"=>#<ActiveModel::Type::Integer:0x00007fbd4365de18 @limit=4, @precision=nil, @range=-2147483648...2147483648, @scale=nil>,
40
+ # "date"=>#<ActiveModel::Type::String:0x00007fbd4363bb38 @false="f", @limit=nil, @precision=nil, @scale=nil, @true="t">,
41
+ # "language_code"=>#<ActiveModel::Type::String:0x00007fbd4363bb38 @false="f", @limit=nil, @precision=nil, @scale=nil, @true="t">,
42
+ # "alphabeth_code"=>#<ActiveModel::Type::String:0x00007fbd4363bb38 @false="f", @limit=nil, @precision=nil, @scale=nil, @true="t">,
43
+ # "created_at"=>#<ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Timestamp:0x00007fbd4363f0f8 @limit=nil, @precision=nil, @scale=nil>,
44
+ # "updated_at"=>#<ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Timestamp:0x00007fbd4363f0f8 @limit=nil, @precision=nil, @scale=nil>,
45
+ # "place_id"=>#<ActiveModel::Type::Integer:0x00007fbd4365de18 @limit=4, @precision=nil, @range=-2147483648...2147483648, @scale=nil>,
46
+ # "author_name"=>#<ActiveModel::Type::String:0x00007fbd4363bb38 @false="f", @limit=nil, @precision=nil, @scale=nil, @true="t">,
47
+ # "council"=>#<ActiveModel::Type::String:0x00007fbd4363bb38 @false="f", @limit=nil, @precision=nil, @scale=nil, @true="t">,
48
+ # "licit"=>#<ActiveModel::Type::Boolean:0x00007fbd4365d3f0 @limit=nil, @precision=nil, @scale=nil>,
49
+ # "meta"=>#<ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb:0x00007fbd4365cc70 @limit=nil, @precision=nil, @scale=nil>}
50
+ #
51
+ # def attribute_types
52
+ # binding.pry
53
+ #Tiun.attribute_types_for(self).attribute_map.reduce({}) {|ats, a| ats.merge(a.name => a.type)}
54
+ # super
55
+ # @_tiun_attribute_types ||= Tiun.attribute_types_for(self) || super
56
+ # end
57
+
58
+ # Specify range to return
59
+ def range range
60
+ offset(range.begin).limit(range.end - range.begin + 1)
61
+ end
62
+
63
+ protected
64
+
65
+ # :nodoc:
66
+ def _tiun_parse_model
67
+ self.attribute_types.map do |( name, attr )|
68
+ if !["created_at", "updated_at"].include?( name )
69
+ kind = case attr.class.to_s.split("::").last
70
+ when /Integer|SQLite3Integer/
71
+ :integer
72
+ when "String"
73
+ :string
74
+ when "Text"
75
+ :text
76
+ when /DateTime|TimeZoneConverter/
77
+ :datetime
78
+ when "Date"
79
+ :date
80
+ when "Time"
81
+ :time
82
+ when "Boolean"
83
+ :boolean
84
+ else
85
+ end
86
+
87
+ if kind
88
+ props = { type: kind }
89
+ props[ :size ] = attr.limit if attr.limit
90
+
91
+ [name.to_sym, props]
92
+ end
93
+ end
94
+ end.compact.to_h
95
+ rescue ActiveRecord::ConnectionNotEstablished
96
+ []
97
+ end
98
+
99
+ #TODO
100
+ #defaults fields: :user
101
+ # tiun as: :branch /as: :is
102
+ # include user(without *_at)/all fields (to list/form),
103
+ # all scopes (to list),
104
+ # all havings - has_many/ones (to form)
105
+ # tiunable_by :field/s - for text search by this field/s
106
+ end
107
+
108
+ Model = Tiun::Model
@@ -0,0 +1,71 @@
1
+ module Tiun::Policy
2
+ class NotAuthorizedError < StandardError; end
3
+
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_reader :user, :record
8
+ end
9
+
10
+ def valid?
11
+ true
12
+ end
13
+
14
+ def all?
15
+ default?
16
+ end
17
+
18
+ def index?
19
+ default?
20
+ end
21
+
22
+ def show?
23
+ scope.where(id: record.id).exists?
24
+ end
25
+
26
+ def create?
27
+ default?
28
+ end
29
+
30
+ def new?
31
+ default?
32
+ end
33
+
34
+ def update?
35
+ default?
36
+ end
37
+
38
+ def destroy?
39
+ default?
40
+ end
41
+
42
+ def scope
43
+ defined?(Pundit) && Pundit.policy_scope!(user, record.class) || ActiveRecord::Base
44
+ end
45
+
46
+ class Scope
47
+ attr_reader :user, :scope
48
+
49
+ def initialize(user, scope)
50
+ @user = user
51
+ @scope = scope
52
+ end
53
+
54
+ def resolve
55
+ scope
56
+ end
57
+ end
58
+
59
+ protected
60
+
61
+ def initialize user, record
62
+ @user = user
63
+ @record = record
64
+ end
65
+
66
+ def default?
67
+ user.respond_to?(:admin?) && user.admin?
68
+ end
69
+ end
70
+
71
+ Policy = Tiun::Policy
@@ -0,0 +1,31 @@
1
+ module ::Tiun::Serializer
2
+ class UndefinedModelError < StandardError; end
3
+
4
+ def self.included kls
5
+ kls.class_eval do
6
+ def initialize model = nil
7
+ raise UndefinedModelError if !model
8
+
9
+ @model = model
10
+ end
11
+ end
12
+ end
13
+
14
+ def as_json *args
15
+ serializable_hash
16
+ end
17
+
18
+ def serializable_hash
19
+ binding.pry
20
+ {
21
+ }
22
+ end
23
+
24
+ def to_json *args
25
+ binding.pry
26
+ @objects.jsonize(context)
27
+ super
28
+ end
29
+ end
30
+
31
+ Serializer = ::Tiun::Serializer
@@ -0,0 +1,5 @@
1
+ module Tiun
2
+ VERSION = "0.0.1"
3
+ end
4
+
5
+ Version = Tiun::VERSION
data/lib/tiun.rb ADDED
@@ -0,0 +1,491 @@
1
+ begin
2
+ require 'pry'
3
+ rescue NameError, LoadError
4
+ end
5
+ require 'erb'
6
+ require 'action_controller'
7
+ require 'active_record'
8
+
9
+ require "tiun/mixins"
10
+ require "tiun/version"
11
+ require "tiun/migration"
12
+ require "tiun/attributes"
13
+ require "tiun/base"
14
+ require "tiun/core_helper"
15
+
16
+ module Tiun
17
+ class NoRailsError < StandardError ;end
18
+ class InvalidControllerError < StandardError ;end
19
+ class InvalidModelError < StandardError ;end
20
+ # extend ActiveSupport::Concern
21
+ extend Tiun::Migration
22
+ extend Tiun::Attributes
23
+
24
+ MAP = {
25
+ 'get' => {
26
+ %r{(?:/(?<c>[^:/]+)).json} => 'index',
27
+ %r{(?:/(?<c>[^/]+)/:[^/]+).json} => 'show',
28
+ },
29
+ 'post' => 'create',
30
+ 'patch' => 'update',
31
+ 'put' => 'update',
32
+ 'delete' => 'destroy'
33
+ }
34
+
35
+ TYPE_MAP = {
36
+ "string" => "string",
37
+ "sequence" => "integer",
38
+ "uri" => "string",
39
+ "list" => nil,
40
+ "json" => "jsonb",
41
+ "enum" => "string",
42
+ }
43
+
44
+ TEMPLATES = {
45
+ model: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "automodel.rb.erb"))),
46
+ policy: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "autopolicy.rb.erb"))),
47
+ controller: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "autocontroller.rb.erb"))),
48
+ # list_serializer: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "autolistserializer.rb.erb"))),
49
+ # serializer: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "autoserializer.rb.erb")))
50
+ }
51
+
52
+ class << self
53
+ def setup
54
+ if defined?(::Rails) && ::Rails.root && !@config
55
+ files = Dir.glob(::Rails.root&.join("config", "tiun", "*.{yml,yaml}")) |
56
+ Dir.glob(::Rails.root&.join("config", "tiun.{yml,yaml}"))
57
+
58
+ setup_with(*files)
59
+ end
60
+ end
61
+
62
+ def setup_if_not
63
+ @setup ||= setup
64
+ end
65
+
66
+ def setup_with *files
67
+ setup_migrations
68
+
69
+ files.each do |file|
70
+ config = append_config(file)
71
+ # load_error_codes_from( config )
72
+ load_types_from(config)
73
+ load_defaults_from(config)
74
+ %i(model controller policy).each do |kind|
75
+ instance_variable_set("@#{kind.to_s.pluralize}", parse_objects(kind, config))
76
+ end
77
+ load_routes_from(config)
78
+ end
79
+
80
+ # validates
81
+ config
82
+ end
83
+
84
+ def kind_for context
85
+ context.methods.reduce(nil) { |k, m| k || m.kind }
86
+ end
87
+
88
+ def model_name_for context
89
+ if type = find_type(kind_for(context))
90
+ type.model || type.name
91
+ else
92
+ context.model ||
93
+ %r{(?:/(?<c>[^/]+)/:[^/]+|/(?<c>[^:/]+)).json} =~ context.path && c ||
94
+ context.name.split(".").first
95
+ end
96
+ end
97
+
98
+ def controller_name_for context
99
+ if type = find_type(kind_for(context))
100
+ type.controller || type.model || type.name
101
+ else
102
+ context.controller ||
103
+ %r{^(?:(?<c>.+)/:[^/]+|/(?<c>[^:]+)).json} =~ context.path && c ||
104
+ context.name.split(".").first
105
+ end
106
+ end
107
+
108
+ def model_title_for context
109
+ name = model_name_for(context)
110
+
111
+ name ? name.singularize.camelize : raise(InvalidModelError)
112
+ end
113
+
114
+ def controller_title_for context
115
+ name = controller_name_for(context)
116
+
117
+ name ? name.pluralize.camelize + 'Controller' : raise(InvalidControllerError)
118
+ end
119
+
120
+ def controller_default_arg_for context
121
+ /:(?<arg>[^\.]+)/ =~ context.path
122
+
123
+ arg
124
+ end
125
+
126
+ def route_title_for context
127
+ name = controller_name_for(context)
128
+
129
+ name ? name.pluralize.tableize : raise(InvalidControllerError)
130
+ end
131
+
132
+ def table_title_for context
133
+ context.table || model_name_for(context).tableize
134
+ end
135
+
136
+ def table_title_for type
137
+ type.name.tableize
138
+ end
139
+
140
+ def policy_title_for context
141
+ context.policy || model_name_for(context).singularize.camelize + "Policy"
142
+ end
143
+
144
+ # def serializer_title_for context
145
+ # context.serializer || model_name_for(context).singularize.camelize + "Serializer"
146
+ # end
147
+ #
148
+ # def list_serializer_title_for context
149
+ # context.list_serializer || model_name_for(context).singularize.camelize + "ListSerializer"
150
+ # end
151
+ #
152
+ # find type record in type record table for last version of
153
+ #
154
+ def find_type type_names_in
155
+ type_names = "#{type_names_in}".split(/\s+/)
156
+
157
+ types.reduce(nil) do |t, type|
158
+ type_names.include?(type.name) && (!t || !t.version || type.version && t.version < type.version) ? type : t
159
+ end unless type_names.blank?
160
+ end
161
+
162
+ # +type_attributes_for+ renders attributes array for the type name or type itself specified,
163
+ # if no type name has been found, it returns a blank array.
164
+ #
165
+ def type_attributes_for type_name_in
166
+ type = type_name_in.is_a?(OpenStruct) ? type_name_in : find_type(type_name_in)
167
+
168
+ return [] unless type
169
+
170
+ type.fields.map do |x|
171
+ if sub = Tiun.find_type(x.kind)
172
+ { x.name => type_attributes_for(sub) }
173
+ else
174
+ x.name
175
+ end
176
+ end
177
+ end
178
+
179
+ def detect_type type_in
180
+ type = TYPE_MAP[type_in]
181
+ type || !type.nil? && "reference" || nil
182
+ #type_in.split(/\s+/).reject {|x|x =~ /^</}.map do |type_tmp|
183
+ # type = TYPE_MAP[type_tmp] #.keys.find {|key| key == type_tmp}
184
+ # type || !type.nil? && "reference" || nil
185
+ #end.compact.uniq.join("_")
186
+ end
187
+
188
+ def load_types_from config
189
+ @types = types | (config.types || [])
190
+ end
191
+
192
+ def action_names_for context
193
+ actions = (context["methods"] || {}).map do |method|
194
+ method_name = method.name
195
+ rule = MAP[method_name]
196
+
197
+ action = rule.is_a?(String) && rule || rule.reduce(nil) do |a, (re, action)|
198
+ a || context.path =~ re && action || nil
199
+ end
200
+
201
+ # TODO validated types
202
+ if ! action
203
+ error :no_action_detected_for_resource_method, { name: context.name, method: method_name }
204
+ end
205
+
206
+ action ? [action, method] : nil
207
+ end.compact.to_h
208
+
209
+ if actions.blank?
210
+ error :no_valid_method_defined_for_resource, { name: context.name }
211
+ end
212
+
213
+ actions
214
+ end
215
+
216
+ def string_eval string, name
217
+ tokens = name.split("::")[0...-1]
218
+ default = tokens[0].blank? && Object || Object.const_get(tokens[0])
219
+
220
+ (tokens[1..-1] || []).reduce(default) do |o, token|
221
+ o.constants.include?(token.to_sym) && o.const_get(token) || o.const_set(token, Module.new)
222
+ end
223
+
224
+ eval(string)
225
+ end
226
+
227
+ def config_reduce config, default
228
+ config.resources.reduce(default) do |res, context|
229
+ if context.name.is_a?(String)
230
+ yield(res, context.name, context)
231
+ else
232
+ res
233
+ end
234
+ end
235
+ rescue NoMethodError
236
+ error :no_resources_section_defined_in_config, {config: config, default: default}
237
+
238
+ []
239
+ end
240
+
241
+ def load_defaults_from config
242
+ @defaults = defaults.to_h.merge(config.defaults.to_h).to_os
243
+ end
244
+
245
+ def parse_objects kind, config
246
+ config_reduce(config, send(kind.to_s.pluralize)) do |res, name, context|
247
+ object_name = send("#{kind}_title_for", context)
248
+
249
+ unless search_for(kind.to_s.pluralize, object_name)
250
+ object_in = constantize(object_name)
251
+ code = TEMPLATES[kind].result(binding)
252
+ object = string_eval(code, object_name)
253
+
254
+ res | [{ name: object_name, code: code, const: object }.to_os]
255
+ else
256
+ res
257
+ end
258
+ end
259
+ end
260
+
261
+ def load_routes_from config
262
+ @routes =
263
+ config_reduce(config, routes) do |r, name, context|
264
+ controller = route_title_for(context)
265
+ actions = action_names_for(context)
266
+
267
+ actions.reduce(r) do |res, (action, method)|
268
+ /(\.(?<format>[^.]+))$/ =~ context.path
269
+
270
+ path =
271
+ /(?<pre>.*)<(?<key>\w+)>(?<post>.*)/ =~ context.path &&
272
+ "#{pre}:#{key}#{post}" || context.path
273
+
274
+ if res.select {|x| x[:uri] == path && x[:kind] == action }.blank?
275
+ attrs = { uri: path, path: "#{controller}##{action}", kind: method.name }
276
+ attrs = attrs.merge(options: {defaults: { format: format }, constraints: { format: format }}) if format
277
+
278
+ res | [attrs]
279
+ else
280
+ res
281
+ end
282
+ end
283
+ end
284
+ end
285
+
286
+ def default_route
287
+ {
288
+ uri: '/meta.json',
289
+ path: 'meta#index',
290
+ kind: 'get',
291
+ options: { defaults: { format: 'json' }, constraints: { format: 'json' }}
292
+ }
293
+ end
294
+
295
+ def draw_routes e
296
+ setup_if_not
297
+ routes.each do |route|
298
+ attrs = { route[:uri] => route[:path] }.merge(route[:options])
299
+ e.send(route[:kind], **attrs)
300
+ end
301
+ end
302
+
303
+ def search_for kind, value
304
+ send(kind).find {|x| x.name == value }
305
+ end
306
+
307
+ def search_all_for kind, value
308
+ send(kind).select {|x| x.name == value }
309
+ end
310
+
311
+ def error_codes
312
+ @error_codes ||= []
313
+ end
314
+
315
+ def types
316
+ @types ||= []
317
+ end
318
+
319
+ def models
320
+ @models ||= []
321
+ end
322
+
323
+ def policies
324
+ @policies ||= []
325
+ end
326
+
327
+ # def serializers
328
+ # @serializers ||= []
329
+ # end
330
+ #
331
+ # def list_serializers
332
+ # @list_serializers ||= []
333
+ # end
334
+ #
335
+ def controllers
336
+ @controllers ||= []
337
+ end
338
+
339
+ def routes
340
+ @routes ||= [default_route]
341
+ end
342
+
343
+ def config
344
+ @config ||= {}
345
+ end
346
+
347
+ def defaults
348
+ @defaults ||= {}.to_os
349
+ end
350
+
351
+ def errors
352
+ @errors ||= []
353
+ end
354
+
355
+ def append_config file
356
+ c = YAML.load(IO.read( file )).to_os
357
+ config[ File.expand_path( file )] = c
358
+ end
359
+
360
+ def settings
361
+ @settings ||= setup_classes(tiuns.map { | model | [ model.name.underscore.to_sym, model.tiun ]}.to_h)
362
+ end
363
+
364
+ def tiuns
365
+ ::Rails.configuration.paths['app/models'].to_a.each do | path |
366
+ Dir.glob("#{path}/**/*.rb").each { |file| require(file) }
367
+ end
368
+
369
+ ActiveRecord::Base.tiuns
370
+ end
371
+
372
+ # def model_names
373
+ # settings.keys.map(&:to_s)
374
+ # end
375
+ #
376
+ def constantize name
377
+ name.constantize
378
+ rescue NameError
379
+ end
380
+
381
+ def base_model
382
+ @base_model ||= ActiveRecord::Base
383
+ end
384
+
385
+ def base_controller
386
+ @base_controller ||= ActionController::Base
387
+ end
388
+
389
+ def error code, options
390
+ errors << { code: code, options: options }
391
+ end
392
+
393
+ def valid?
394
+ errors.blank?
395
+ end
396
+
397
+ #def target_version
398
+ #
399
+ #end
400
+
401
+ # def type_of kind
402
+ # case kind
403
+ # when 'string'
404
+ # :"ActiveModel::Type::String"
405
+ # when 'integer', 'index'
406
+ # :"ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer"
407
+ # else
408
+ # error :invalid_attribute_type_for_kind, { kind: kind }
409
+ # end
410
+ # end
411
+ # def plain_parm parm
412
+ # case parm
413
+ # when String
414
+ # array = parm.split( /\s*,\s*/ )
415
+ #
416
+ # if array.size > 1
417
+ # plain_array( array )
418
+ # else
419
+ # array.first.to_sym
420
+ # end
421
+ # when Hash
422
+ # plain_hash( parm )
423
+ # when Array
424
+ # plain_array( parm )
425
+ # else
426
+ # nil
427
+ # end
428
+ # end
429
+ #
430
+ # def plain_hash hash
431
+ # hash.map do |( key, parms )|
432
+ # [ key.to_sym, plain_parm( parms )]
433
+ # end.to_h
434
+ # end
435
+ #
436
+ # def plain_array array
437
+ # array.map do | parm |
438
+ # plain_parm( parm )
439
+ # end.flatten
440
+ # end
441
+ #
442
+ # def setup_classes settings
443
+ # settings.each do | (model_name, tiun) |
444
+ # name = -> { model_name.to_s.camelize }
445
+ # names = -> { model_name.to_s.pluralize.camelize }
446
+ # params = -> { tiun[:fields].map(&:first) }
447
+ #
448
+ # binding.pry
449
+ # controller_rb = <<-RB
450
+ # class #{names[]}Controller < #{base_controller}
451
+ # include Tiun::Base
452
+ #
453
+ # def model
454
+ # ::#{name[]} ;end
455
+ #
456
+ # def object_serializer
457
+ # #{name[]}Serializer ;end
458
+ #
459
+ # def objects_serializer
460
+ # #{names[]}Serializer
461
+ # rescue NameError
462
+ # Tiun::PagedCollectionSerializer ;end
463
+ #
464
+ # def permitted_params
465
+ # params.require( '#{model_name}' ).permit( #{params[]} ) ;end;end
466
+ # RB
467
+ #
468
+ # policy_rb = <<-RB
469
+ # class #{name[]}Policy
470
+ # include Tiun::Policy
471
+ # end
472
+ # RB
473
+ #
474
+ # class_eval(controller_rb)
475
+ # class_eval(policy_rb)
476
+ # end
477
+ # end
478
+ #
479
+ def root
480
+ Gem::Specification.find_by_name( "tiun" ).full_gem_path
481
+ end
482
+
483
+ end
484
+
485
+ # included do
486
+ # end
487
+ end
488
+
489
+ require "tiun/base"
490
+ require "tiun/policy"
491
+ require "tiun/engine"