admin_it 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|