cullender 0.0.1

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 (107) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/javascripts/cullender/application.js +13 -0
  6. data/app/assets/javascripts/cullender/rules.js +46 -0
  7. data/app/assets/stylesheets/cullender/application.css +13 -0
  8. data/app/assets/stylesheets/cullender/rules.css.scss +15 -0
  9. data/app/controllers/cullender/rules_controller.rb +68 -0
  10. data/app/controllers/cullender_controller.rb +105 -0
  11. data/app/helpers/cullender/rules_helper.rb +136 -0
  12. data/app/models/cullender/rule.rb +204 -0
  13. data/app/views/cullender/rules/_form.html.erb +20 -0
  14. data/app/views/cullender/rules/_trigger_fields.html.erb +19 -0
  15. data/app/views/cullender/rules/create.js.erb +3 -0
  16. data/app/views/cullender/rules/index.html.erb +25 -0
  17. data/app/views/cullender/rules/new.html.erb +1 -0
  18. data/app/views/cullender/rules/show.html.erb +3 -0
  19. data/config/locales/en.yml +12 -0
  20. data/config/routes.rb +4 -0
  21. data/lib/cullender.rb +96 -0
  22. data/lib/cullender/controllers/filter_logic.rb +89 -0
  23. data/lib/cullender/controllers/scoped_views.rb +17 -0
  24. data/lib/cullender/core_ext/hash.rb +1 -0
  25. data/lib/cullender/core_ext/hash/deep_delete.rb +13 -0
  26. data/lib/cullender/engine.rb +15 -0
  27. data/lib/cullender/engine/routes.rb +306 -0
  28. data/lib/cullender/mapping.rb +139 -0
  29. data/lib/cullender/version.rb +3 -0
  30. data/lib/generators/cullender/cullender_generator.rb +66 -0
  31. data/lib/generators/cullender/install_generator.rb +18 -0
  32. data/lib/generators/cullender/orm_helpers.rb +32 -0
  33. data/lib/generators/cullender/templates/cullender.rb +3 -0
  34. data/lib/generators/cullender/templates/cullender_migration.rb +13 -0
  35. data/lib/tasks/cullender_tasks.rake +4 -0
  36. data/spec/controllers/cullender/rules_controller_spec.rb +180 -0
  37. data/spec/cullender/controllers/filter_logic_spec.rb +233 -0
  38. data/spec/dummy/README.rdoc +28 -0
  39. data/spec/dummy/Rakefile +6 -0
  40. data/spec/dummy/app/assets/javascripts/application.js +17 -0
  41. data/spec/dummy/app/assets/stylesheets/application.css +14 -0
  42. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  43. data/spec/dummy/app/controllers/events_controller.rb +60 -0
  44. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  45. data/spec/dummy/app/models/event.rb +16 -0
  46. data/spec/dummy/app/views/cullender/rules/_form.html.erb +33 -0
  47. data/spec/dummy/app/views/cullender/rules/index.html.erb +24 -0
  48. data/spec/dummy/app/views/cullender/rules/new.html.erb +1 -0
  49. data/spec/dummy/app/views/cullender/rules/show.html.erb +3 -0
  50. data/spec/dummy/app/views/events/_form.html.erb +21 -0
  51. data/spec/dummy/app/views/events/edit.html.erb +6 -0
  52. data/spec/dummy/app/views/events/index.html.erb +27 -0
  53. data/spec/dummy/app/views/events/new.html.erb +5 -0
  54. data/spec/dummy/app/views/events/show.html.erb +10 -0
  55. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  56. data/spec/dummy/bin/bundle +3 -0
  57. data/spec/dummy/bin/rails +4 -0
  58. data/spec/dummy/bin/rake +4 -0
  59. data/spec/dummy/config.ru +4 -0
  60. data/spec/dummy/config/application.rb +23 -0
  61. data/spec/dummy/config/boot.rb +9 -0
  62. data/spec/dummy/config/database.yml +25 -0
  63. data/spec/dummy/config/environment.rb +5 -0
  64. data/spec/dummy/config/environments/development.rb +27 -0
  65. data/spec/dummy/config/environments/production.rb +80 -0
  66. data/spec/dummy/config/environments/test.rb +36 -0
  67. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/dummy/config/initializers/cullender.rb +3 -0
  69. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  70. data/spec/dummy/config/initializers/inflections.rb +16 -0
  71. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  72. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  73. data/spec/dummy/config/initializers/session_store.rb +3 -0
  74. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  75. data/spec/dummy/config/locales/cullender.en.yml +12 -0
  76. data/spec/dummy/config/locales/en.yml +23 -0
  77. data/spec/dummy/config/routes.rb +5 -0
  78. data/spec/dummy/db/development.sqlite3 +0 -0
  79. data/spec/dummy/db/migrate/123344556676_create_cullender_tables.rb +12 -0
  80. data/spec/dummy/db/schema.rb +24 -0
  81. data/spec/dummy/db/test.sqlite3 +0 -0
  82. data/spec/dummy/log/development.log +9987 -0
  83. data/spec/dummy/log/test.log +19786 -0
  84. data/spec/dummy/public/404.html +27 -0
  85. data/spec/dummy/public/422.html +26 -0
  86. data/spec/dummy/public/500.html +26 -0
  87. data/spec/dummy/public/favicon.ico +0 -0
  88. data/spec/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  89. data/spec/dummy/tmp/cache/assets/development/sprockets/268a828ee4997cf5c19106e5f00fcfb8 +0 -0
  90. data/spec/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  91. data/spec/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  92. data/spec/dummy/tmp/cache/assets/development/sprockets/56b2eb9b46b9aca7e86b5132ddf0d9de +0 -0
  93. data/spec/dummy/tmp/cache/assets/development/sprockets/750887406b3e8dacb2b03f986330932d +0 -0
  94. data/spec/dummy/tmp/cache/assets/development/sprockets/750b42e431d194c41f5b1cde4a257e47 +0 -0
  95. data/spec/dummy/tmp/cache/assets/development/sprockets/921642fe740290e9e5e88a706e5a562c +0 -0
  96. data/spec/dummy/tmp/cache/assets/development/sprockets/a967ed3fc5f8406f3ff72180775f1b40 +0 -0
  97. data/spec/dummy/tmp/cache/assets/development/sprockets/ae74bf4fc3fd20e7f7b98860b8ef0f74 +0 -0
  98. data/spec/dummy/tmp/cache/assets/development/sprockets/c7dbd1f5acc2d5bc078363f4f3c70c54 +0 -0
  99. data/spec/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  100. data/spec/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  101. data/spec/dummy/tmp/cache/assets/development/sprockets/d995daf8d6f36c27b6e9d1b4672eaf1e +0 -0
  102. data/spec/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  103. data/spec/helpers/cullender/rules_helper_spec.rb +360 -0
  104. data/spec/models/cullender/rule_spec.rb +425 -0
  105. data/spec/requests/rules_spec.rb +19 -0
  106. data/spec/spec_helper.rb +60 -0
  107. metadata +319 -0
