tiun 0.0.1

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.
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"