tiun 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tiun/base.rb ADDED
@@ -0,0 +1,371 @@
1
+ require 'active_support'
2
+
3
+ module Tiun::Base
4
+ class OutOfRangeError < StandardError; end
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attr_reader :objects, :object
10
+
11
+
12
+ #attr_accessor :params
13
+ #define_callbacks :subscribe
14
+ # include Kaminari::ConfigurationMethods
15
+ #class_attribute :_helpers
16
+ #self._helpers = Module.new
17
+ #include ActiveSupport::Configurable
18
+ #config_accessor :co
19
+ # helper_method :cookies if defined?(helper_method)
20
+ # around_perform do |job, block, _|
21
+ # I18n.with_locale(I18n.locale.to_s, &block)
22
+ # include Pundit
23
+
24
+ # before_action :authenticate_user!
25
+ # before_action :set_tokens, only: %i(index)
26
+ # before_action :set_page, only: %i(index)
27
+ # before_action :set_locales
28
+ before_action :new_object, only: %i(create)
29
+ before_action :fetch_object, only: %i(show update destroy)
30
+ before_action :fetch_objects, only: %i(index)
31
+ before_action :parse_range, only: %i(index ac)
32
+ after_action :return_range, only: %i(index ac)
33
+ # before_action :authorize!
34
+
35
+ rescue_from Exception, with: :exception
36
+ rescue_from ActionView::MissingTemplate, with: :missing_template
37
+ rescue_from ActionController::ParameterMissing, with: :unprocessable_entity
38
+ rescue_from ActiveRecord::RecordNotUnique,
39
+ ActiveRecord::RecordInvalid,
40
+ ActiveRecord::RecordNotSaved,
41
+ CarrierWave::IntegrityError, with: :unprocessable_entity
42
+ rescue_from ActiveRecord::RecordNotFound, with: :not_found
43
+ rescue_from OutOfRangeError, with: :out_of_range
44
+
45
+ # has_scope :with_token, only: %i(index all)
46
+ # has_scope :with_tokens, only: %i(index), type: :array
47
+
48
+ end
49
+
50
+ # GET /<objects>/
51
+ def index
52
+ respond_to do |format|
53
+ format.html { render :index }
54
+ format.json { render status: code, json: serialize_collection(paged_objects) }
55
+ format.jsonp { render status: code, json: serialize_collection(paged_objects) }
56
+ format.any { render status: code, json: serialize_collection(paged_objects), content_type: 'application/json' }
57
+ end
58
+ end
59
+
60
+ # POST /<objects>/create.json
61
+ def create
62
+ object.save!
63
+
64
+ respond_to do |format|
65
+ format.html
66
+ format.json { render json: serialize(object) }
67
+ format.jsonp { head :ok }
68
+ format.any { render json: serialize(object), content_type: 'application/json' }
69
+ end
70
+ end
71
+
72
+ # PUT /<objects>/:id.json
73
+ def update
74
+ object.update!(permitted_params)
75
+
76
+ respond_to do |format|
77
+ format.html
78
+ format.json { render json: serialize(object) }
79
+ format.jsonp { head :ok }
80
+ format.any { render json: serialize(object), content_type: 'application/json' }
81
+ end
82
+ end
83
+
84
+ # GET /<objects>/:id.json
85
+ def show
86
+ respond_to do |format|
87
+ format.html
88
+ format.json { render json: serialize(object) }
89
+ format.jsonp { render json: serialize(object) }
90
+ format.any { render json: serialize(object), content_type: 'application/json' }
91
+ end
92
+ end
93
+
94
+ # DELETE /<objects>/:id.json
95
+ def destroy
96
+ object.destroy
97
+
98
+ respond_to do |format|
99
+ format.html
100
+ format.json { render json: serialize(object) }
101
+ format.jsonp { head :ok }
102
+ format.any { render json: serialize(object), content_type: 'application/json' }
103
+ end
104
+ rescue Exception
105
+ binding.pry
106
+ end
107
+
108
+ # GET /<objects>/ac.json
109
+ def ac
110
+ @objects = apply_scopes(model)
111
+
112
+ respond_to do |format|
113
+ format.json {
114
+ render json: {
115
+ list: objects.limit(ac_limit).jsonize(ac_context),
116
+ total: objects.total_count
117
+ }
118
+ }
119
+ end
120
+ end
121
+
122
+ # def dashboard
123
+ # settings = { settings: Tiun.settings, locales: @locales }
124
+ #
125
+ # render inline: react_component('Dashboard', settings), layout: 'tiun'
126
+ # end
127
+ #
128
+ protected
129
+
130
+ def code
131
+ total == 0 ? 204 : total > range.end + 1 ? 206 : 200
132
+ end
133
+
134
+ def model_name
135
+ @_model_name ||= self.class.to_s.gsub(/.*::/, "").gsub("Controller", "").singularize
136
+ end
137
+
138
+ def param_name
139
+ @_param_name ||= model_name.tableize.singularize
140
+ end
141
+
142
+ def model
143
+ @_model ||= model_name.constantize
144
+ end
145
+
146
+ # def serializer
147
+ # @serializer ||= "#{model_name}Serializer".constantize
148
+ # end
149
+ #
150
+ # def list_serializer
151
+ # @list_serializer ||= "#{model_name}ListSerializer".constantize
152
+ # end
153
+ #
154
+ def policy
155
+ @_policy ||= "#{model_name}Policy".constantize
156
+ end
157
+
158
+ # def objects_serializer
159
+ # list_serializer.new(model: model)
160
+ # end
161
+ #
162
+ def apply_scopes model
163
+ model
164
+ end
165
+
166
+ # def policy_name
167
+ # @policy_name ||= Object.const_get(model.name + "Policy")
168
+ # rescue NameError
169
+ # @policy_name = Object.const_get("Tiun::#{model.name}Policy")
170
+ # end
171
+ #
172
+ def not_found _e
173
+ render json: { args: params.permit(*permitted_filter).to_h }, status: 404
174
+ end
175
+
176
+ def out_of_range _e
177
+ render json: { args: params.permit(*permitted_filter).to_h }, status: 416
178
+ end
179
+
180
+ def unprocessable_entity e
181
+ errors = @object && @object.errors.any? && @object.errors || e.to_s
182
+ args = params.permit(*permitted_filter).to_h
183
+ render json: { args: args, error: errors }, status: :unprocessable_entity
184
+ end
185
+
186
+ def parameter_missing e
187
+ error = "[#{e.class}]> #{e.message} \n\t #{e.backtrace.join("\n\t")}"
188
+ args = params.permit(*permitted_filter).to_h
189
+ render json: { args: args, error: error }, status: 500
190
+ end
191
+
192
+ def missing_template e
193
+ error = "[#{e.class}]> #{e.message} \n\t #{e.backtrace.join("\n\t")}"
194
+ args = params.permit(*permitted_filter).to_h
195
+ render json: { args: args, error: error }, status: 500
196
+ end
197
+
198
+ def exception e
199
+ error = "[#{e.class}]> #{e.message} \n\t #{e.backtrace.join("\n\t")}"
200
+ args = params.permit(*permitted_filter).to_h
201
+ render json: { args: args, error: error }, status: 500
202
+ end
203
+
204
+ def ac_limit
205
+ 500
206
+ end
207
+
208
+ def context
209
+ @_context ||= { except: supplement_names, locales: locales }
210
+ end
211
+
212
+ def ac_context
213
+ @_ac_context ||= { only: %i(key value) }
214
+ end
215
+
216
+ def permitted_filter
217
+ @_permitted_filter ||= {}
218
+ @_permitted_filter[action_name] ||=
219
+ if c = self.class.instance_variable_get(:@context)[action_name]
220
+ c.args.map {|x|x.name}
221
+ else
222
+ [model.primary_key]
223
+ end
224
+ end
225
+
226
+ def permitted_self
227
+ @_permitted_self ||= {}
228
+ @_permitted_self[action_name] ||=
229
+ if c = self.class.instance_variable_get(:@context)[action_name]
230
+ c.args.map {|x|x.name}
231
+ else
232
+ model.attribute_types.keys
233
+ end
234
+ end
235
+
236
+ def permitted_children
237
+ @_permitted_children ||=
238
+ model.nested_attributes_options.reduce({}) do |res, (name, opts)|
239
+ child_model = model.reflections[name.to_s].klass
240
+ value = child_model.attribute_types.keys
241
+ value << :_destroy if opts[:allow_destroy]
242
+ res[name] = value - supplement_names.map(&:to_s)
243
+
244
+ res
245
+ end
246
+ end
247
+
248
+ def permitted_params
249
+ params.require(param_name).permit(*permitted_self, **permitted_children)
250
+ end
251
+
252
+ def supplement_names
253
+ %i(created_at updated_at tsv)
254
+ end
255
+
256
+ def total
257
+ %i(total_size total_count count).reduce(nil) do |count, method|
258
+ objects.respond_to?(method) && !count ? objects.send(method) : count
259
+ end
260
+ end
261
+
262
+ def paged_objects
263
+ objects.range(range)
264
+ end
265
+
266
+ def render_type type
267
+ end
268
+
269
+ def get_properties
270
+ @get_properties ||=
271
+ if k = self.class.instance_variable_get(:@context)[action_name]&.kind
272
+ Tiun.type_attributes_for(k)
273
+ else
274
+ model.attribute_types.keys
275
+ end
276
+ end
277
+
278
+ def serialize_collection collection
279
+ collection.jsonize(context.merge(only: get_properties))
280
+ # format.json { render :index, json: objects, locales: locales,
281
+ # serializer: objects_serializer,
282
+ # each_serializer: serializer,
283
+ # total: objects.total_count,
284
+ # page: page,
285
+ # per: per }
286
+ end
287
+
288
+ def serialize object
289
+ object.jsonize(context.merge(only: get_properties))
290
+ # format.json { render :show, json: @object, locales: @locales,
291
+ # serializer: serializer }
292
+ end
293
+ #
294
+
295
+ def authorize!
296
+ binding.pry
297
+ if !policy.new(current_user, @object).send(action_name + '?')
298
+ raise Tiun::Policy::NotAuthorizedError, "not allowed to do #{action_name} this #{@object.inspect}"
299
+ end
300
+ end
301
+
302
+ def per
303
+ @per ||= (params[:per] ||
304
+ if args = self.class.instance_variable_get(:@context)[action_name]&.args
305
+ args.reduce(nil) { |res, x| res || x.name == "per" && x.default || nil }
306
+ end || 25).to_i
307
+ end
308
+
309
+ def page
310
+ @page ||= (params[:page] || 1).to_i
311
+ end
312
+
313
+ def locales
314
+ @locales ||= [I18n.locale]
315
+ end
316
+
317
+ def set_tokens
318
+ @tokens ||= params[:with_tokens] || []
319
+ end
320
+
321
+ def default_arg
322
+ self.class.instance_variable_get(:@default_arg)[action_name]
323
+ rescue NameError
324
+ "id"
325
+ end
326
+
327
+ def range
328
+ @range
329
+ end
330
+
331
+ # callbacks
332
+ def parse_range
333
+ @range =
334
+ if /(?<b>[0-9]+)-(?<e>[0-9]+)/ =~ request.headers["Range"]
335
+ (b.to_i..e.to_i - 1)
336
+ else
337
+ ((page - 1) * per..(page - 1) * per + per - 1)
338
+ end
339
+
340
+ raise OutOfRangeError if total > 0 && @range.begin >= total || @range.end < @range.begin
341
+ end
342
+
343
+ def new_object
344
+ @object = model.new(permitted_params)
345
+ end
346
+
347
+ def fetch_objects
348
+ @objects = apply_scopes(model)#.page(params[:page])
349
+ end
350
+
351
+ def fetch_object
352
+ arg_name = default_arg
353
+
354
+ @object ||=
355
+ if !arg_name
356
+ model.find(params[model.primary_key])
357
+ elsif model.respond_to?("by_#{arg_name}")
358
+ model.send("by_#{arg_name}", params[arg_name]).first
359
+ else
360
+ model.where({ arg_name => params[arg_name] }).first
361
+ end || raise(ActiveRecord::RecordNotFound)
362
+ end
363
+
364
+ def return_range
365
+ response.headers["Accept-Ranges"] = "records"
366
+ response.headers["Content-Range"] = "records #{range.begin}-#{range.end}/#{total}"
367
+ response.headers["Content-Length"] = [range.end + 1, total].min - range.begin
368
+ end
369
+ end
370
+
371
+ Base = Tiun::Base
data/lib/tiun/cli.rb ADDED
@@ -0,0 +1,88 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ require 'tiun'
5
+ require 'tiun/actor'
6
+
7
+ class Tiun::CLI
8
+ DEFAULT_OPTIONS = {
9
+ config: nil,
10
+ }.to_os
11
+
12
+ def option_parser
13
+ @option_parser ||=
14
+ OptionParser.new do |opts|
15
+ opts.banner = "Usage: setup.rb [options & actions]"
16
+
17
+ opts.on("-c", "--use-config=CONFIG", String, "use specific tiun config file to apply") do |config|
18
+ options[:config] = config
19
+ end
20
+
21
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
22
+ options[:verbose] = v
23
+ end
24
+
25
+ opts.on("-h", "--help", "This help") do |v|
26
+ puts opts
27
+ puts "\nActions: \n#{actions.join("\n")}"
28
+ exit
29
+ end
30
+ end
31
+
32
+ if @argv
33
+ @option_parser.default_argv.replace(@argv)
34
+ elsif @option_parser.default_argv.empty?
35
+ @option_parser.default_argv << "-h"
36
+ end
37
+
38
+ @option_parser
39
+ end
40
+
41
+ def options
42
+ @options ||= DEFAULT_OPTIONS.dup
43
+ end
44
+
45
+ def actions
46
+ @actions ||= parse.actions.select { |a| Tiun::Actor.kinds.include?(a) }
47
+ end
48
+
49
+ def tiun
50
+ @tiun ||= options.config &&
51
+ Tiun.setup_with(options.config) ||
52
+ Tiun.setup
53
+ end
54
+
55
+ def default_parse
56
+ @parse = OpenStruct.new(options: options, actions: option_parser.default_argv)
57
+ end
58
+
59
+ def parse!
60
+ return @parse if @parse
61
+
62
+ option_parser.parse!
63
+
64
+ default_parse
65
+ end
66
+
67
+ def parse
68
+ parse!
69
+ rescue OptionParser::InvalidOption
70
+ default_parse
71
+ end
72
+
73
+ def run
74
+ actions.reduce({}.to_os) do |res, action_name|
75
+ res[action_name] = Tiun::Actor.for!(action_name, tiun)
76
+
77
+ res
78
+ end.map do |action_name, actor|
79
+ actor.apply_to(Tiun)
80
+ end
81
+ end
82
+
83
+ def initialize argv = nil
84
+ @argv = argv&.split(/\s+/)
85
+ end
86
+ end
87
+
88
+ Cli = Tiun::CLI
@@ -0,0 +1,6 @@
1
+ class Tiun::CoreController < ActionController::Base
2
+ include Tiun::CoreHelper
3
+ include Tiun::Base
4
+ end
5
+
6
+ CoreController = Tiun::CoreController
@@ -0,0 +1,13 @@
1
+ module ::Tiun::CoreHelper
2
+ include ActionView::Helpers::TagHelper
3
+
4
+ def react_component name, props = {}, options = {}, &block
5
+ html_options = options.reverse_merge(data: {
6
+ react_class: name,
7
+ react_props: (props.is_a?(String) ? props : props.to_json)
8
+ })
9
+ content_tag(:div, '', html_options, &block)
10
+ end
11
+ end
12
+
13
+ CoreHelper = ::Tiun::CoreHelper
@@ -0,0 +1,35 @@
1
+ require 'rails'
2
+ require 'active_record'
3
+
4
+ require 'tiun/model'
5
+
6
+ class Tiun::Engine < ::Rails::Engine
7
+ isolate_namespace Tiun
8
+
9
+ engine_name 'tiun'
10
+
11
+ paths["app"] << "lib"
12
+ paths["app/controllers"] << "lib"
13
+ paths["app/views"] << "lib"
14
+ # paths["config"] << "lib/config"
15
+ # paths["config/locales"] << "lib/locales"
16
+ paths["config/routes.rb"] << "lib/config/routes.rb"
17
+ # require 'pry'
18
+ # binding.pry
19
+
20
+ # initializer "tiun", group: :all do |app|
21
+ # PgHero.time_zone = PgHero.config["time_zone"] if PgHero.config["time_zone"]
22
+ #
23
+
24
+ # end
25
+
26
+ # config.autoload_paths += %W(#{Tiun::Engine.root}/lib/tiun/autoloads)
27
+ # config.autoload_paths += %W(#{Tiun::Engine.root}/lib/tiun/views)
28
+
29
+ config.to_prepare do
30
+ ::ActiveRecord::Base.extend(Tiun::Model)
31
+ Tiun.setup
32
+ end
33
+ end
34
+
35
+ Engine = Tiun::Engine
@@ -0,0 +1,20 @@
1
+ module ::Tiun::ListSerializer
2
+ def as_json *_args
3
+ serializable_hash
4
+ end
5
+
6
+ def to_json *_args
7
+ serializable_hash
8
+ end
9
+
10
+ def serializable_hash(*_args)
11
+ binding.pry
12
+ {
13
+ list: super,
14
+ page: @options[:page] || 1,
15
+ total: @options[:total] || @object.count
16
+ }
17
+ end
18
+ end
19
+
20
+ ListSerializer = ::Tiun::ListSerializer
@@ -0,0 +1,13 @@
1
+ class Tiun::MetaController < ActionController::Base
2
+ #before_action :authenticate_user!
3
+ #before_action :authorize!
4
+
5
+ # GET /meta/
6
+ def index
7
+ joint = Tiun.config.values.reduce({}) {|res, h| res.merge(h) }
8
+ #binding.pry
9
+ render json: joint.as_json
10
+ end
11
+ end
12
+
13
+ MetaController = Tiun::MetaController
@@ -0,0 +1,105 @@
1
+ require 'tiun/version'
2
+
3
+ module Tiun::Migration
4
+ Proxy = Struct.new(:name, :timestamp, :code) do
5
+ delegate :migrate, :announce, :write, :disable_ddl_transaction, to: :migration
6
+
7
+ def basename
8
+ ""
9
+ # TODO fake name file
10
+ end
11
+
12
+ def scope
13
+ nil
14
+ end
15
+
16
+ def version
17
+ timestamp.utc.strftime("%Y%m%d%H%M%S").to_i
18
+ end
19
+
20
+ private
21
+
22
+ def migration
23
+ @migration ||= load_migration
24
+ end
25
+
26
+ def load_migration
27
+ Tiun.string_eval(code, name)
28
+ name.constantize.new(name, version)
29
+ end
30
+
31
+ def initialize(name, timestamp, code)
32
+ super
33
+ @migration = nil
34
+ end
35
+ end
36
+
37
+ MigrationTemplate = ERB.new(IO.read(File.join(File.dirname(__FILE__), "automigration.rb.erb")))
38
+
39
+ EMBED = <<-END
40
+ class ActiveRecord::MigrationContext
41
+ alias :__old_migrations :migrations
42
+
43
+ def migrations
44
+ (Tiun.migrations | __old_migrations).sort_by(&:version)
45
+ end
46
+ end
47
+ END
48
+
49
+ def base_migration
50
+ @base_migration ||= ActiveRecord::Migration[5.2]
51
+ end
52
+
53
+ def migration_name_for type
54
+ "Create" + type.name.pluralize.camelize
55
+ end
56
+
57
+ def fields_for type
58
+ (type.fields || []) | (defaults.fields || [])
59
+ end
60
+
61
+ def migration_fields_for type
62
+ fields_for(type).map do |attrs|
63
+ type = detect_type(attrs.kind)
64
+ type && { name: attrs.name, type: type, options: {} } || nil
65
+ end.compact
66
+ end
67
+
68
+ def migrations
69
+ return @migrations if @migrations
70
+
71
+ @migrations =
72
+ types.reduce([]) do |migrations, type|
73
+ next migrations if type.parent
74
+
75
+ migration_name = migration_name_for(type)
76
+
77
+ if migrations.any? { |m| m.name == migration_name }
78
+ error :duplicated_migration_found, {name: migration_name, type: type}
79
+ else
80
+ table_title = table_title_for(type)
81
+ migration_fields = migration_fields_for(type)
82
+ code = MigrationTemplate.result(binding)
83
+ timestamp = type.timestamp
84
+
85
+ if timestamp
86
+ migrations << Proxy.new(migration_name, type.timestamp, code)
87
+ else
88
+ error :no_timestamp_defined_for_migration, {name: migration_name, code: code}
89
+ end
90
+ end
91
+
92
+ migrations
93
+ end
94
+ end
95
+
96
+ def search_all_for kind, value
97
+ send(kind).select {|x| x.name == value }
98
+ end
99
+
100
+ def setup_migrations
101
+ Kernel.module_eval(EMBED)
102
+ end
103
+ end
104
+
105
+ Migration = Tiun::Migration
@@ -0,0 +1,48 @@
1
+ class Object
2
+ def blank?
3
+ case self
4
+ when NilClass, FalseClass
5
+ true
6
+ when TrueClass
7
+ false
8
+ when Hash, Array
9
+ !self.any?
10
+ else
11
+ self.to_s == ""
12
+ end
13
+ end
14
+
15
+ def to_os
16
+ if self.respond_to?(:to_h)
17
+ if self.is_a?(OpenStruct)
18
+ self
19
+ else
20
+ OpenStruct.new(self.to_h.map {|(x, y)| [x.to_s, y.respond_to?(:map) && y.to_os || y] }.to_h)
21
+ end
22
+ else
23
+ OpenStruct.new("" => self)
24
+ end
25
+ rescue
26
+ binding.pry
27
+ end
28
+ end
29
+
30
+ class Array
31
+ def to_os
32
+ self.map {|x| x.to_os }
33
+ end
34
+ end
35
+
36
+ class OpenStruct
37
+ def map *args, &block
38
+ res = self.class.new
39
+
40
+ self.each_pair do |key, value|
41
+ res[key] = block[key, value]
42
+ end
43
+
44
+ res
45
+ end
46
+ end
47
+
48
+ Mixins = Object