@@ -0,0 +1,204 @@
1
+ module Cullender
2
+ class Rule < ActiveRecord::Base
3
+
4
+ before_create :add_event_percolation
5
+ before_update :modify_event_percolation
6
+ before_destroy :remove_event_percolation
7
+
8
+
9
+ serialize :triggers, Hash
10
+
11
+ validates :name, :presence => true, :uniqueness => true, :length => {
12
+ :minimum => 4,
13
+ :maximum => 20
14
+ }
15
+
16
+ # basic operators ["term", "terms", "string", "range", "prefix", "fuzzy"]
17
+ OPERATORS = {
18
+ "text_field" => {
19
+ I18n.t("cullender.operators.term") => "term",
20
+ I18n.t("cullender.operators.prefix") => "prefix"
21
+ },
22
+ "number_field" => {
23
+ I18n.t("cullender.operators.gt") => "gt",
24
+ I18n.t("cullender.operators.gte") => "gte",
25
+ I18n.t("cullender.operators.lt") => "lt",
26
+ I18n.t("cullender.operators.lte") => "lte",
27
+ I18n.t("cullender.operators.eq") => "term"
28
+ },
29
+ "range_field" => {
30
+ I18n.t("cullender.operators.gt") => "gt",
31
+ I18n.t("cullender.operators.gte") => "gte",
32
+ I18n.t("cullender.operators.lt") => "lt",
33
+ I18n.t("cullender.operators.lte") => "lte",
34
+ I18n.t("cullender.operators.eq") => "term"
35
+ },
36
+ "date" => {
37
+ I18n.t("cullender.operators.from") => "from",
38
+ I18n.t("cullender.operators.to") => "to"
39
+ },
40
+ "datetime" => {
41
+ I18n.t("cullender.operators.from") => "from",
42
+ I18n.t("cullender.operators.to") => "to"
43
+ },
44
+ "select" => {
45
+ I18n.t("cullender.operators.is") => "term",
46
+ I18n.t("cullender.operators.is_not") => "term"
47
+ },
48
+ "check_box" => {
49
+ I18n.t("cullender.operators.eq") => "is"
50
+ }
51
+ }
52
+
53
+ FILTER_MAP = {
54
+ "term" => "==",
55
+ "prefix" => "starts with",
56
+ "gt" => ">",
57
+ "gte" => ">=",
58
+ "lt" => "<",
59
+ "lte" => "<=",
60
+ "from" => ">=",
61
+ "to" => "<="
62
+ }
63
+
64
+ FIELD_TYPE_MAP = {
65
+ 'string' => 'text_field',
66
+ 'integer' => 'number_field',
67
+ 'long' => 'number_field',
68
+ 'double' => 'number_field',
69
+ 'float' => 'number_field',
70
+ 'date' => 'date',
71
+ 'boolean' => 'check_box'
72
+ }
73
+
74
+
75
+ # This method searches elastic search for events that match this specific
76
+ def fire(event)
77
+ event.add_labels(self.labels) unless self.labels.empty?
78
+ # send any notifications
79
+ end
80
+
81
+ # converts the current rule to a proc accepting an an elastic search query object
82
+ def to_query_proc
83
+ Proc.new do
84
+ self.to_query
85
+ end
86
+ end
87
+
88
+ def to_query
89
+ query = ::Tire::Search::Query.new
90
+ if triggers.present?
91
+
92
+ # check if there is only one field
93
+ count = number_of_fields
94
+ if count > 1
95
+ # complex operators ["boolean", "filtered", "dis_max", "nested"]
96
+ convert_to_boolean(query)
97
+ elsif count == 1
98
+ query = convert_to_basic_query(query, triggers.first[0], triggers.first[1])
99
+ else
100
+
101
+ end
102
+ end
103
+ query
104
+ end
105
+
106
+
107
+ def convert_to_boolean(query, params = self.triggers)
108
+ query.boolean do |bool|
109
+ params.each do |key, values|
110
+ if key.to_i > 0
111
+ bool.should do |q|
112
+ convert_to_boolean(q, values)
113
+ end
114
+ else
115
+ bool.must do |q|
116
+ convert_to_basic_query(q, key, values)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ query
122
+ end
123
+
124
+
125
+
126
+ # def to_filter
127
+ # search = ::Tire::Search::Search.new
128
+ # filters_to_or = []
129
+ # search.query.all
130
+ # triggers.each do |key, value|
131
+ # if value.has_key?("o") && value.has_key?("v")
132
+ # search.filter :term, key.to_sym => value["v"]
133
+ # else
134
+ # f = self.to_search(::Tire::Search::Search.new, value).to_hash
135
+ # filters_to_or << f[:filter]
136
+ # end
137
+ # end
138
+ # search.filter :or, filters_to_or unless filters_to_or.empty?
139
+ # search
140
+ # end
141
+
142
+ private
143
+
144
+ def add_event_percolation
145
+ begin
146
+ # debugger
147
+ response = Event.index.register_percolator_query(self.name, :query => self.to_query.to_hash)
148
+ !response.nil?
149
+ rescue Exception => e
150
+ false
151
+ end
152
+ end
153
+
154
+ def modify_event_percolation
155
+ begin
156
+ response = Event.index.unregister_percolator_query(self.name)
157
+ response = Event.index.register_percolator_query(self.name, :query => self.to_query.to_hash)
158
+ !response.nil?
159
+ rescue Exception => e
160
+ false
161
+ end
162
+ end
163
+
164
+ def remove_event_percolation
165
+ begin
166
+ response = Event.index.unregister_percolator_query(self.name, :query => self.to_query.to_hash)
167
+ !response.nil?
168
+ rescue Exception => e
169
+ false
170
+ end
171
+ end
172
+ private
173
+
174
+ ##
175
+ # Calculate the number of fields in the triggers hash
176
+ def number_of_fields(params = self.triggers)
177
+ if params.present?
178
+ params.values.inject(0) do |sum, value|
179
+ if value.is_a?(Hash)
180
+ # puts value.keys.inspect
181
+ sum + (value.keys.include?("o") ? 1 : number_of_fields(value) )
182
+ else
183
+ sum + 1
184
+ end
185
+ end
186
+ else
187
+ 0
188
+ end
189
+ end
190
+
191
+ def convert_to_basic_query(query, field_name, params = {})
192
+ # basic operators ["term", "terms", "string", "range", "prefix", "fuzzy"]
193
+ if (query.is_a?(Tire::Search::Query))
194
+ operator = params["o"]
195
+ value = params["v"]
196
+ query.send(operator.to_sym, field_name, value)
197
+ else
198
+ raise ArgumentError, "Argument is not a Tire::Search::Search."
199
+ end
200
+ query
201
+ end
202
+
203
+ end
204
+ end
@@ -0,0 +1,20 @@
1
+ <%= simple_form_for rule, :html => { :class => "form-horizontal" } do |f| %>
2
+ <%= f.error_notification %>
3
+ <div class="form-inputs">
4
+ <fieldset id="rule">
5
+ <legend>Rule Description</legend>
6
+ <%= f.input :name %>
7
+ <%= f.input :description %>
8
+ <%= f.association :table, :prompt => "Select Type" %>
9
+ </fieldset>
10
+
11
+ <fieldset id="triggers" class="filters">
12
+ <legend>Triggers</legend>
13
+ <%= render 'trigger_fields', :locals => {properties: Event.properties} %>
14
+ </fieldset>
15
+
16
+ <div class="form-actions">
17
+ <%= f.error :base %>
18
+ <%= f.button :submit %>
19
+ </div>
20
+ <% end %>
@@ -0,0 +1,19 @@
1
+ <div id="cullender_triggers">
2
+ <% if params.has_key?("triggers") || params.has_key?("rule") && params["rule"].has_key?("triggers") %>
3
+ <%= render_property_tree("rule[triggers]", mapping, extract_triggers(params)) do |html, at_end, hierarchy| %>
4
+ <div class="controls">
5
+ <%= html %>
6
+ </div>
7
+ <div class="controls">
8
+ <% if at_end < 0 %>
9
+ <%= cullender_collection_or_select(mapping.keys, hierarchy) %>
10
+ <% else %>
11
+ <%= cullender_collection_and_select(mapping.keys, hierarchy) %>
12
+ <% end -%>
13
+ </div>
14
+ <% end %>
15
+ <% end %>
16
+ <div class="controls">
17
+ <%= cullender_collection_or_select(mapping.keys, [], {:base => true}) %>
18
+ </div>
19
+ </div>
@@ -0,0 +1,3 @@
1
+ jQuery(function() {
2
+ $('#cullender_triggers').replaceWith("<%= escape_javascript(render 'trigger_fields', mapping: resource_class.mapping) %>");
3
+ });
@@ -0,0 +1,25 @@
1
+ <h1>Listing rules</h1>
2
+
3
+ <table>
4
+ <thead>
5
+ <tr>
6
+ <th>Name</th>
7
+ <th>Enabled</th>
8
+ <th></th>
9
+ </tr>
10
+ </thead>
11
+
12
+ <tbody>
13
+ <% @rules.each do |rule| %>
14
+ <tr>
15
+ <td><%= link_to rule.name, rule %></td>
16
+ <td><%= link_to 'Edit', rule %></td>
17
+ <td><%= link_to 'Destroy', rule, method: :delete, data: { confirm: 'Are you sure?' } %></td>
18
+ </tr>
19
+ <% end %>
20
+ </tbody>
21
+ </table>
22
+
23
+ <br />
24
+
25
+ <%= link_to 'New Rule', new_rule_path %>
@@ -0,0 +1 @@
1
+ <%= render 'form', :rule => @rule %>
@@ -0,0 +1,3 @@
1
+ <%= render 'form', :rule => @rule %>
2
+
3
+ <%= link_to 'Back', rules_path %>
@@ -0,0 +1,12 @@
1
+ en:
2
+ cullender:
3
+ operators:
4
+ term: "is"
5
+ prefix: "starts with"
6
+ gt: ">"
7
+ gte: ">="
8
+ lte: "<="
9
+ lt: "<"
10
+ eq: "=="
11
+ from: "from"
12
+ to: "to"
@@ -0,0 +1,4 @@
1
+ # Cullender::Engine.routes.draw do
2
+ # resources :rules, :except => :edit
3
+ # resources :events, :only => [:show, :update]
4
+ # end
@@ -0,0 +1,96 @@
1
+ require "tire"
2
+ require "cullender/core_ext/hash"
3
+
4
+ module Rails
5
+ module Generators
6
+ module Migration
7
+ module ClassMethods
8
+ def next_migration_number(dirname) #:nodoc:
9
+ "123344556676"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+
17
+ module Cullender
18
+
19
+ module Controllers
20
+ autoload :ScopedViews, 'cullender/controllers/scoped_views'
21
+ autoload :FilterLogic, "cullender/controllers/filter_logic"
22
+ end
23
+
24
+
25
+ # The parent controller all Devise controllers inherits from.
26
+ # Defaults to ApplicationController. This should be set early
27
+ # in the initialization process and should be set to a string.
28
+ mattr_accessor :parent_controller
29
+ @@parent_controller = "ApplicationController"
30
+
31
+ # The router Devise should use to generate routes. Defaults
32
+ # to :main_app. Should be overriden by engines in order
33
+ # to provide custom routes.
34
+ mattr_accessor :router_name
35
+ @@router_name = nil
36
+
37
+ # Define a set of modules that are called when a mapping is added.
38
+ mattr_reader :helpers
39
+ @@helpers = Set.new
40
+ # @@helpers << Cullender::Controllers::Helpers
41
+
42
+ # Store scopes mappings.
43
+ mattr_reader :mappings
44
+ @@mappings = ActiveSupport::OrderedHash.new
45
+
46
+
47
+ # Scoped views. Since it relies on fallbacks to render default views, it's
48
+ # turned off by default.
49
+ mattr_accessor :scoped_views
50
+ @@scoped_views = false
51
+
52
+ # Default way to setup Cullender. Run rails generate cullender:install to create
53
+ # a fresh initializer with all configuration values.
54
+ def self.setup
55
+ yield self
56
+ end
57
+
58
+ # Regenerates url helpers considering Devise.mapping
59
+ def self.regenerate_helpers!
60
+ # Devise::Controllers::UrlHelpers.remove_helpers!
61
+ # Devise::Controllers::UrlHelpers.generate_helpers!
62
+ end
63
+
64
+
65
+ # Small method that adds a mapping to Devise.
66
+ def self.add_mapping(resource, options)
67
+ mapping = Cullender::Mapping.new(resource, options)
68
+ @@mappings[mapping.name] = mapping
69
+ @@default_scope ||= mapping.name
70
+ @@helpers.each { |h| h.define_helpers(mapping) }
71
+ mapping
72
+ end
73
+
74
+
75
+ class Getter
76
+ def initialize name
77
+ @name = name
78
+ end
79
+
80
+ def get
81
+ ActiveSupport::Dependencies.constantize(@name)
82
+ end
83
+ end
84
+
85
+ def self.ref(arg)
86
+ if defined?(ActiveSupport::Dependencies::ClassCache)
87
+ ActiveSupport::Dependencies::reference(arg)
88
+ Getter.new(arg)
89
+ else
90
+ ActiveSupport::Dependencies.ref(arg)
91
+ end
92
+ end
93
+ end
94
+
95
+ require 'cullender/mapping'
96
+ require "cullender/engine"
@@ -0,0 +1,89 @@
1
+ module Cullender
2
+ module Controllers
3
+ module FilterLogic
4
+ def add_or_filter(key, params, field, should_raise = false)
5
+ if params.present? && params.has_key?(key)
6
+ if should_raise
7
+ params[key] = {1 => params[key], 2 => add_operator(field)}
8
+ else
9
+ idx = params[key].keys.last.to_i + 1
10
+ params[key].deep_merge!({idx => add_operator(field)})
11
+ end
12
+ else
13
+ if should_raise
14
+ params[key] = add_operator(field)
15
+ else
16
+ params[key] = add_operator(field)
17
+ end
18
+ end
19
+ end
20
+
21
+ def add_and_filter(key, params, field)
22
+ clean_field(field) unless field.is_a? String
23
+ if params.present? && params.has_key?(key)
24
+ params[key].deep_merge!(add_operator(field))
25
+ else
26
+ params[key] = add_operator(field)
27
+ end
28
+ end
29
+
30
+ def clean_field(field)
31
+
32
+ field.each do |key, value|
33
+ if value.is_a?(Hash)
34
+ clean_field(value)
35
+ else
36
+ field.delete(key) if value.blank?
37
+ end
38
+ end
39
+ end
40
+
41
+ def remove_and_filter(key, params, field)
42
+ if params.present? && params.has_key?(key)
43
+ if field.is_a? String
44
+ params[key].delete(field)
45
+ else
46
+ params[key].deep_delete(field)
47
+ end
48
+ end
49
+ end
50
+
51
+ def add_operator(field)
52
+ if field.is_a?(Hash)
53
+ hash = {}
54
+ field.each do |key, value|
55
+ if ["o", "v"].include?(key)
56
+ hash = field
57
+ break
58
+ else
59
+ hash.merge!({key => add_operator(value)}) unless ["o", "v"].include?(key)
60
+ end
61
+ end
62
+ elsif
63
+ hash = {field => {"o" => nil}}
64
+ end
65
+ hash
66
+ end
67
+
68
+ def apply_filter_actions(name, filter_params, params)
69
+ # debugger
70
+ updated = false
71
+ if params.has_key?("or") && (params["or"].has_key?("commit") || params["or"].has_key?("raise"))
72
+ nfilter = params.delete("or")
73
+ should_raise = nfilter.has_key?("raise")
74
+ add_or_filter(name, filter_params, should_raise ? nfilter["raise_field"] : nfilter["field"], should_raise) if (nfilter["field"].present? || nfilter["raise_field"].present?)
75
+ updated = true
76
+ elsif params.has_key?("and") && params["and"].has_key?("commit")
77
+ nfilter = params.delete("and")
78
+ add_and_filter(name, filter_params, nfilter["field"]) if nfilter["field"].present?
79
+ updated = true
80
+ elsif params.has_key?("remove")
81
+ nfilter = params.delete("remove")
82
+ remove_and_filter(name, filter_params, nfilter)
83
+ updated = true
84
+ end
85
+ updated
86
+ end
87
+ end
88
+ end
89
+ end