cullender 0.0.1

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