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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +11 -0
  3. data/README.md +28 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/config/agilibox_manifest.js +2 -0
  6. data/app/assets/javascripts/agilibox/all.coffee +1 -0
  7. data/app/assets/javascripts/agilibox/filters_date.coffee +15 -0
  8. data/app/assets/javascripts/agilibox/form_anchor_referer.coffee +9 -0
  9. data/app/assets/javascripts/agilibox/form_reset.coffee +5 -0
  10. data/app/assets/javascripts/agilibox/modals.coffee +129 -0
  11. data/app/assets/stylesheets/agilibox/all.sass +5 -0
  12. data/app/assets/stylesheets/agilibox/filters.sass +43 -0
  13. data/app/assets/stylesheets/agilibox/flash.sass +6 -0
  14. data/app/assets/stylesheets/agilibox/modals.sass +45 -0
  15. data/app/assets/stylesheets/agilibox/pagination.sass +6 -0
  16. data/app/assets/stylesheets/agilibox/print.sass +74 -0
  17. data/app/controllers/agilibox/application_controller.rb +5 -0
  18. data/app/controllers/agilibox/small_data/filters_controller.rb +36 -0
  19. data/app/controllers/concerns/agilibox/back_url_concern.rb +18 -0
  20. data/app/filters/agilibox/small_data/filter.rb +68 -0
  21. data/app/filters/agilibox/small_data/filter_strategy.rb +5 -0
  22. data/app/filters/agilibox/small_data/filter_strategy_by_date.rb +6 -0
  23. data/app/filters/agilibox/small_data/filter_strategy_by_date_begin.rb +6 -0
  24. data/app/filters/agilibox/small_data/filter_strategy_by_date_end.rb +6 -0
  25. data/app/filters/agilibox/small_data/filter_strategy_by_key_value.rb +16 -0
  26. data/app/filters/agilibox/small_data/filter_strategy_by_tags.rb +11 -0
  27. data/app/filters/agilibox/small_data/filter_strategy_by_time_period.rb +37 -0
  28. data/app/helpers/agilibox/all_helpers.rb +13 -0
  29. data/app/helpers/agilibox/bootstrap_helper.rb +6 -0
  30. data/app/helpers/agilibox/button_helper.rb +160 -0
  31. data/app/helpers/agilibox/filters_helper.rb +68 -0
  32. data/app/helpers/agilibox/form_helper.rb +49 -0
  33. data/app/helpers/agilibox/link_helper.rb +52 -0
  34. data/app/helpers/agilibox/pagination_helper.rb +6 -0
  35. data/app/helpers/agilibox/routes_helper.rb +20 -0
  36. data/app/helpers/agilibox/sorting_helper.rb +50 -0
  37. data/app/helpers/agilibox/text_helper.rb +122 -0
  38. data/app/libs/agilibox/sortable_uuid_generator.rb +11 -0
  39. data/app/models/concerns/agilibox/active_record_uuid_concern.rb +15 -0
  40. data/app/models/concerns/agilibox/default_values_concern.rb +13 -0
  41. data/app/models/concerns/agilibox/model_i18n.rb +25 -0
  42. data/app/models/concerns/agilibox/model_to_s.rb +9 -0
  43. data/app/models/concerns/agilibox/polymorphic_id.rb +34 -0
  44. data/app/models/concerns/agilibox/search.rb +30 -0
  45. data/app/serializers/agilibox/serializers/base.rb +17 -0
  46. data/app/serializers/agilibox/serializers/xlsx.rb +36 -0
  47. data/app/serializers/agilibox/serializers.rb +2 -0
  48. data/app/views/agilibox/search/_form.html.slim +6 -0
  49. data/config/locales/common.en.yml +199 -0
  50. data/config/locales/common.fr.yml +210 -0
  51. data/config/routes.rb +5 -0
  52. data/lib/agilibox/active_record_comma_type_cast.rb +12 -0
  53. data/lib/agilibox/core_and_rails_ext.rb +2 -0
  54. data/lib/agilibox/engine.rb +9 -0
  55. data/lib/agilibox/form_back_url.rb +18 -0
  56. data/lib/agilibox/version.rb +3 -0
  57. data/lib/agilibox.rb +5 -0
  58. data/lib/tasks/agilibox_tasks.rake +4 -0
  59. metadata +115 -0
@@ -0,0 +1,6 @@
1
+ module Agilibox::BootstrapHelper
2
+ def icon(id)
3
+ id = id.to_s.gsub("_", "-")
4
+ content_tag(:span, class: "icon fa fa-#{id}"){}
5
+ end
6
+ end
@@ -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,6 @@
1
+ module Agilibox::PaginationHelper
2
+ def paginate(objects, options = {})
3
+ options = {theme: "twitter-bootstrap-3"}.merge(options)
4
+ super(objects, options).gsub(/>(\s+)</, '><').html_safe
5
+ end
6
+ 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,9 @@
1
+ module Agilibox::ModelToS
2
+ def to_s
3
+ %w(name title label).map do |m|
4
+ return send(m) if respond_to?(m)
5
+ end
6
+
7
+ super
8
+ end
9
+ end
@@ -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