rails_core_extensions 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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.hound.yml +2 -0
  4. data/.rspec +2 -0
  5. data/.ruby-style.yml +233 -0
  6. data/.travis.yml +14 -0
  7. data/Gemfile +2 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +62 -0
  10. data/Rakefile +12 -0
  11. data/gemfiles/rails3.gemfile +13 -0
  12. data/gemfiles/rails4.gemfile +14 -0
  13. data/lib/rails_core_extensions.rb +41 -0
  14. data/lib/rails_core_extensions/action_controller_sortable.rb +22 -0
  15. data/lib/rails_core_extensions/action_view_currency_extensions.rb +26 -0
  16. data/lib/rails_core_extensions/action_view_extensions.rb +62 -0
  17. data/lib/rails_core_extensions/action_view_has_many_extensions.rb +32 -0
  18. data/lib/rails_core_extensions/activatable.rb +38 -0
  19. data/lib/rails_core_extensions/active_model_extensions.rb +47 -0
  20. data/lib/rails_core_extensions/active_record_cache_all_attributes.rb +43 -0
  21. data/lib/rails_core_extensions/active_record_cloning.rb +81 -0
  22. data/lib/rails_core_extensions/active_record_extensions.rb +228 -0
  23. data/lib/rails_core_extensions/active_record_liquid_extensions.rb +32 -0
  24. data/lib/rails_core_extensions/active_support_concern.rb +134 -0
  25. data/lib/rails_core_extensions/breadcrumb.rb +170 -0
  26. data/lib/rails_core_extensions/caches_action_without_host.rb +17 -0
  27. data/lib/rails_core_extensions/concurrency.rb +152 -0
  28. data/lib/rails_core_extensions/position_initializer.rb +27 -0
  29. data/lib/rails_core_extensions/railtie.rb +7 -0
  30. data/lib/rails_core_extensions/sortable.rb +52 -0
  31. data/lib/rails_core_extensions/tasks/position_initializer.rake +12 -0
  32. data/lib/rails_core_extensions/time_with_zone.rb +16 -0
  33. data/lib/rails_core_extensions/version.rb +3 -0
  34. data/rails_core_extensions.gemspec +38 -0
  35. data/spec/action_controller_sortable_spec.rb +52 -0
  36. data/spec/action_view_extensions_spec.rb +25 -0
  37. data/spec/active_model_extensions_spec.rb +130 -0
  38. data/spec/active_record_extensions_spec.rb +126 -0
  39. data/spec/breadcrumb_spec.rb +85 -0
  40. data/spec/concurrency_spec.rb +110 -0
  41. data/spec/position_initializer_spec.rb +48 -0
  42. data/spec/schema.rb +17 -0
  43. data/spec/spec_helper.rb +31 -0
  44. data/spec/spec_helper_model_base.rb +37 -0
  45. data/spec/support/coverage_loader.rb +26 -0
  46. metadata +294 -0
