Module: Tiun

Extended by:
Attributes, Migration
Defined in:
lib/tiun.rb,
lib/tiun/version.rb

Defined Under Namespace

Modules: Actor, Attributes, Auth, Base, CoreHelper, ListSerializer, Migration, Model, Policy, Serializer Classes: CLI, CoreController, Engine, InvalidControllerError, InvalidModelError, MetaController, NoRailsError

Constant Summary collapse

MAP =
{
   'get' => {
      %r{(?:/(?<c>[^:/]+)).json} => 'index',
      %r{(?:/(?<c>[^/]+)/:[^/]+).json} => 'show',
   },
   'post' => 'create',
   'patch' => 'update',
   'put' => 'update',
   'delete' => 'destroy'
}
TYPE_MAP =
{
   "string" => "string",
   "sequence" => "integer",
   "uri" => "string",
   "list" => nil,
   "json" => "jsonb",
   "enum" => "string",
}
TEMPLATES =
{
   model: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "automodel.rb.erb"))),
   policy: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "autopolicy.rb.erb"))),
   controller: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "autocontroller.rb.erb"))),
#      list_serializer: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "autolistserializer.rb.erb"))),
#      serializer: ERB.new(IO.read(File.join(File.dirname(__FILE__), "tiun", "autoserializer.rb.erb")))
}
VERSION =
"0.0.2.1"

Constants included from Migration

Migration::EMBED, Migration::MigrationTemplate

Constants included from Attributes

Attributes::AR_MAP

Class Method Summary collapse

Methods included from Migration

base_migration, fields_for, migration_fields_for, migration_name_for, migrations, search_all_for, setup_migrations

Methods included from Attributes

attribute_name_for, attribute_types_for, attributes

Class Method Details

.action_names_for(context) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/tiun.rb', line 207

def action_names_for context
   actions = (context["methods"] || {}).map do |method|
      method_name = method.name
      rule = MAP[method_name]

      action =
         method.action || (rule.is_a?(String) && rule || rule.reduce(nil) do |a, (re, action)|
            a || context.path =~ re && action || nil
         end)

      # TODO validated types
      if ! action
         error :no_action_detected_for_resource_method, { name: context.name, method: method_name }
      end

      action ? [action, method] : nil
   end.compact.to_h

   if actions.blank?
      error :no_valid_method_defined_for_resource, { name: context.name }
   end

   actions
end

.append_config(file) ⇒ Object



371
372
373
374
# File 'lib/tiun.rb', line 371

def append_config file
   c = YAML.load(IO.read( file )).to_os
   config[ File.expand_path( file )] = c
end

.base_controllerObject



405
406
407
# File 'lib/tiun.rb', line 405

def base_controller
   @base_controller ||= defined?(ApplicationController) ? ApplicationController : ActionController::Base
end

.base_model(name = nil) ⇒ Object



401
402
403
# File 'lib/tiun.rb', line 401

def base_model name = nil
   @base_model ||= table_exist?(name) ? ActiveRecord::Base : ActiveModel::Model
end

.configObject



359
360
361
# File 'lib/tiun.rb', line 359

def config
   @config ||= {}
end

.config_reduce(config, default) ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/tiun.rb', line 243

def config_reduce config, default
   config.resources.reduce(default) do |res, context|
      if context.name.is_a?(String)
         yield(res, context.name, context)
      else
         res
      end
   end
rescue NoMethodError
   error :no_resources_section_defined_in_config, {config: config, default: default}

   default
end

.constantize(name) ⇒ Object

def model_names

   settings.keys.map(&:to_s)
end


392
393
394
395
# File 'lib/tiun.rb', line 392

def constantize name
   name.constantize
rescue NameError
end

.controller_default_arg_for(context) ⇒ Object



121
122
123
# File 'lib/tiun.rb', line 121

def controller_default_arg_for context
   context.key || /:(?<arg>[^\.]+)/.match(context.path)&.[](:arg)
end

.controller_name_for(context) ⇒ Object



99
100
101
102
103
104
105
106
107
# File 'lib/tiun.rb', line 99

def controller_name_for context
   if type = find_type(kind_for(context))
      type.controller || type.model || type.name
   else
      context.controller ||
         %r{^(?:(?<c>.+)/:[^/]+|/(?<c>[^:]+)).json} =~ context.path && c ||
         context.name.split(".").first
   end
