agilibox 1.0.0
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/CHANGELOG.md +11 -0
- data/README.md +28 -0
- data/Rakefile +37 -0
- data/app/assets/config/agilibox_manifest.js +2 -0
- data/app/assets/javascripts/agilibox/all.coffee +1 -0
- data/app/assets/javascripts/agilibox/filters_date.coffee +15 -0
- data/app/assets/javascripts/agilibox/form_anchor_referer.coffee +9 -0
- data/app/assets/javascripts/agilibox/form_reset.coffee +5 -0
- data/app/assets/javascripts/agilibox/modals.coffee +129 -0
- data/app/assets/stylesheets/agilibox/all.sass +5 -0
- data/app/assets/stylesheets/agilibox/filters.sass +43 -0
- data/app/assets/stylesheets/agilibox/flash.sass +6 -0
- data/app/assets/stylesheets/agilibox/modals.sass +45 -0
- data/app/assets/stylesheets/agilibox/pagination.sass +6 -0
- data/app/assets/stylesheets/agilibox/print.sass +74 -0
- data/app/controllers/agilibox/application_controller.rb +5 -0
- data/app/controllers/agilibox/small_data/filters_controller.rb +36 -0
- data/app/controllers/concerns/agilibox/back_url_concern.rb +18 -0
- data/app/filters/agilibox/small_data/filter.rb +68 -0
- data/app/filters/agilibox/small_data/filter_strategy.rb +5 -0
- data/app/filters/agilibox/small_data/filter_strategy_by_date.rb +6 -0
- data/app/filters/agilibox/small_data/filter_strategy_by_date_begin.rb +6 -0
- data/app/filters/agilibox/small_data/filter_strategy_by_date_end.rb +6 -0
- data/app/filters/agilibox/small_data/filter_strategy_by_key_value.rb +16 -0
- data/app/filters/agilibox/small_data/filter_strategy_by_tags.rb +11 -0
- data/app/filters/agilibox/small_data/filter_strategy_by_time_period.rb +37 -0
- data/app/helpers/agilibox/all_helpers.rb +13 -0
- data/app/helpers/agilibox/bootstrap_helper.rb +6 -0
- data/app/helpers/agilibox/button_helper.rb +160 -0
- data/app/helpers/agilibox/filters_helper.rb +68 -0
- data/app/helpers/agilibox/form_helper.rb +49 -0
- data/app/helpers/agilibox/link_helper.rb +52 -0
- data/app/helpers/agilibox/pagination_helper.rb +6 -0
- data/app/helpers/agilibox/routes_helper.rb +20 -0
- data/app/helpers/agilibox/sorting_helper.rb +50 -0
- data/app/helpers/agilibox/text_helper.rb +122 -0
- data/app/libs/agilibox/sortable_uuid_generator.rb +11 -0
- data/app/models/concerns/agilibox/active_record_uuid_concern.rb +15 -0
- data/app/models/concerns/agilibox/default_values_concern.rb +13 -0
- data/app/models/concerns/agilibox/model_i18n.rb +25 -0
- data/app/models/concerns/agilibox/model_to_s.rb +9 -0
- data/app/models/concerns/agilibox/polymorphic_id.rb +34 -0
- data/app/models/concerns/agilibox/search.rb +30 -0
- data/app/serializers/agilibox/serializers/base.rb +17 -0
- data/app/serializers/agilibox/serializers/xlsx.rb +36 -0
- data/app/serializers/agilibox/serializers.rb +2 -0
- data/app/views/agilibox/search/_form.html.slim +6 -0
- data/config/locales/common.en.yml +199 -0
- data/config/locales/common.fr.yml +210 -0
- data/config/routes.rb +5 -0
- data/lib/agilibox/active_record_comma_type_cast.rb +12 -0
- data/lib/agilibox/core_and_rails_ext.rb +2 -0
- data/lib/agilibox/engine.rb +9 -0
- data/lib/agilibox/form_back_url.rb +18 -0
- data/lib/agilibox/version.rb +3 -0
- data/lib/agilibox.rb +5 -0
- data/lib/tasks/agilibox_tasks.rake +4 -0
- metadata +115 -0
@@ -0,0 +1,160 @@
|
|
1
|
+
module Agilibox::ButtonHelper
|
2
|
+
def bs_button(url, options = {})
|
3
|
+
action = options.delete(:action)
|
4
|
+
icon = options.delete(:icon)
|
5
|
+
text = options.delete(:text) || t("actions.#{action}")
|
6
|
+
title = options.delete(:title) || text
|
7
|
+
|
8
|
+
text = "#{icon icon} <span>#{text}</span>".html_safe
|
9
|
+
|
10
|
+
options = {
|
11
|
+
:class => "btn btn-xs link_#{action}",
|
12
|
+
:title => title,
|
13
|
+
}.deep_merge(options)
|
14
|
+
|
15
|
+
if confirm = options.delete(:confirm)
|
16
|
+
confirm = t("actions.confirm") if confirm == true
|
17
|
+
|
18
|
+
options.deep_merge!(
|
19
|
+
:data => {
|
20
|
+
:confirm => confirm,
|
21
|
+
}
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
link_to(text, url, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def print_button(options = {})
|
29
|
+
options = {
|
30
|
+
:icon => :print,
|
31
|
+
:action => :print,
|
32
|
+
:class => "btn btn-xs btn-default",
|
33
|
+
:onclick => "print(); return false;",
|
34
|
+
}.merge(options)
|
35
|
+
|
36
|
+
bs_button("#", options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_button(url, options = {})
|
40
|
+
options = {
|
41
|
+
:icon => :plus,
|
42
|
+
:action => :create,
|
43
|
+
:class =>"btn btn-xs btn-success link_create"
|
44
|
+
}.merge(options)
|
45
|
+
|
46
|
+
bs_button(url, options)
|
47
|
+
end
|
48
|
+
|
49
|
+
def read_button(url, options = {})
|
50
|
+
options = {
|
51
|
+
:icon => "info-circle",
|
52
|
+
:action => :read,
|
53
|
+
}.merge(options)
|
54
|
+
|
55
|
+
bs_button(url, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
def download_button(url, options = {})
|
59
|
+
options = {
|
60
|
+
:icon => "cloud-download",
|
61
|
+
:action => :download,
|
62
|
+
:download => url,
|
63
|
+
}.merge(options)
|
64
|
+
|
65
|
+
bs_button(url, options)
|
66
|
+
end
|
67
|
+
|
68
|
+
def export_button(url, options = {})
|
69
|
+
ext = url.split(".").last
|
70
|
+
|
71
|
+
if ext.length <= 4
|
72
|
+
action = :"export_#{ext}"
|
73
|
+
else
|
74
|
+
action = :export
|
75
|
+
end
|
76
|
+
|
77
|
+
options = {
|
78
|
+
:icon => "cloud-download",
|
79
|
+
:action => action,
|
80
|
+
:download => url,
|
81
|
+
}.merge(options)
|
82
|
+
|
83
|
+
bs_button(url, options)
|
84
|
+
end
|
85
|
+
|
86
|
+
def update_button(url, options = {})
|
87
|
+
options = {
|
88
|
+
:icon => :pencil,
|
89
|
+
:action => :update,
|
90
|
+
}.merge(options)
|
91
|
+
|
92
|
+
bs_button(url, options)
|
93
|
+
end
|
94
|
+
|
95
|
+
def delete_button(url, options = {})
|
96
|
+
options = {
|
97
|
+
:icon => :trash,
|
98
|
+
:action => :delete,
|
99
|
+
:class => "btn btn-xs btn-danger link_delete",
|
100
|
+
:confirm => true,
|
101
|
+
:method => :delete,
|
102
|
+
}.merge(options)
|
103
|
+
|
104
|
+
bs_button(url, options)
|
105
|
+
end
|
106
|
+
|
107
|
+
def copy_button(url, options = {})
|
108
|
+
options = {
|
109
|
+
:icon => :copy,
|
110
|
+
:action => :copy,
|
111
|
+
}.merge(options)
|
112
|
+
|
113
|
+
bs_button(url, options)
|
114
|
+
end
|
115
|
+
|
116
|
+
def complete_button(url, options = {})
|
117
|
+
options = {
|
118
|
+
:icon => :check,
|
119
|
+
:action => :complete,
|
120
|
+
:class => "btn btn-xs btn-success link_complete",
|
121
|
+
:confirm => true,
|
122
|
+
:method => :patch,
|
123
|
+
}.merge(options)
|
124
|
+
|
125
|
+
bs_button(url, options)
|
126
|
+
end
|
127
|
+
|
128
|
+
def snooze_button(url, options = {})
|
129
|
+
options = {
|
130
|
+
:icon => :"clock-o",
|
131
|
+
:action => :snooze,
|
132
|
+
:confirm => true,
|
133
|
+
:method => :patch,
|
134
|
+
}.merge(options)
|
135
|
+
|
136
|
+
bs_button(url, options)
|
137
|
+
end
|
138
|
+
|
139
|
+
def lock_button(url, options = {})
|
140
|
+
options = {
|
141
|
+
:icon => :lock,
|
142
|
+
:action => :lock,
|
143
|
+
:confirm => true,
|
144
|
+
:method => :patch,
|
145
|
+
}.merge(options)
|
146
|
+
|
147
|
+
bs_button(url, options)
|
148
|
+
end
|
149
|
+
|
150
|
+
def unlock_button(url, options = {})
|
151
|
+
options = {
|
152
|
+
:icon => :unlock,
|
153
|
+
:action => :unlock,
|
154
|
+
:confirm => true,
|
155
|
+
:method => :patch,
|
156
|
+
}.merge(options)
|
157
|
+
|
158
|
+
bs_button(url, options)
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Agilibox::FiltersHelper
|
2
|
+
def filter_submit_button(options = {})
|
3
|
+
options[:class] ||= "btn btn-default submit filter-submit"
|
4
|
+
options[:type] ||= "submit"
|
5
|
+
options[:value] ||= "submit"
|
6
|
+
|
7
|
+
text = options.delete(:text) || t("actions.filter")
|
8
|
+
icon = options.delete(:icon) || "filter"
|
9
|
+
|
10
|
+
content_tag(:button, options) do
|
11
|
+
icon(icon) + " " + text
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def filter_reset_button(options = {})
|
16
|
+
options[:class] ||= "btn btn-default reset filter-reset"
|
17
|
+
options[:type] ||= "submit"
|
18
|
+
options[:value] ||= "reset"
|
19
|
+
|
20
|
+
text = options.delete(:text) || t("actions.reset")
|
21
|
+
icon = options.delete(:icon) || "rotate-left"
|
22
|
+
|
23
|
+
content_tag(:button, options) do
|
24
|
+
icon(icon) + " " + text
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def filter_buttons
|
29
|
+
filter_reset_button + filter_submit_button
|
30
|
+
end
|
31
|
+
|
32
|
+
def filters_form(options = {}, &block)
|
33
|
+
if options.key?(:buttons)
|
34
|
+
buttons = optins.delete(:buttons)
|
35
|
+
else
|
36
|
+
buttons = true
|
37
|
+
end
|
38
|
+
|
39
|
+
options = {
|
40
|
+
:url => agilibox.small_data_filters_path,
|
41
|
+
:wrapper => :inline_form,
|
42
|
+
}.merge(options)
|
43
|
+
|
44
|
+
html = simple_form_for(:filters, options, &block)
|
45
|
+
|
46
|
+
if buttons
|
47
|
+
html = html.gsub("</form>", "#{filter_buttons}</form>").html_safe
|
48
|
+
end
|
49
|
+
|
50
|
+
html
|
51
|
+
end
|
52
|
+
|
53
|
+
def agilibox_time_periods_for_select
|
54
|
+
{
|
55
|
+
t("time_periods.all_time") => "",
|
56
|
+
t("time_periods.today") => "today",
|
57
|
+
t("time_periods.yesterday") => "yesterday" ,
|
58
|
+
t("time_periods.this_week") => "this_week",
|
59
|
+
t("time_periods.last_week") => "last_week",
|
60
|
+
t("time_periods.this_month") => "this_month",
|
61
|
+
t("time_periods.last_month") => "last_month",
|
62
|
+
t("time_periods.this_year") => "this_year",
|
63
|
+
t("time_periods.last_year") => "last_year",
|
64
|
+
t("time_periods.custom_date") => "custom_date",
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Agilibox::FormHelper
|
2
|
+
def form_buttons(opts = {})
|
3
|
+
back_url = opts[:back_url]
|
4
|
+
back_url = url_for(:back).html_safe if back_url.blank?
|
5
|
+
back_url = URI(back_url).path if back_url.include?("://")
|
6
|
+
|
7
|
+
if opts[:obj].present?
|
8
|
+
if opts[:obj].new_record?
|
9
|
+
submit_action = :create
|
10
|
+
else
|
11
|
+
submit_action = :update
|
12
|
+
end
|
13
|
+
else
|
14
|
+
submit_action = :save
|
15
|
+
end
|
16
|
+
|
17
|
+
content_tag("div", class: "actions") do
|
18
|
+
submit = content_tag(:button, type: :submit, class: "btn btn-sm btn-success") do
|
19
|
+
content_tag(:span, class: "fa fa-save") {} + " " + t("actions.#{submit_action}")
|
20
|
+
end
|
21
|
+
|
22
|
+
cancel = content_tag("a", href: back_url, class: "btn btn-primary btn-sm") do
|
23
|
+
content_tag(:span, class: "fa fa-times"){} + " " + t("actions.cancel")
|
24
|
+
end
|
25
|
+
|
26
|
+
cancel = "" if back_url == false
|
27
|
+
|
28
|
+
submit + cancel
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def horizontal_form_for(obj, opts={}, &block)
|
33
|
+
opts = {
|
34
|
+
:wrapper => "horizontal_form",
|
35
|
+
:html => {
|
36
|
+
:class => "form-horizontal"
|
37
|
+
}
|
38
|
+
}.deep_merge(opts)
|
39
|
+
|
40
|
+
simple_form_for(obj, opts, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def search_form(opts = {})
|
44
|
+
action = opts.delete(:action) || request.fullpath
|
45
|
+
|
46
|
+
render "agilibox/search/form", action: action
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Agilibox::LinkHelper
|
2
|
+
def link_to_object(obj, options = {})
|
3
|
+
return if obj.nil?
|
4
|
+
|
5
|
+
if policy(obj).read?
|
6
|
+
link_to(obj.to_s, engine_polymorphic_path(obj), options)
|
7
|
+
else
|
8
|
+
obj.to_s
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def icon_link_to(icon, name, options = nil, html_options = nil, &block)
|
13
|
+
name = "#{icon(icon)} #{name}".html_safe
|
14
|
+
link_to(name, options, html_options, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def web_link(text, opts = {})
|
18
|
+
return if text.to_s.blank?
|
19
|
+
|
20
|
+
href = text
|
21
|
+
href = "http://#{text}" unless text.include?("://")
|
22
|
+
|
23
|
+
link_to(text, href, opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
def email_link(text, opts = {})
|
27
|
+
return if text.to_s.blank?
|
28
|
+
|
29
|
+
href = "mailto:#{text}"
|
30
|
+
|
31
|
+
link_to(text, href, opts)
|
32
|
+
end
|
33
|
+
|
34
|
+
def tel_link(text, opts = {})
|
35
|
+
return if text.to_s.blank?
|
36
|
+
|
37
|
+
value = text.gsub(" ", "")
|
38
|
+
href = "tel:#{value}"
|
39
|
+
|
40
|
+
link_to(text, href, opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
def twitter_link(text, opts = {})
|
44
|
+
return if text.to_s.blank?
|
45
|
+
|
46
|
+
href = text
|
47
|
+
href = "https://twitter.com/#{text}" unless text.include?("twitter.com")
|
48
|
+
href = "https://#{text}" unless href.include?("://")
|
49
|
+
|
50
|
+
link_to(text, href, opts)
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Agilibox::RoutesHelper
|
2
|
+
def engine_polymorphic_path(obj, opts = {})
|
3
|
+
engine = obj.class.parents[-2]
|
4
|
+
|
5
|
+
if engine.nil?
|
6
|
+
routes = main_app
|
7
|
+
else
|
8
|
+
routes = engine::Engine.routes
|
9
|
+
end
|
10
|
+
|
11
|
+
opts = {
|
12
|
+
:controller => "/#{obj.class.to_s.tableize}",
|
13
|
+
:action => :show,
|
14
|
+
:id => obj.to_param,
|
15
|
+
:only_path => true
|
16
|
+
}.merge(opts)
|
17
|
+
|
18
|
+
routes.url_for(opts)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Agilibox::SortingHelper
|
2
|
+
def sortable_column(name, column)
|
3
|
+
unless column.is_a?(Symbol)
|
4
|
+
raise ArgumentError, "invalid column, please use symbol"
|
5
|
+
end
|
6
|
+
|
7
|
+
current_column, current_direction = sortable_column_order
|
8
|
+
|
9
|
+
if current_column == column
|
10
|
+
if current_direction == :asc
|
11
|
+
name = "#{name} ↓"
|
12
|
+
new_sort_param = "-#{column}"
|
13
|
+
end
|
14
|
+
|
15
|
+
if current_direction == :desc
|
16
|
+
name = "#{name} ↑"
|
17
|
+
new_sort_param = column
|
18
|
+
end
|
19
|
+
|
20
|
+
klass = "sort #{current_direction}"
|
21
|
+
else
|
22
|
+
new_sort_param = column
|
23
|
+
klass = "sort"
|
24
|
+
end
|
25
|
+
|
26
|
+
url_params = params.to_h.symbolize_keys.merge(sort: new_sort_param)
|
27
|
+
|
28
|
+
link_to(name, url_params, class: klass)
|
29
|
+
end
|
30
|
+
|
31
|
+
def sortable_column_order(sort_param = params[:sort])
|
32
|
+
sort_param = sort_param.to_s
|
33
|
+
|
34
|
+
if sort_param.present?
|
35
|
+
if sort_param.start_with?("-")
|
36
|
+
column = sort_param[1..-1].to_sym
|
37
|
+
direction = :desc
|
38
|
+
else
|
39
|
+
column = sort_param.to_sym
|
40
|
+
direction = :asc
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if block_given?
|
45
|
+
yield(column, direction)
|
46
|
+
else
|
47
|
+
[column, direction]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Agilibox::TextHelper
|
2
|
+
include ::ActionView::Helpers::NumberHelper
|
3
|
+
include ::ActionView::Helpers::SanitizeHelper
|
4
|
+
include ::ActionView::Helpers::TextHelper
|
5
|
+
|
6
|
+
def euros(n)
|
7
|
+
currency(n, "€")
|
8
|
+
end
|
9
|
+
|
10
|
+
def currency(n, u)
|
11
|
+
return if n.nil?
|
12
|
+
|
13
|
+
I18n.t("number.currency.format.format")
|
14
|
+
.gsub("%n", number(n))
|
15
|
+
.gsub("%u", u)
|
16
|
+
.gsub(" ", "\u00A0")
|
17
|
+
end
|
18
|
+
|
19
|
+
def percentage(n)
|
20
|
+
return if n.nil?
|
21
|
+
|
22
|
+
(number(n) + " %").gsub(" ", "\u00A0")
|
23
|
+
end
|
24
|
+
|
25
|
+
def number(n)
|
26
|
+
return if n.nil?
|
27
|
+
|
28
|
+
opts = {}
|
29
|
+
|
30
|
+
if n.class.to_s.match(/Float|Decimal/i)
|
31
|
+
opts[:precision] = 2
|
32
|
+
else
|
33
|
+
opts[:precision] = 0
|
34
|
+
end
|
35
|
+
|
36
|
+
opts[:delimiter] = I18n.t("number.format.delimiter")
|
37
|
+
opts[:separator] = I18n.t("number.format.separator")
|
38
|
+
|
39
|
+
number_with_precision(n, opts).gsub(" ", "\u00A0")
|
40
|
+
end
|
41
|
+
|
42
|
+
def date(d)
|
43
|
+
return if d.nil?
|
44
|
+
I18n.l(d)
|
45
|
+
end
|
46
|
+
|
47
|
+
def hours(n)
|
48
|
+
return if n.nil?
|
49
|
+
|
50
|
+
number = number_with_precision(n, precision: 2)
|
51
|
+
text = I18n.t("datetime.prompts.hour").downcase
|
52
|
+
text = text.pluralize if n > 1
|
53
|
+
"#{number} #{text}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def text2html(str)
|
57
|
+
return if str.to_s.blank?
|
58
|
+
|
59
|
+
str = str.gsub("\r", "").strip
|
60
|
+
strip_tags(str).gsub("\n", "<br />").html_safe
|
61
|
+
end
|
62
|
+
|
63
|
+
def lf2br(str)
|
64
|
+
return if str.to_s.blank?
|
65
|
+
|
66
|
+
str.gsub("\r", "").gsub("\n", "<br />").html_safe
|
67
|
+
end
|
68
|
+
|
69
|
+
def info(object, attribute, value_or_options = nil, options = {})
|
70
|
+
if value_or_options.nil?
|
71
|
+
value = object.public_send(attribute)
|
72
|
+
elsif value_or_options.is_a?(Hash)
|
73
|
+
value = object.public_send(attribute)
|
74
|
+
options = value_or_options
|
75
|
+
else
|
76
|
+
value = value_or_options
|
77
|
+
end
|
78
|
+
|
79
|
+
if value.blank?
|
80
|
+
value = options[:default]
|
81
|
+
return if value == :hide
|
82
|
+
end
|
83
|
+
|
84
|
+
label = options[:label] || object.t(attribute)
|
85
|
+
tag = options[:tag] || :div
|
86
|
+
separator = options[:separator] || " : "
|
87
|
+
helper = options[:helper]
|
88
|
+
i18n_key = "#{object.class.to_s.tableize.singularize}/#{attribute}"
|
89
|
+
nested = I18n.t("activerecord.attributes.#{i18n_key}").is_a?(Hash)
|
90
|
+
klass = object.is_a?(Module) ? object : object.class
|
91
|
+
object_type = klass.to_s.split("::").last.underscore
|
92
|
+
|
93
|
+
value = t("yes") if value === true
|
94
|
+
value = t("no") if value === false
|
95
|
+
value = object.t("#{attribute}.#{value}") if nested
|
96
|
+
value = send(helper, value) if helper
|
97
|
+
value = number(value) if value.is_a?(Numeric)
|
98
|
+
value = l(value) if value.is_a?(Time)
|
99
|
+
value = l(value) if value.is_a?(Date)
|
100
|
+
value = value.to_s
|
101
|
+
|
102
|
+
html_label = content_tag(:strong, class: "info-label") { label }
|
103
|
+
span_css_class = "info-value #{object_type}-#{attribute}"
|
104
|
+
html_value = content_tag(:span, class: span_css_class) { value }
|
105
|
+
separator_html = content_tag(:span, class: "info-separator") { separator }
|
106
|
+
|
107
|
+
content_tag(tag, class: "info") do
|
108
|
+
[html_label, separator_html, html_value].join.html_safe
|
109
|
+
end
|
110
|
+
end # def info
|
111
|
+
|
112
|
+
def tags(object)
|
113
|
+
return "" if object.tag_list.empty?
|
114
|
+
|
115
|
+
object.tag_list.map { |tag|
|
116
|
+
content_tag(:span, class: "tag label label-primary"){
|
117
|
+
"#{icon :tag} #{tag}".html_safe
|
118
|
+
}
|
119
|
+
}.join(" ").html_safe
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# Microtime based uuids to be sortable
|
2
|
+
class Agilibox::SortableUUIDGenerator
|
3
|
+
REGEX_WITH_DASHES = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/
|
4
|
+
REGEX_WITHOUT_DASHES = /^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/
|
5
|
+
|
6
|
+
def self.generate
|
7
|
+
prefix = Time.zone.now.strftime("%s%9N").to_i.to_s(16)
|
8
|
+
suffix = SecureRandom.hex(8)
|
9
|
+
(prefix + suffix).gsub(REGEX_WITHOUT_DASHES, '\1-\2-\3-\4-\5')
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Agilibox::ActiveRecordUUIDConcern
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def assign_default_uuid
|
7
|
+
if id.nil? && self.class.columns_hash["id"].type == :uuid
|
8
|
+
self.id = ::Agilibox::SortableUUIDGenerator.generate
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
included do
|
13
|
+
before_save :assign_default_uuid
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Agilibox::DefaultValuesConcern
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def assign_default_values; end
|
5
|
+
|
6
|
+
def assign_default(attribute, value)
|
7
|
+
self.send("#{attribute}=", value) if self.send(attribute).nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
included do
|
11
|
+
after_initialize :assign_default_values
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Agilibox::ModelI18n
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def t(*args)
|
5
|
+
self.class.t(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def ts
|
9
|
+
self.class.ts
|
10
|
+
end
|
11
|
+
|
12
|
+
class_methods do
|
13
|
+
def t(*args)
|
14
|
+
if args.any?
|
15
|
+
human_attribute_name(*args)
|
16
|
+
else
|
17
|
+
model_name.human
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def ts
|
22
|
+
model_name.human(count: 2)
|
23
|
+
end
|
24
|
+
end # class_methods
|
25
|
+
end # Agilibox::ModelI18n
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Agilibox::PolymorphicId
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
def self.polymorphic_id_for(relation_name)
|
6
|
+
module_src = File.read(__FILE__).split("__END__").last
|
7
|
+
module_src = module_src.gsub("relation", relation_name.to_s)
|
8
|
+
send :include, eval(module_src)
|
9
|
+
end
|
10
|
+
|
11
|
+
def guid
|
12
|
+
return nil if new_record?
|
13
|
+
|
14
|
+
"#{self.class.base_class}-#{self.id}"
|
15
|
+
end
|
16
|
+
end # included
|
17
|
+
end # module
|
18
|
+
|
19
|
+
# __END__
|
20
|
+
|
21
|
+
Module.new do
|
22
|
+
def relation_guid
|
23
|
+
return nil if relation_type.blank? || relation_id.blank?
|
24
|
+
|
25
|
+
"#{relation_type}-#{relation_id}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def relation_guid=(guid)
|
29
|
+
return self.relation = nil if guid.blank?
|
30
|
+
|
31
|
+
type, id = guid.split("-", 2)
|
32
|
+
self.relation = type.constantize.find(id)
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Agilibox::Search
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
class_methods do
|
5
|
+
def default_search_fields
|
6
|
+
fields = columns.map do |column|
|
7
|
+
"#{table_name}.#{column.name}"
|
8
|
+
end
|
9
|
+
end # def default_search_fields
|
10
|
+
|
11
|
+
def search(q, *fields)
|
12
|
+
words = q.to_s.parameterize.split("-")
|
13
|
+
fields = default_search_fields if fields.empty?
|
14
|
+
|
15
|
+
sql_query = words.map.with_index do |word, index|
|
16
|
+
fields.map do |field|
|
17
|
+
"(LOWER(CAST(#{field} AS TEXT)) LIKE :w#{index})"
|
18
|
+
end.join(" OR ")
|
19
|
+
end.map{ |e| "(#{e})" }.join(" AND ")
|
20
|
+
|
21
|
+
sql_params_a = words.map.with_index do |word, index|
|
22
|
+
["w#{index}".to_sym, "%#{word}%"]
|
23
|
+
end
|
24
|
+
|
25
|
+
sql_params_h = Hash[sql_params_a]
|
26
|
+
|
27
|
+
self.where(sql_query, sql_params_h)
|
28
|
+
end # def search
|
29
|
+
end # class_methods
|
30
|
+
end # class Agilibox::Search
|