agilibox 1.0.0

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