end

.controller_title_for(context) ⇒ Object



115
116
117
118
119
# File 'lib/tiun.rb', line 115

def controller_title_for context
   name = controller_name_for(context)

   name ? name.pluralize.camelize + 'Controller' : raise(InvalidControllerError)
end

.controllersObject

def serializers

   @serializers ||= []
end

def list_serializers
   @list_serializers ||= []
end


351
352
353
# File 'lib/tiun.rb', line 351

def controllers
   @controllers ||= []
end

.default_routeObject



302
303
304
305
306
307
308
309
# File 'lib/tiun.rb', line 302

def default_route
   {
      uri: '/meta.json',
      path: 'meta#index',
      kind: 'get',
      options: { defaults: { format: 'json' }, constraints: { format: 'json' }}
   }
end

.defaultsObject



363
364
365
# File 'lib/tiun.rb', line 363

def defaults
   @defaults ||= {}.to_os
end

.detect_type(type_in) ⇒ Object



194
195
196
197
198
199
200
201
# File 'lib/tiun.rb', line 194

def detect_type type_in
   type = TYPE_MAP[type_in]
   type || !type.nil? && "reference" || nil
   #type_in.split(/\s+/).reject {|x|x =~ /^</}.map do |type_tmp|
   #   type = TYPE_MAP[type_tmp] #.keys.find {|key| key == type_tmp}
   #   type || !type.nil? && "reference" || nil
   #end.compact.uniq.join("_")
end

.draw_routes(e) ⇒ Object



311
312
313
314
315
316
317
# File 'lib/tiun.rb', line 311

def draw_routes e
   setup_if_not
   routes.each do |route|
      attrs = { route[:uri] => route[:path] }.merge(route[:options])
      e.send(route[:kind], **attrs)
   end
end

.error(code, options) ⇒ Object



417
418
419
# File 'lib/tiun.rb', line 417

def error code, options
   errors << { code: code, options: options }
end

.error_codesObject



327
328
329
# File 'lib/tiun.rb', line 327

def error_codes
   @error_codes ||= []
end

.errorsObject



367
368
369
# File 'lib/tiun.rb', line 367

def errors
   @errors ||= []
end

.find_type(type_names_in) ⇒ Object

find type record in type record table for last version of



153
154
155
156
157
158
159
# File 'lib/tiun.rb', line 153

def find_type type_names_in
   type_names = "#{type_names_in}".split(/\s+/)

   types.reduce(nil) do |t, type|
      type_names.include?(type.name) && (!t || !t.version || type.version && t.version < type.version) ? type : t
   end unless type_names.blank?
end

.kind_for(context) ⇒ Object



85
86
87
# File 'lib/tiun.rb', line 85

def kind_for context
   context.methods.reduce(nil) { |k, m| k || m.kind }
end

.load_defaults_from(config) ⇒ Object



257
258
259
# File 'lib/tiun.rb', line 257

def load_defaults_from config
   @defaults = defaults.to_h.merge(config.defaults.to_h).to_os
end

.load_routes_from(config) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/tiun.rb', line 277

def load_routes_from config
   @routes =
   config_reduce(config, routes) do |r, name, context|
      controller = route_title_for(context)
      actions = action_names_for(context)

      actions.reduce(r) do |res, (action, method)|
         /(\.(?<format>[^.]+))$/ =~ context.path

         path =
            /(?<pre>.*)<(?<key>\w+)>(?<post>.*)/ =~ context.path &&
            "#{pre}:#{key}#{post}" || context.path

         if res.select {|x| x[:uri] == path && x[:kind] == action }.blank?
            attrs = { uri: path, path: "#{controller}##{action}", kind: method.name }
            attrs = attrs.merge(options: {defaults: { format: format }, constraints: { format: format }}) if format

            res | [attrs]
         else
            res
         end
      end
   end
end

.load_types_from(config) ⇒ Object



203
204
205
# File 'lib/tiun.rb', line 203

def load_types_from config
   @types = types | (config.types || [])
end

.model_name_for(context) ⇒ Object