@@ -0,0 +1,22 @@
1
+ module RailsCoreExtensions
2
+ module ActionControllerSortable
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def sortable
9
+ include RailsCoreExtensions::ActionControllerSortable::InstanceMethods
10
+ end
11
+ end
12
+
13
+ module InstanceMethods
14
+ def sort
15
+ RailsCoreExtensions::Sortable.new(params, controller_name).sort
16
+
17
+ render :update do |page|
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,26 @@
1
+ module ActionView
2
+ module Helpers
3
+ module FormTagHelper
4
+ # Create currency selector tag -- see select_tag for options
5
+ def currency_select_tag(name, current_value, options={})
6
+ selectable_options = []
7
+ selectable_options << ["--", nil] if options[:include_blank]
8
+ selectable_options += EnabledCurrency.all.map(&:iso_code) unless EnabledCurrency.all.empty?
9
+ select_tag(name, options_for_select(selectable_options, current_value), options)
10
+ end
11
+ end
12
+
13
+ module DateHelper
14
+ def currency_select(object_name, method, options = {})
15
+ value = options[:object] || EnabledCurrency.base_currency
16
+ currency_select_tag("#{object_name}[#{method}]", value, options.merge(:id => "#{object_name}_#{method}"))
17
+ end
18
+ end
19
+
20
+ class FormBuilder
21
+ def currency_select(method, options = {})
22
+ @template.currency_select(@object_name, method, options.merge(:object => @object.send(method)))
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ module RailsCoreExtensions
2
+ module ActionViewExtensions
3
+ def textilize(content)
4
+ super(h(content)).html_safe
5
+ end
6
+
7
+ # Will recursively parse a Hash and any sub hashes to be normal hashes
8
+ # This is useful when exporting hashes to non ruby systems, which don't understand HashWithIndifferentAccess
9
+ def hashify(element)
10
+ if element.is_a? Hash
11
+ element = element.to_hash if element.is_a?(HashWithIndifferentAccess)
12
+ element.each_pair do |key, value|
13
+ element[key] = hashify(value)
14
+ end
15
+ else
16
+ # Must hashify enumerables encase their sub items are hashes
17
+ # Can't enumerate through string as it returns strings, which are also enumerable (stack level too deep)
18
+ if element.respond_to?(:each) && !element.is_a?(String)
19
+ element.map{ |sub| hashify(sub) }
20
+ else
21
+ element
22
+ end
23
+ end
24
+ end
25
+
26
+ # Generates a tooltip with given text
27
+ # text is textilized before display
28
+ def tooltip(hover_element_id, text, title='')
29
+ content = "<div style='width: 25em'>#{textilize(text)}</div>"
30
+ "<script>" +
31
+ "new Tip('#{hover_element_id}', '#{escape_javascript(content)}',"+
32
+ "{title : '#{escape_javascript title}', className: 'silver_smaller_div',"+
33
+ "showOn: 'mouseover', hideOn: { event: 'mouseout' }, fixed: false});"+
34
+ "</script>"
35
+ end
36
+
37
+ def expandable_list_for(objects, show = 4)
38
+ first, others = objects[0..(show-1)], objects[show..(objects.size-1)]
39
+ first.each do |o|
40
+ yield o
41
+ end
42
+ if others
43
+ content_tag 'div', :id => 'others', :style => 'display: none' do
44
+ others.each do |o|
45
+ yield o
46
+ end
47
+ end
48
+ "#{others.size} Others - ".html_safe + link_to_function("Show/Hide", "$('others').toggle()")
49
+ end
50
+ end
51
+
52
+ def boolean_select_tag(name, *args)
53
+ options = args.extract_options!
54
+ options ||= {}
55
+ opts = [['Yes', '1'], ['No', '0']]
56
+ opts = [blank_option] + opts if options[:include_blank]
57
+ select_tag name, options_for_select(opts, options[:selected])
58
+ end
59
+ end
60
+ end
61
+
62
+ ActionView::Base.send(:include, RailsCoreExtensions::ActionViewExtensions) if defined?(ActionView::Base)
@@ -0,0 +1,32 @@
1
+ module RailsCoreExtensions
2
+ module HasManyExtensions
3
+ module Tags
4
+ def hm_check_box(object_name, method, options = {})
5
+ object = options.delete(:object)
6
+ parent = options.delete(:parent) || instance_variable_get(object_name)
7
+ objects = options.delete(:objects) || parent.send(method)
8
+ check_box_tag("#{object_name}[#{method}][]", object, objects.include?(object), options.merge(:id => "#{object_name}_#{method}"))
9
+ end
10
+
11
+ def hm_empty_array(object_name, method)
12
+ hidden_field_tag "#{object_name}[#{method}][]"
13
+ end
14
+ end
15
+
16
+ module FormBuilder
17
+ def hm_empty_array(method)
18
+ @habtm_fields ||= {}
19
+ @habtm_fields[method] = @object.send(method)
20
+ @template.hm_empty_array(@object_name, method)
21
+ end
22
+
23
+ def hm_check_box(method, object, options = {})
24
+ empty = (hm_empty_array(method) unless @habtm_fields && @habtm_fields[method])
25
+ (empty || '').html_safe + @template.hm_check_box(@object_name, method, options.merge(:object => object, :parent => @object, :objects => @habtm_fields[method]))
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ ActionView::Base.send(:include, RailsCoreExtensions::HasManyExtensions::Tags) if defined?(ActionView::Base)
32
+ ActionView::Helpers::FormBuilder.send(:include, RailsCoreExtensions::HasManyExtensions::FormBuilder) if defined?(ActionView::Base)
@@ -0,0 +1,38 @@
1
+ module Activatable
2
+
3
+ def self.included(controller)
4
+ controller.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def activatable
10
+ include Activatable::InstanceMethods
11
+ end
12
+
13
+ end
14
+
15
+ module InstanceMethods
16
+
17
+ def activate(success_block = nil)
18
+ resource.active = params[:active].presence || false
19
+ action = resource.active ? 'activate' : 'inactivate'
20
+
21
+ resource.save!
22
+
23
+ success_block ||= -> {
24
+ flash[:success] = "#{resource} #{action}d"
25
+ redirect_to(collection_path)
26
+ }
27
+
28
+ success_block.call
29
+
30
+ rescue ActiveRecord::ActiveRecordError => e
31
+ resource.errors.add(:base, "Failed to #{action}: " + e.message)
32
+ flash[:error] = resource.errors.full_messages.to_sentence
33
+ redirect_to(collection_path)
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -0,0 +1,47 @@
1
+ module ActiveModelExtensions
2
+ module Validations
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ # Validates the presence of the required fields identified in a rule-string.
8
+ #
9
+ # Similar to validates_presence_of macro, but is an INSTANCE method.
10
+ # This allows it to vary depending on custom settings.
11
+ #
12
+ # Example:
13
+ # validate_required_fields "field1,field2 or field4"
14
+ #
15
+
16
+ # The string is a CSV of required field rules, where each field rule is:
17
+ # - the name of a required field
18
+ # - OR a set of required field names spearated by 'or' (where only ONE is required)
19
+ #
20
+ class CustomPresenceValidator < ActiveModel::Validator
21
+ def validate(record)
22
+ required_fields = Array.wrap(@options[:attributes]).first.call || []
23
+ return if required_fields.empty?
24
+
25
+ required_fields.flatten.each do |required_field|
26
+ if required_field.include? ' or '
27
+ fields = required_field.split(' or ')
28
+ if fields.all? { |field| record.send(field).to_s.blank? }
29
+ record.errors.add(:base, "One of %s is required" % fields.map(&:humanize).to_sentence)
30
+ end
31
+ else
32
+ if record.send(required_field).to_s.blank?
33
+ record.errors.add(required_field, "is required")
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+
41
+ module ClassMethods
42
+ def validate_presence_by_custom_rules(*with)
43
+ validates_with CustomPresenceValidator, _merge_attributes(with)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,43 @@
1
+ module ActiveRecordCacheAllAttributes
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ end
5
+
6
+ module InstanceMethods
7
+ def clear_attribute_cache
8
+ self.class.cache.delete("#{self.class.name}.attribute_cache") if self.class.should_cache?
9
+ end
10
+ end
11
+
12
+ module ClassMethods
13
+ def cache
14
+ Rails.cache
15
+ end
16
+
17
+ def attribute_cache
18
+ cache_key = "#{self.name}.attribute_cache"
19
+ if self.should_cache?
20
+ cache.read(cache_key) || self.generate_cache(cache_key)
21
+ else
22
+ self.generate_attributes_hash
23
+ end
24
+ end
25
+
26
+ def generate_attributes_hash
27
+ scope = self
28
+ scope = scope.ordered if respond_to?(:ordered)
29
+ Hash[scope.all.map { |o| [o.send(self.cache_attributes_by), o.attributes] }]
30
+ end
31
+
32
+ def generate_cache(cache_key)
33
+ if (cache_value = generate_attributes_hash)
34
+ cache.write(cache_key, cache_value)
35
+ end
36
+ cache_value
37
+ end
38
+
39
+ def should_cache?
40
+ Rails.configuration.action_controller.perform_caching
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,81 @@
1
+ module ActiveRecordCloning
2
+
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+
9
+ def inherited(subclass)
10
+ super
11
+ subclass.cloned_attributes_hash = cloned_attributes_hash
12
+ end
13
+
14
+ def attributes_included_in_cloning
15
+ cloned_attributes_hash[:include].dup
16
+ end
17
+
18
+ def attributes_excluded_from_cloning
19
+ cloned_attributes_hash[:exclude].dup
20
+ end
21
+
22
+ def clones_attributes(*attributes)
23
+ cloned_attributes_hash[:include] = attributes.map(&:to_sym)
24
+ end
25
+
26
+ def clones_attributes_except(*attributes)
27
+ cloned_attributes_hash[:exclude] = attributes.map(&:to_sym)
28
+ end
29
+
30
+ protected
31
+
32
+ def cloned_attributes_hash
33
+ @cloned_attributes ||= {:include => [], :exclude => []}
34
+ end
35
+
36
+ def cloned_attributes_hash=(attributes_hash)
37
+ @cloned_attributes = attributes_hash
38
+ end
39
+
40
+ end
41
+
42
+ module InstanceMethods
43
+
44
+ def self.included(base)
45
+ base.class_eval %q{
46
+ alias_method :base_clone_attributes, :clone_attributes
47
+ def clone_attributes(reader_method = :read_attribute, attributes = {})
48
+ allowed = cloned_attributes
49
+ base_clone_attributes(reader_method, attributes).delete_if { |k,v| !allowed.include?(k.to_sym) }
50
+ end
51
+ }
52
+ end
53
+
54
+ def clone_excluding(excludes=[])
55
+ method = ActiveRecord::Base.instance_methods(false).include?(:clone) ? :clone : :dup
56
+ cloned = send(method)
57
+
58
+ excludes ||= []
59
+ excludes = [excludes] unless excludes.is_a?(Enumerable)
60
+
61
+ excludes.each do |excluded_attr|
62
+ attr_writer = (excluded_attr.to_s + '=').to_sym
63
+ cloned.send attr_writer, nil
64
+ end
65
+
66
+ cloned
67
+ end
68
+
69
+ private
70
+
71
+ def cloned_attributes
72
+ included_attributes = if self.class.attributes_included_in_cloning.empty?
73
+ attribute_names.map(&:to_sym)
74
+ else
75
+ attribute_names.map(&:to_sym) & self.class.attributes_included_in_cloning
76
+ end
77
+ included_attributes - self.class.attributes_excluded_from_cloning
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,228 @@
1
+ module ActiveRecordExtensions
2
+ def self.included(base)
3
+ base.extend ClassMethods
4
+ end
5
+
6
+ module ClassMethods
7
+ # Like establish_connection but postfixes the key with the rails environment
8
+ # e.g. database('hello') in development will look for the database
9
+ # which in config/database.yml is called hello_development
10
+ def database(key)
11
+ establish_connection("#{key}_#{Rails.env}")
12
+ end
13
+
14
+ def cache_all_attributes(options = {})
15
+ method = options[:by] || 'id'
16
+ class_eval <<-CACHE
17
+ after_save :clear_attribute_cache
18
+ after_destroy :clear_attribute_cache
19
+ cattr_accessor :cache_attributes_by
20
+ self.cache_attributes_by = '#{method}'
21
+ CACHE
22
+ extend ActiveRecordCacheAllAttributes::ClassMethods
23
+ include ActiveRecordCacheAllAttributes::InstanceMethods
24
+ end
25
+
26
+ # Create a new object from the attributes passed in
27
+ # OR update an existing
28
+ #
29
+ # If an :id attribute is present it will assume it's an existing record, and needs update
30
+ def new_or_update!(hash={}, options = {:hard_update => true})
31
+ hash.symbolize_keys!
32
+ if hash[:id].blank?
33
+ self.new(hash)
34
+ else
35
+ rec = self.find(hash[:id])
36
+ if options[:hard_update]
37
+ rec.update_attributes!(hash.except(:id))
38
+ else
39
+ rec.update_attributes(hash.except(:id))
40
+ end
41
+ rec
42
+ end
43
+ end
44
+
45
+ def enum(field, values, options = {})
46
+ const_set("#{field.to_s.upcase}_OPTIONS", values)
47
+
48
+ select_options = if values.is_a?(Array)
49
+ values.map.with_index{|v, i| [v.to_s.humanize, i]}
50
+ elsif values.is_a?(Hash)
51
+ values.values.map.with_index{|v, i| [v, i]}
52
+ end
53
+ const_set("#{field.to_s.upcase}_SELECT_OPTIONS", select_options)
54
+
55
+ values.each.with_index do |value, i|
56
+ const_set("#{field.to_s.upcase}_#{value.to_s.upcase}", i)
57
+ method_name = options[:short_name] ? "#{value}?" : "#{field}_#{value}?"
58
+ class_eval <<-ENUM
59
+ def #{method_name}
60
+ #{field} == #{i}
61
+ end
62
+ ENUM
63
+ end
64
+ class_eval <<-ENUM
65
+ def #{field}_name
66
+ #{field.to_s.upcase}_OPTIONS[#{field}]
67
+ end
68
+ ENUM
69
+ end
70
+
71
+ def optional_fields(*possible_fields)
72
+ @optional_fields_loader = possible_fields.pop if possible_fields.last.is_a?(Proc)
73
+
74
+ class << self
75
+ def enabled_fields
76
+ @enabled_fields || @optional_fields_loader.try(:call)
77
+ end
78
+
79
+ def enabled_fields=(fields)
80
+ @enabled_fields = fields
81
+ end
82
+ end
83
+
84
+ possible_fields.each do |field|
85
+ instance_eval <<-EVAL
86
+ def #{field}_enabled?
87
+ enabled_fields.include?(:#{field})
88
+ end
89
+ EVAL
90
+ end
91
+ end
92
+
93
+
94
+ def position_helpers_for(*collections)
95
+ collections.each do |collection|
96
+ class_eval <<-EVAL
97
+ after_save do |record|
98
+ record.rebalance_#{collection.to_s.singularize}_positions!
99
+ end
100
+
101
+ def assign_#{collection.to_s.singularize}_position(object)
102
+ object.position = (#{collection}.last.try(:position) || 0) + 1 unless object.position
103
+ end
104
+
105
+ def rebalance_#{collection.to_s.singularize}_positions!(object = nil)
106
+ reload
107
+ #{collection}.sort_by(&:position).each_with_index do |o, index|
108
+ if o.position != (index + 1)
109
+ o.update_attribute(:position, index + 1)
110
+ end
111
+ end
112
+ end
113
+ EVAL
114
+ end
115
+ end
116
+
117
+
118
+ # Validates presence of -- but works on parent within accepts_nested_attributes
119
+ #
120
+ def validates_presence_of_parent(foreign_key)
121
+ after_save do |record|
122
+ unless record.send(foreign_key)
123
+ record.errors.add_on_blank(foreign_key)
124
+ raise ActiveRecord::ActiveRecordError, record.errors.full_messages.to_sentence
125
+ end
126
+ end
127
+ end
128
+
129
+
130
+ # Run a block, being respectful of connection pools
131
+ #
132
+ # Useful for when you're not in the standard rails request process,
133
+ # since normally rails will take, then clear you're connection in the
134
+ # processing of the request.
135
+ #
136
+ # If you don't do this in, say, a command line task with threads, then
137
+ # you'll run out of connections after 5 x Threads are run simultaneously...
138
+ def with_connection_pooling
139
+ # Verify active connections and remove and disconnect connections
140
+ # associated with stale threads.
141
+ ActiveRecord::Base.verify_active_connections!
142
+
143
+ yield
144
+
145
+ # This code checks in the connection being used by the current thread back
146
+ # into the connection pool. It repeats this if you are using multiple
147
+ # connection pools. It will not disconnect the connection.
148
+ #
149
+ # Returns any connections in use by the current thread back to the pool,
150
+ # and also returns connections to the pool cached by threads that are no
151
+ # longer alive.
152
+ ActiveRecord::Base.clear_active_connections!
153
+
154
+ end
155
+
156
+
157
+ def translate(key, options = {})
158
+ klass = self
159
+ klass = klass.superclass while klass.superclass != ActiveRecord::Base
160
+ I18n.translate key, options.merge(:scope => klass.name.tableize.singularize)
161
+ end
162
+
163
+
164
+ def t(key, options = {})
165
+ self.translate(key, options)
166
+ end
167
+
168
+ end
169
+
170
+
171
+ module InstanceMethods
172
+
173
+
174
+ def all_errors
175
+ errors_hash = {}
176
+ self.errors.each do |attr, msg|
177
+ (errors_hash[attr] ||= []) << if self.respond_to?(attr) && (record_attr = self.send(attr)).is_a?(ActiveRecord::Base)
178
+ record_attr.all_errors
179
+ else
180
+ msg
181
+ end
182
+ end
183
+ errors_hash
184
+ end
185
+
186
+
187
+ def to_drop
188
+ @drop_class ||= (self.class.name+'Drop').constantize
189
+ @drop_class.new(self)
190
+ end
191
+ alias_method :to_liquid, :to_drop
192
+
193
+
194
+ # A unique id - even if you are unsaved!
195
+ def unique_id
196
+ id || @generated_dom_id || (@generated_dom_id = Time.now.to_f.to_s.gsub('.', '_'))
197
+ end
198
+
199
+
200
+ #getting audits
201
+ def audit_log
202
+ return (self.methods.include?('audits') ? self.audits : [])
203
+ end
204
+
205
+
206
+
207
+ private
208
+
209
+ def t(key, options = {})
210
+ self.class.translate(key, options)
211
+ end
212
+
213
+
214
+ def transfer_records(klass, objects, options = {})
215
+ record_ids = objects.flat_map { |o|
216
+ o.send(klass.name.underscore + '_ids')
217
+ }
218
+ unless record_ids.empty?
219
+ options[:foreign_key] ||= self.class.name.underscore + '_id'
220
+ update_options = options.except(:foreign_key)
221
+ update_options[options[:foreign_key]] = id
222
+ klass.where(id: record_ids).update_all(update_options)
223
+ end
224
+ end
225
+
226
+ end
227
+
228
+ end