admin_it 1.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/.rubocop.yml +5 -0
- data/.yardopts +3 -0
- data/Gemfile +5 -0
- data/README.md +58 -0
- data/README_RU.md +52 -0
- data/Rakefile +23 -0
- data/admin_it.gemspec +38 -0
- data/app/assets/fonts/admin_it/FontAwesome.otf +0 -0
- data/app/assets/fonts/admin_it/fontawesome-webfont.eot +0 -0
- data/app/assets/fonts/admin_it/fontawesome-webfont.svg +414 -0
- data/app/assets/fonts/admin_it/fontawesome-webfont.ttf +0 -0
- data/app/assets/fonts/admin_it/fontawesome-webfont.woff +0 -0
- data/app/assets/fonts/admin_it/glyphicons-halflings-regular.eot +0 -0
- data/app/assets/fonts/admin_it/glyphicons-halflings-regular.svg +229 -0
- data/app/assets/fonts/admin_it/glyphicons-halflings-regular.ttf +0 -0
- data/app/assets/fonts/admin_it/glyphicons-halflings-regular.woff +0 -0
- data/app/assets/javascript/admin_it/admin_it.js +89 -0
- data/app/assets/javascript/admin_it/bootstrap.min.js +6 -0
- data/app/assets/stylesheets/admin_it/admin_it.css +25 -0
- data/app/assets/stylesheets/admin_it/bootstrap-theme.min.css +7 -0
- data/app/assets/stylesheets/admin_it/bootstrap.min.css +7 -0
- data/app/assets/stylesheets/admin_it/font-awesome.min.css +4 -0
- data/app/views/admin_it/context/_table.html.slim +38 -0
- data/app/views/admin_it/context/_tiles.html.slim +23 -0
- data/app/views/admin_it/edit.html.slim +2 -0
- data/app/views/admin_it/new.html.slim +2 -0
- data/app/views/admin_it/shared/_child.html.slim +15 -0
- data/app/views/admin_it/shared/_fields.html.slim +19 -0
- data/app/views/admin_it/shared/_filters.html.slim +40 -0
- data/app/views/admin_it/shared/_form.html.slim +50 -0
- data/app/views/admin_it/shared/_pagination.html.slim +41 -0
- data/app/views/admin_it/shared/_toolbar.html.slim +30 -0
- data/app/views/admin_it/show.html.slim +6 -0
- data/app/views/admin_it/table.html.slim +2 -0
- data/app/views/admin_it/tiles.html.slim +2 -0
- data/app/views/layouts/admin_it.html.slim +37 -0
- data/app/views/layouts/admin_it_dialog.html.slim +8 -0
- data/config.ru +7 -0
- data/lib/admin_it/config.rb +24 -0
- data/lib/admin_it/context/collection_context.rb +239 -0
- data/lib/admin_it/context/context.rb +232 -0
- data/lib/admin_it/context/show_context.rb +45 -0
- data/lib/admin_it/context/single_context.rb +199 -0
- data/lib/admin_it/context/table_context.rb +66 -0
- data/lib/admin_it/context/tiles_context.rb +31 -0
- data/lib/admin_it/context.rb +6 -0
- data/lib/admin_it/controller.rb +42 -0
- data/lib/admin_it/data/active_record.rb +283 -0
- data/lib/admin_it/data/data_behavior.rb +18 -0
- data/lib/admin_it/data/hash.rb +27 -0
- data/lib/admin_it/data/object.rb +51 -0
- data/lib/admin_it/data.rb +24 -0
- data/lib/admin_it/definitions.rb +89 -0
- data/lib/admin_it/engine.rb +56 -0
- data/lib/admin_it/env.rb +37 -0
- data/lib/admin_it/errors.rb +5 -0
- data/lib/admin_it/field/field.rb +174 -0
- data/lib/admin_it/field.rb +1 -0
- data/lib/admin_it/filters/entity_filter.rb +4 -0
- data/lib/admin_it/filters/field_filter.rb +31 -0
- data/lib/admin_it/filters/filter.rb +150 -0
- data/lib/admin_it/filters/value_filter.rb +52 -0
- data/lib/admin_it/filters.rb +4 -0
- data/lib/admin_it/helpers/field.rb +25 -0
- data/lib/admin_it/helpers/input.rb +22 -0
- data/lib/admin_it/helpers/page.rb +26 -0
- data/lib/admin_it/helpers/table.rb +85 -0
- data/lib/admin_it/helpers/tiles.rb +17 -0
- data/lib/admin_it/helpers/toolbar.rb +40 -0
- data/lib/admin_it/helpers/top_menu.rb +39 -0
- data/lib/admin_it/helpers.rb +12 -0
- data/lib/admin_it/locales/en.yml +14 -0
- data/lib/admin_it/locales/ru.yml +14 -0
- data/lib/admin_it/renderable.rb +18 -0
- data/lib/admin_it/resource.rb +292 -0
- data/lib/admin_it/utils.rb +21 -0
- data/lib/admin_it/version.rb +5 -0
- data/lib/admin_it.rb +19 -0
- data/lib/extend_it/array_of.rb +181 -0
- data/lib/extend_it/asserts.rb +38 -0
- data/lib/extend_it/callbacks.rb +105 -0
- data/lib/extend_it/caller.rb +35 -0
- data/lib/extend_it/class.rb +47 -0
- data/lib/extend_it/dsl.rb +94 -0
- data/lib/extend_it/refines.rb +6 -0
- data/lib/extend_it/symbolize.rb +39 -0
- data/lib/extend_it.rb +5 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/combustion_test.sqlite +0 -0
- data/spec/internal/db/schema.rb +3 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/lib/context/collection_context_spec.rb +20 -0
- data/spec/lib/context/context_spec.rb +28 -0
- data/spec/lib/context/single_context_spec.rb +26 -0
- data/spec/lib/data/object_spec.rb +29 -0
- data/spec/lib/definitions_spec.rb +171 -0
- data/spec/lib/field_spec.rb +62 -0
- data/spec/lib/resource_spec.rb +39 -0
- data/spec/lib/utils_spec.rb +39 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/example_groups/context_example_group.rb +50 -0
- data/spec/support/shared_examples/context.rb +2 -0
- metadata +337 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module AdminIt
|
2
|
+
module ObjectData
|
3
|
+
module Context
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def load_fields
|
10
|
+
all = entity_class.instance_methods(false) - Object.instance_methods
|
11
|
+
getters = all.select do |m|
|
12
|
+
m.to_s =~ /\w\z/ && entity_class.instance_method(m).arity == 0
|
13
|
+
end
|
14
|
+
setters = all.select do |m|
|
15
|
+
m.to_s[-1] == '=' && entity_class.instance_method(m).arity == 1
|
16
|
+
end
|
17
|
+
fields = getters.map do |m|
|
18
|
+
AdminIt::Field.create(
|
19
|
+
m,
|
20
|
+
entity_class,
|
21
|
+
readable: true,
|
22
|
+
writable: setters.include?("#{m}=".to_sym)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
setters.reject! { |m| getters.include?(m.to_s[0..-2].to_sym) }
|
26
|
+
fields.concat(setters.map do |m|
|
27
|
+
name = m.to_s[0..-2].to_sym
|
28
|
+
AdminIt::Field.create(
|
29
|
+
name,
|
30
|
+
entity_class,
|
31
|
+
readable: false,
|
32
|
+
writable: true
|
33
|
+
)
|
34
|
+
end)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module Field
|
40
|
+
protected
|
41
|
+
|
42
|
+
def read_value(entity)
|
43
|
+
entity.send(name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def write_value(entity, value)
|
47
|
+
entity.send("#{name}=", value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require File.join %w(admin_it data data_behavior)
|
2
|
+
require File.join %w(admin_it data object)
|
3
|
+
require File.join %w(admin_it data hash)
|
4
|
+
require File.join %w(admin_it data active_record)
|
5
|
+
|
6
|
+
module AdminIt
|
7
|
+
def self.register_data(entity_class, mod)
|
8
|
+
return if entity_class.nil?
|
9
|
+
@data_modules ||= []
|
10
|
+
@data_modules.unshift [entity_class, mod]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.data_module(entity_class)
|
14
|
+
return nil if entity_class.nil?
|
15
|
+
@data_modules ||= []
|
16
|
+
@data_modules.each do |mod|
|
17
|
+
return mod[1] if entity_class <= mod[0]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
register_data Object, AdminIt::ObjectData
|
22
|
+
register_data Hash, AdminIt::HashData
|
23
|
+
register_data ActiveRecord::Base, AdminIt::ActiveRecordData
|
24
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
#require 'delegate'
|
2
|
+
|
3
|
+
module AdminIt
|
4
|
+
class ResourceDefinition # < SimpleDelegator
|
5
|
+
COLLECTIONS = %i(table tiles list)
|
6
|
+
SINGLE = %i(show new edit)
|
7
|
+
CONTEXTS = COLLECTIONS + SINGLE
|
8
|
+
|
9
|
+
# extend ExtendIt::ArrayOf
|
10
|
+
|
11
|
+
# array_of Context do
|
12
|
+
# scope(:all) { |_| true }
|
13
|
+
# find_by :name
|
14
|
+
# end
|
15
|
+
|
16
|
+
# dsl_array :context, ArrayOfContext, create_entity: :create_context
|
17
|
+
|
18
|
+
def initialize(resource)
|
19
|
+
@resource = resource
|
20
|
+
@resource.contexts.replace(CONTEXTS.map do |name|
|
21
|
+
Object.const_get("AdminIt::#{name.capitalize}Context")
|
22
|
+
.create_class(name, @resource)
|
23
|
+
end)
|
24
|
+
end
|
25
|
+
|
26
|
+
def exclude_context(*args)
|
27
|
+
args.flatten.each do |arg|
|
28
|
+
arg = Utils.assert_symbol_arg(arg) { next }
|
29
|
+
@resource.contexts.reject! { |c| c.context_name == arg }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def exclude_collection
|
34
|
+
@resource.contexts.reject! { |c| c.collection? }
|
35
|
+
end
|
36
|
+
|
37
|
+
def exclude_single
|
38
|
+
@resource.contexts.reject! { |c| c.single? }
|
39
|
+
end
|
40
|
+
|
41
|
+
def contexts(*names, &block)
|
42
|
+
unless names.empty?
|
43
|
+
@resource.contexts.replace(
|
44
|
+
names.flatten.uniq.map { |name| context(name) }
|
45
|
+
)
|
46
|
+
end
|
47
|
+
if block_given?
|
48
|
+
@resource.contexts.each { |context| context.instance_eval(&block) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def context(name, context_class: nil, &block)
|
53
|
+
Utils.assert_symbol_arg!(name, 'name')
|
54
|
+
context = @resource[name]
|
55
|
+
if context.nil?
|
56
|
+
unless context_class <= Context
|
57
|
+
context_class =
|
58
|
+
Object.const_get("AdminIt::#{name.capitalize}Context")
|
59
|
+
end
|
60
|
+
context = context_class.create_class(name, @resource)
|
61
|
+
@resource.contexts << context
|
62
|
+
end
|
63
|
+
context.instance_eval(&block) if block_given?
|
64
|
+
context
|
65
|
+
end
|
66
|
+
|
67
|
+
def all(&block)
|
68
|
+
@resource.contexts.each { |c| c.instance_eval(&block) } if block_given?
|
69
|
+
end
|
70
|
+
|
71
|
+
def single(&block)
|
72
|
+
@resource.singles.each { |c| c.instance_eval(&block) } if block_given?
|
73
|
+
end
|
74
|
+
|
75
|
+
def collection(&block)
|
76
|
+
return unless block_given?
|
77
|
+
@resource.collections.each { |c| c.instance_eval(&block) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def default_context(value)
|
81
|
+
return if @resource[value].nil?
|
82
|
+
@resource.instance_variable_set(:@default_context, value)
|
83
|
+
end
|
84
|
+
|
85
|
+
def icon(value = nil)
|
86
|
+
@resource.instance_variable_set(:@icon, value.to_s)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# require 'simple-navigation'
|
2
|
+
# SimpleNavigation.config_file_paths << File.expand_path(File.join(%w(.. .. config)), __FILE__)
|
3
|
+
|
4
|
+
module AdminIt
|
5
|
+
class Engine < Rails::Engine
|
6
|
+
# paths['app/controllers'] = File.join('lib', 'admin_it', 'controllers')
|
7
|
+
|
8
|
+
config.to_prepare do
|
9
|
+
Rails.application.config.i18n.load_path +=
|
10
|
+
Dir[Engine.root.join('lib', 'admin_it', 'locales', '*.yml')]
|
11
|
+
|
12
|
+
unless File.basename($0) == "rake" && ARGV.include?("db:migrate")
|
13
|
+
Dir[File.join(AdminIt.config.root, '**', '*.rb')].each do |file|
|
14
|
+
require file
|
15
|
+
end
|
16
|
+
end
|
17
|
+
# Assets.register(Rails.application.assets)
|
18
|
+
|
19
|
+
# AdminIt.init
|
20
|
+
|
21
|
+
Engine.routes.draw do
|
22
|
+
AdminIt.resources.each do |name, resource|
|
23
|
+
resources(resource.plural,
|
24
|
+
controller: "admin_it/#{name}",
|
25
|
+
except: [:index]) do
|
26
|
+
resource.collections.each do |context|
|
27
|
+
next unless context.collection?
|
28
|
+
get context.context_name, on: :collection
|
29
|
+
end
|
30
|
+
unless resource.collections.empty?
|
31
|
+
get('/', on: :collection, action: resource.default_context)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
unless AdminIt.resources.empty?
|
36
|
+
name, resource = AdminIt.resources.first
|
37
|
+
get('/',
|
38
|
+
controller: "admin_it/#{name}",
|
39
|
+
action: resource.default_context)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
#AdminIt.compile_menu
|
44
|
+
|
45
|
+
ActionController::Base.module_eval do
|
46
|
+
prepend_view_path 'lib/views'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.config
|
52
|
+
config = AdminIt::Config
|
53
|
+
yield config if block_given?
|
54
|
+
config
|
55
|
+
end
|
56
|
+
end
|
data/lib/admin_it/env.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module AdminIt
|
2
|
+
#
|
3
|
+
# Framework detection methods
|
4
|
+
#
|
5
|
+
module Env
|
6
|
+
# @private
|
7
|
+
def self.framework
|
8
|
+
return @framework unless @framework.nil?
|
9
|
+
gems = Gem.loaded_specs.keys
|
10
|
+
if gems.include?('rails')
|
11
|
+
@framework = :rails
|
12
|
+
elsif gems.include?('sinatra')
|
13
|
+
@framework = :sinatra
|
14
|
+
else
|
15
|
+
@framework = :unknown
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# @private
|
20
|
+
def self.pundit?
|
21
|
+
Gem::Specification.find_by_name('pundit')
|
22
|
+
true
|
23
|
+
rescue Gem::LoadError
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private
|
28
|
+
def self.rails?
|
29
|
+
framework == :rails
|
30
|
+
end
|
31
|
+
|
32
|
+
# @private
|
33
|
+
def self.sinatra?
|
34
|
+
framework == :sinatra
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require File.join %w(extend_it class)
|
2
|
+
require File.join %w(extend_it dsl)
|
3
|
+
require File.join %w(extend_it callbacks)
|
4
|
+
|
5
|
+
module AdminIt
|
6
|
+
#
|
7
|
+
# Describes any field of data
|
8
|
+
#
|
9
|
+
# @author [alexiss]
|
10
|
+
#
|
11
|
+
class Field
|
12
|
+
extend ExtendIt::Class
|
13
|
+
extend DataBehavior
|
14
|
+
include ExtendIt::Callbacks
|
15
|
+
|
16
|
+
TYPES = %i(unknown integer float string date relation enum)
|
17
|
+
|
18
|
+
define_callbacks :initialize#, :configure
|
19
|
+
|
20
|
+
class << self
|
21
|
+
extend ExtendIt::Dsl
|
22
|
+
|
23
|
+
# attr_reader :field_name, :entity_class
|
24
|
+
|
25
|
+
dsl_accessor :display_name do |value = nil|
|
26
|
+
value.nil? ? default_display_name : value.to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
dsl_accessor :type, default: TYPES[0] do |value|
|
30
|
+
TYPES.include?(value) ? value : TYPES[0]
|
31
|
+
end
|
32
|
+
|
33
|
+
dsl_accessor :placeholder do |value = nil|
|
34
|
+
value.nil? ? display_name : value
|
35
|
+
end
|
36
|
+
|
37
|
+
dsl_boolean :readable, :writable, :visible, :sortable
|
38
|
+
|
39
|
+
dsl_block :read, :write, :render, :display
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def default_display_name
|
44
|
+
name.to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
inherited_class_reader :field_name, :entity_class
|
49
|
+
|
50
|
+
def self.create(name, _entity_class,
|
51
|
+
type: :unknown,
|
52
|
+
readable: true,
|
53
|
+
writable: true,
|
54
|
+
visible: true,
|
55
|
+
sortable: true
|
56
|
+
)
|
57
|
+
base = self
|
58
|
+
Class.new(base) do
|
59
|
+
#run_callbacks :configure do
|
60
|
+
@field_name, @entity_class = name, _entity_class
|
61
|
+
import_data_module(base)
|
62
|
+
self.readable = readable
|
63
|
+
self.writable = writable
|
64
|
+
self.visible = visible
|
65
|
+
self.sortable = sortable
|
66
|
+
self.type = type
|
67
|
+
#end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.hide
|
72
|
+
@visible = false
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.show
|
76
|
+
@visible = true
|
77
|
+
end
|
78
|
+
|
79
|
+
class_attr_reader :entity_class, :display_name, :type
|
80
|
+
attr_writer :visible, :readable, :writable
|
81
|
+
|
82
|
+
def initialize(readable: nil, writable: nil, visible: nil, sortable: nil)
|
83
|
+
run_callbacks :initialize do
|
84
|
+
@readable = readable.nil? ? self.class.readable? : readable == true
|
85
|
+
@writable = writable.nil? ? self.class.writable? : writable == true
|
86
|
+
@visible = visible.nil? ? self.class.visible? : visible == true
|
87
|
+
@sortable = sortable.nil? ? self.class.sortable? : sortable == true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def name
|
92
|
+
@name ||= self.class.field_name
|
93
|
+
end
|
94
|
+
|
95
|
+
def readable?
|
96
|
+
@readable == true
|
97
|
+
end
|
98
|
+
|
99
|
+
def writable?
|
100
|
+
@writable == true
|
101
|
+
end
|
102
|
+
|
103
|
+
def visible?
|
104
|
+
@visible == true
|
105
|
+
end
|
106
|
+
|
107
|
+
def sortable?
|
108
|
+
@sortable == true
|
109
|
+
end
|
110
|
+
|
111
|
+
def read(entity)
|
112
|
+
unless readable?
|
113
|
+
fail FieldReadError, "Attempt to read write-only field #{name}"
|
114
|
+
end
|
115
|
+
self.class.read.nil? ? read_value(entity) : self.class.read.call(entity)
|
116
|
+
end
|
117
|
+
|
118
|
+
def show(entity)
|
119
|
+
unless readable?
|
120
|
+
fail FieldReadError, "Attempt to read write-only field #{name}"
|
121
|
+
end
|
122
|
+
self.class.display.nil? ? show_value(entity) : self.class.display.call(entity)
|
123
|
+
end
|
124
|
+
|
125
|
+
def write(entity, value)
|
126
|
+
unless writable?
|
127
|
+
fail FieldWriteError, "Attempt to write read-only field #{name}"
|
128
|
+
end
|
129
|
+
if self.class.write.nil?
|
130
|
+
write_value(entity, value)
|
131
|
+
else
|
132
|
+
self.class.write.call(entity, value)
|
133
|
+
end
|
134
|
+
entity
|
135
|
+
end
|
136
|
+
|
137
|
+
def render(entity, instance: nil)
|
138
|
+
renderer = self.class.render
|
139
|
+
return if renderer.nil?
|
140
|
+
# method used as event emmiter, call block in instance or caller
|
141
|
+
# context if it present
|
142
|
+
if instance.nil?
|
143
|
+
self.class.render.call(entity)
|
144
|
+
else
|
145
|
+
instance.instance_exec(entity, &renderer)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def input(template, entity)
|
150
|
+
typed_method = "#{type}_input".to_sym
|
151
|
+
if respond_to?(typed_method)
|
152
|
+
send typed_method, template, entity
|
153
|
+
else
|
154
|
+
Helpers::Input.new(template, self, entity: entity)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
protected
|
159
|
+
|
160
|
+
def read_value(entity)
|
161
|
+
raise NotImplementedError,
|
162
|
+
"Attempt to read field #{name} with unimplemented reader"
|
163
|
+
end
|
164
|
+
|
165
|
+
def show_value(entity)
|
166
|
+
read_value(entity)
|
167
|
+
end
|
168
|
+
|
169
|
+
def write_value(entity, value)
|
170
|
+
raise NotImplementedError,
|
171
|
+
"Attempt to write to field #{name} with unimplemented writer"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join %w(admin_it field field)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module AdminIt
|
2
|
+
class FieldFilter < Filter
|
3
|
+
class << self
|
4
|
+
attr_reader :field
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
def default_display_name
|
9
|
+
field.nil? ? superclass.default_display_name : field.display_name
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class_attr_reader :field
|
14
|
+
|
15
|
+
def self.create(name, _resource, _field)
|
16
|
+
field_class = create_class(name, _resource)
|
17
|
+
field_class.class_eval do
|
18
|
+
_field = _field.to_sym if _field.is_a?(String)
|
19
|
+
@field =
|
20
|
+
if _field <= Field
|
21
|
+
_field
|
22
|
+
elsif _field.is_a?(Symbol)
|
23
|
+
@resource.fields.find { |fld| fld.field_name == _field }
|
24
|
+
else
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
field_class
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'json'
|
3
|
+
require File.join %w(extend_it class)
|
4
|
+
require File.join %w(extend_it dsl)
|
5
|
+
require File.join %w(extend_it callbacks)
|
6
|
+
require File.join %w(extend_it asserts)
|
7
|
+
require File.join %w(extend_it symbolize)
|
8
|
+
|
9
|
+
using ExtendIt::Symbolize
|
10
|
+
|
11
|
+
module AdminIt
|
12
|
+
class Filter
|
13
|
+
extend ExtendIt::Class
|
14
|
+
extend DataBehavior
|
15
|
+
include ExtendIt::Callbacks
|
16
|
+
extend ExtendIt::Asserts
|
17
|
+
|
18
|
+
REGEXP = /\A(?<name>[a-zA-Z_][a-zA-Z0-9_]*)(?:\((?<params>[^)]*)\))?\z/
|
19
|
+
|
20
|
+
define_callbacks :save
|
21
|
+
|
22
|
+
class << self
|
23
|
+
extend ExtendIt::Dsl
|
24
|
+
|
25
|
+
attr_reader :filter_name, :resource
|
26
|
+
|
27
|
+
dsl_accessor :display_name do |value = nil|
|
28
|
+
value.nil? ? default_display_name : value.to_s
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def create_class(name, _resource)
|
34
|
+
assert_symbol(:name, binding: binding)
|
35
|
+
base = self
|
36
|
+
Class.new(base) do
|
37
|
+
@filter_name, @resource = name, _resource
|
38
|
+
@entity_class = @resource.entity_class
|
39
|
+
import_data_module(base)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_display_name
|
44
|
+
filter_name
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.create(name, _resource)
|
49
|
+
create_class(name, _resource)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.load(str, filters)
|
53
|
+
m = REGEXP.match(str)
|
54
|
+
return nil if m.nil?
|
55
|
+
name = m[:name].to_sym
|
56
|
+
filter_class = filters.find { |f| f.filter_name == name }
|
57
|
+
return nil if filter_class.nil?
|
58
|
+
opts = {}
|
59
|
+
args = m[:params].nil? ? [] : m[:params].split(',').map do |param|
|
60
|
+
param.strip!
|
61
|
+
arr = param.split(':')
|
62
|
+
if arr.size > 1
|
63
|
+
opts[arr[0].strip.to_sym] = arr[1].strip
|
64
|
+
nil
|
65
|
+
else
|
66
|
+
arr[0]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
args << opts unless opts.empty?
|
70
|
+
filter_class.new(*args.compact)
|
71
|
+
end
|
72
|
+
|
73
|
+
class_attr_reader :display_name
|
74
|
+
|
75
|
+
def name
|
76
|
+
@name ||= self.class.filter_name
|
77
|
+
end
|
78
|
+
|
79
|
+
def dump
|
80
|
+
args = []
|
81
|
+
opts = {}
|
82
|
+
result = ''
|
83
|
+
run_callbacks :save, arguments: { arguments: args, options: opts } do
|
84
|
+
result = "#{name}"
|
85
|
+
unless args.empty? && opts.empty?
|
86
|
+
args.concat(opts.map { |k, v| "#{k}:#{v}" })
|
87
|
+
result << "(#{args.join(',')})"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
93
|
+
def change(str)
|
94
|
+
end
|
95
|
+
|
96
|
+
def apply(collection)
|
97
|
+
collection
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
TRUE_VALUES = %i(true yes)
|
103
|
+
FALSE_VALUES = %i(false no)
|
104
|
+
NULL_VALUES = %i(nil null)
|
105
|
+
OCT_REGEXP = /\A0[0-7]+\z/
|
106
|
+
BIN_REGEXP = /\A0[bB][01]+\z/
|
107
|
+
HEX_REGEXP = /\A0[xX]\h+\z/
|
108
|
+
INT_REGEXP = /\A[+\-]?\d+\z/
|
109
|
+
FLOAT_REGEXP = /\A
|
110
|
+
[+\-]?
|
111
|
+
(?:\d*\.\d+(?:[eE][+\-]?\d+)?)|
|
112
|
+
(?:\d+(?:\.\d*)?[eE][+\-]?\d+)
|
113
|
+
\z/x
|
114
|
+
DATE = %q{[0-3]?[0-9][\/.\-][0-3]?[0-9][\/.\-](?:[0-9]{2})?[0-9]{2}}
|
115
|
+
TIME = %q{[0-2]?[0-9][.:\-][0-5]?[0-9]}
|
116
|
+
DATE_REGEXP = /\A#{DATE}\z/
|
117
|
+
TIME_REGEXP = /\A#{TIME}\z/
|
118
|
+
DATETIME_REGEXP = /\A#{DATE}(?:\s+|[\/.\-])#{TIME}\z/
|
119
|
+
|
120
|
+
def parse_argument(arg)
|
121
|
+
return arg unless arg.is_a?(String)
|
122
|
+
arg.strip!
|
123
|
+
sym = arg.downcase.to_sym
|
124
|
+
return nil if NULL_VALUES.include?(sym)
|
125
|
+
return true if TRUE_VALUES.include?(sym)
|
126
|
+
return false if FALSE_VALUES.include?(sym)
|
127
|
+
if (arg[0] == '\'' || arg[0] == '"') && arg[0] == arg[-1]
|
128
|
+
return arg[1..-2]
|
129
|
+
end
|
130
|
+
case arg
|
131
|
+
when OCT_REGEXP then arg.to_i(8)
|
132
|
+
when BIN_REGEXP then arg.to_i(2)
|
133
|
+
when HEX_REGEXP then arg.to_i(16)
|
134
|
+
when INT_REGEXP then arg.to_i(10)
|
135
|
+
when FLOAT_REGEXP then arg.to_f
|
136
|
+
when DATE_REGEXP then Date.parse(arg)
|
137
|
+
when TIME_REGEXP then Time.parse(arg)
|
138
|
+
when DATETIME_REGEXP then DateTime.parse(arg)
|
139
|
+
else JSON.parse(arg)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def create_argument(arg)
|
144
|
+
case arg
|
145
|
+
when String then "\"#{arg.gsub('"', '\\"')}\""
|
146
|
+
else arg.to_s
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module AdminIt
|
2
|
+
class ValueFilter < FieldFilter
|
3
|
+
attr_reader :values
|
4
|
+
|
5
|
+
def initialize(*values, **opts)
|
6
|
+
@values = values.map { |v| parse_argument(v) }.uniq
|
7
|
+
end
|
8
|
+
|
9
|
+
before_save do |arguments: [], options: {}|
|
10
|
+
arguments.concat(@values.map { |v| create_argument(v) })
|
11
|
+
end
|
12
|
+
|
13
|
+
def change(str)
|
14
|
+
return if str.nil? || !str.is_a?(String) || str.empty?
|
15
|
+
@values = [] unless /[+\-]/ =~ str
|
16
|
+
str.split(',').each do |param|
|
17
|
+
param.strip!
|
18
|
+
if param[0] == '-'
|
19
|
+
@values.delete_if { |v| v == parse_argument(param[1..-1]) }
|
20
|
+
else
|
21
|
+
param = param[1..-1] if param[0] == '+'
|
22
|
+
@values << parse_argument(param)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
@values.uniq!
|
26
|
+
end
|
27
|
+
|
28
|
+
def all_values(collection = nil, &block)
|
29
|
+
collection ||= []
|
30
|
+
enum = Enumerator.new do |yielder|
|
31
|
+
values = collection.map { |e| self.class.field.read(e) }
|
32
|
+
values.uniq.each do |value|
|
33
|
+
yileder << {
|
34
|
+
value: value, count: values.count { |v| v == value }
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
block_given? ? enum.each(&block) : enum
|
39
|
+
end
|
40
|
+
|
41
|
+
def value(val)
|
42
|
+
create_argument(val)
|
43
|
+
end
|
44
|
+
|
45
|
+
def apply(collection)
|
46
|
+
return collection if @values.empty?
|
47
|
+
collection.select do |entity|
|
48
|
+
@values.include?(self.class.field.read(entity))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|