89
90
91
92
93
94
95
96
97
# File 'lib/tiun.rb', line 89

def model_name_for context
   if type = find_type(kind_for(context))
      type.model || type.name
   else
      context.model ||
         %r{(?:/(?<c>[^/]+)/:[^/]+|/(?<c>[^:/]+)).json} =~ context.path && c ||
         context.name.split(".").first
   end
end

.model_title_for(context) ⇒ Object



109
110
111
112
113
# File 'lib/tiun.rb', line 109

def model_title_for context
   name = model_name_for(context)

   name ? name.singularize.camelize : raise(InvalidModelError)
end

.modelsObject



335
336
337
# File 'lib/tiun.rb', line 335

def models
   @models ||= []
end

.parse_objects(kind, config) ⇒ Object



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/tiun.rb', line 261

def parse_objects kind, config
   config_reduce(config, send(kind.to_s.pluralize)) do |res, name, context|
      object_name = send("#{kind}_title_for", context)

      unless search_for(kind.to_s.pluralize, object_name)
         object_in = constantize(object_name)
         code = TEMPLATES[kind].result(binding)
         object = string_eval(code, object_name)

         res | [{ name: object_name, code: code, const: object }.to_os]
      else
         res
      end
   end
end

.policiesObject



339
340
341
# File 'lib/tiun.rb', line 339

def policies
   @policies ||= []
end

.policy_title_for(context) ⇒ Object



139
140
141
# File 'lib/tiun.rb', line 139

def policy_title_for context
   context.policy || model_name_for(context).singularize.camelize + "Policy"
end

.rootObject

def type_of kind

   case kind
   when 'string'
      :"ActiveModel::Type::String"
   when 'integer', 'index'
      :"ActiveRecord::ConnectionAdapters::SQLite3Adapter::SQLite3Integer"
   else
      error :invalid_attribute_type_for_kind, { kind: kind }
   end
end
def plain_parm parm
   case parm
   when String
      array = parm.split( /\s*,\s*/ )

      if array.size > 1
         plain_array( array )
      else
         array.first.to_sym
      end
   when Hash
      plain_hash( parm )
   when Array
      plain_array( parm )
   else
      nil
   end
end

def plain_hash hash
   hash.map do |( key, parms )|
      [ key.to_sym, plain_parm( parms )]
   end.to_h
end

def plain_array array
   array.map do | parm |
      plain_parm( parm )
   end.flatten
end

