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