def setup_classes settings
   settings.each do | (model_name, tiun) |
      name = -> { model_name.to_s.camelize }
      names = -> { model_name.to_s.pluralize.camelize }
      params = -> { tiun[:fields].map(&:first) }

      binding.pry
      controller_rb = <<-RB
         class #{names[]}Controller < #{base_controller}
            include Tiun::Base

            def model
               ::#{name[]} ;end

            def object_serializer
               #{name[]}Serializer ;end

            def objects_serializer
               #{names[]}Serializer
            rescue NameError
               Tiun::PagedCollectionSerializer ;end

            def permitted_params
               params.require( '#{model_name}' ).permit( #{params[]} ) ;end;end
      RB

      policy_rb = <<-RB
         class #{name[]}Policy
            include Tiun::Policy
         end
      RB

      class_eval(controller_rb)
      class_eval(policy_rb)
   end
end


507
508
509
# File 'lib/tiun.rb', line 507

def root
   Gem::Specification.find_by_name( "tiun" ).full_gem_path
end

.route_title_for(context) ⇒ Object



125
126
127
128
129
# File 'lib/tiun.rb', line 125

def route_title_for context
   name = controller_name_for(context)

   name ? name.pluralize.tableize : raise(InvalidControllerError)
end

.routesObject



355
356
357
# File 'lib/tiun.rb', line 355

def routes
   @routes ||= [default_route]
end

.search_all_for(kind, value) ⇒ Object



323
324
325
# File 'lib/tiun.rb', line 323

def search_all_for kind, value
   send(kind).select {|x| x.name == value }
end

.search_for(kind, value) ⇒ Object



319
320
321
# File 'lib/tiun.rb', line 319

def search_for kind, value
   send(kind).find {|x| x.name == value }
end

.settingsObject



376
377
378
# File 'lib/tiun.rb', line 376

def settings
   @settings ||= setup_classes(tiuns.map { | model | [ model.name.underscore.to_sym, model.tiun ]}.to_h)
end

.setupObject



54
55
56
57
58
59
60
61
# File 'lib/tiun.rb', line 54

def setup
   if defined?(::Rails) && ::Rails.root && !@config
      files = Dir.glob(::Rails.root&.join("config", "tiun", "*.{yml,yaml}")) |
              Dir.glob(::Rails.root&.join("config", "tiun.{yml,yaml}"))

      setup_with(*files)
   end
end

.setup_if_notObject



63
64
65
# File 'lib/tiun.rb', line 63

def setup_if_not
   @setup ||= setup
end

.setup_with(*files) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/tiun.rb', line 67

def setup_with *files
   setup_migrations

   files.each do |file|
      config = append_config(file)
      # load_error_codes_from( config )
      load_types_from(config)
      load_defaults_from(config)
      %i(model controller policy).each do |kind|
         instance_variable_set("@#{kind.to_s.pluralize}", parse_objects(kind, config))
      end
      load_routes_from(config)
   end

   # validates
   config
end

.string_eval(string, name) ⇒ Object



232
233
234
235
236
237
238
239
240
241
# File 'lib/tiun.rb', line 232

def string_eval string, name
   tokens = name.split("::")[0...-1]
   default = tokens[0].blank? && Object || Object.const_get(tokens[0])

   (tokens[1..-1] || []).reduce(default) do |o, token|
      o.constants.include?(token.to_sym) && o.const_get(token) || o.const_set(token, Module.new)
   end

   eval(string)
end

.sublings_for(type_name_in, kind_in = %i(read write)) ⇒ Object

generates a hash to collect all subling relation for the specificed kind type



163
164
165
166
167
168
169
170
171
172
173
# File 'lib/tiun.rb', line 163

def sublings_for type_name_in, kind_in = %i(read write)
  kind = [kind_in].flatten

  type_attributes_for(type_name_in, kind).reduce({}) do |res, value_in|
     if value_in.is_a?(Hash)
        res.merge(value_in)
     else
        res
     end
  end
end

.table_exist?(name) ⇒ Boolean

Returns:

  • (Boolean)


397
398
399
# File 'lib/tiun.rb', line 397

def table_exist? name
   ActiveRecord::Base.connection.data_source_exists?(name.to_s.tableize)
end

.table_title_for(type) ⇒ Object



131
132
133
# File 'lib/tiun.rb', line 131

def table_title_for context
   context.table || model_name_for(context).tableize
end

.template_controller_for(context) ⇒ Object



409
410
411
412
413
414
415
# File 'lib/tiun.rb', line 409

def template_controller_for context
   t = context.template&.camelize

   self.const_get(t).const_get(:Controller)
rescue NameError, TypeError
   ::Tiun::Base
end

.tiunsObject



380
381
382
383
384
385
386
# File 'lib/tiun.rb', line 380

def tiuns
   ::Rails.configuration.paths['app/models'].to_a.each do | path |
      Dir.glob("#{path}/**/*.rb").each { |file| require(file) }
   end

   ActiveRecord::Base.tiuns
end

.type_attributes_for(type_name_in, kind = %i(read write)) ⇒ Object

type_attributes_for renders attributes array for the type name or type itself specified, if no type name has been found, it returns a blank array.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/tiun.rb', line 178

def type_attributes_for type_name_in, kind = %i(read write)
   type = type_name_in.is_a?(OpenStruct) ? type_name_in : find_type(type_name_in)

   return [] unless type

   type.fields.map do |x|
      next nil unless x.to_h.keys.select { |y| /only$/ =~ y }.reduce(kind)  { |k, prop| x[prop] ? ["#{prop}".sub("only","").to_sym] & k : k }.any?

      if sub = Tiun.find_type(x.kind)
         { x.name => type_attributes_for(sub, kind) }
      else
         x.name
      end
   end.compact
end

.typesObject



331
332
333
# File 'lib/tiun.rb', line 331

def types
   @types ||= []
end

.valid?Boolean

Returns:

  • (Boolean)


421
422
423
# File 'lib/tiun.rb', line 421

def valid?
   errors.blank?
end