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,32 @@
1
+ module RailsCoreExtensions
2
+ module ActiveRecordLiquidExtensions
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+ end
7
+
8
+ module ClassMethods
9
+ def validates_liquid(field)
10
+ field = field.to_sym
11
+ before_validation do |record|
12
+ begin
13
+ Liquid::Template.parse(record.send(field))
14
+ rescue Liquid::SyntaxError => e
15
+ record.errors.add(field, "Liquid Syntax Error: #{e}")
16
+ end
17
+ end
18
+ end
19
+
20
+ def liquid_field(field)
21
+ class_eval <<-CODE
22
+ def parsed_#{field}
23
+ Liquid::Template.parse(#{field})
24
+ end
25
+
26
+ def render_#{field}(*args)
27
+ parsed_#{field}.render!(*args)
28
+ end
29
+ CODE
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,134 @@
1
+ module ActiveSupport
2
+ # A typical module looks like this:
3
+ #
4
+ # module M
5
+ # def self.included(base)
6
+ # base.extend ClassMethods
7
+ # base.send(:include, InstanceMethods)
8
+ # scope :disabled, where(:disabled => true)
9
+ # end
10
+ #
11
+ # module ClassMethods
12
+ # ...
13
+ # end
14
+ #
15
+ # module InstanceMethods
16
+ # ...
17
+ # end
18
+ # end
19
+ #
20
+ # By using <tt>ActiveSupport::Concern</tt> the above module could instead be written as:
21
+ #
22
+ # require 'active_support/concern'
23
+ #
24
+ # module M
25
+ # extend ActiveSupport::Concern
26
+ #
27
+ # included do
28
+ # scope :disabled, where(:disabled => true)
29
+ # end
30
+ #
31
+ # module ClassMethods
32
+ # ...
33
+ # end
34
+ #
35
+ # module InstanceMethods
36
+ # ...
37
+ # end
38
+ # end
39
+ #
40
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module and a +Bar+
41
+ # module which depends on the former, we would typically write the following:
42
+ #
43
+ # module Foo
44
+ # def self.included(base)
45
+ # base.class_eval do
46
+ # def self.method_injected_by_foo
47
+ # ...
48
+ # end
49
+ # end
50
+ # end
51
+ # end
52
+ #
53
+ # module Bar
54
+ # def self.included(base)
55
+ # base.method_injected_by_foo
56
+ # end
57
+ # end
58
+ #
59
+ # class Host
60
+ # include Foo # We need to include this dependency for Bar
61
+ # include Bar # Bar is the module that Host really needs
62
+ # end
63
+ #
64
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We could try to hide
65
+ # these from +Host+ directly including +Foo+ in +Bar+:
66
+ #
67
+ # module Bar
68
+ # include Foo
69
+ # def self.included(base)
70
+ # base.method_injected_by_foo
71
+ # end
72
+ # end
73
+ #
74
+ # class Host
75
+ # include Bar
76
+ # end
77
+ #
78
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt> is the +Bar+ module,
79
+ # not the +Host+ class. With <tt>ActiveSupport::Concern</tt>, module dependencies are properly resolved:
80
+ #
81
+ # require 'active_support/concern'
82
+ #
83
+ # module Foo
84
+ # extend ActiveSupport::Concern
85
+ # included do
86
+ # class_eval do
87
+ # def self.method_injected_by_foo
88
+ # ...
89
+ # end
90
+ # end
91
+ # end
92
+ # end
93
+ #
94
+ # module Bar
95
+ # extend ActiveSupport::Concern
96
+ # include Foo
97
+ #
98
+ # included do
99
+ # self.method_injected_by_foo
100
+ # end
101
+ # end
102
+ #
103
+ # class Host
104
+ # include Bar # works, Bar takes care now of its dependencies
105
+ # end
106
+ #
107
+ module Concern
108
+ def self.extended(base)
109
+ base.instance_variable_set("@_dependencies", [])
110
+ end
111
+
112
+ def append_features(base)
113
+ if base.instance_variable_defined?("@_dependencies")
114
+ base.instance_variable_get("@_dependencies") << self
115
+ return false
116
+ else
117
+ return false if base < self
118
+ @_dependencies.each { |dep| base.send(:include, dep) }
119
+ super
120
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
121
+ base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods")
122
+ base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
123
+ end
124
+ end
125
+
126
+ def included(base = nil, &block)
127
+ if base.nil?
128
+ @_included_block = block
129
+ else
130
+ super
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,170 @@
1
+ module RailsCoreExtensions
2
+
3
+ module Breadcrumb
4
+
5
+ def breadcrumbs(object_or_nested_array = nil)
6
+ breadcrumbs_builder_for(object_or_nested_array).breadcrumbs
7
+ end
8
+
9
+
10
+ private
11
+
12
+ def breadcrumbs_builder_for(object_or_nested_array)
13
+ BreadcrumbsBuilder.new(self, object_or_nested_array)
14
+ end
15
+
16
+
17
+ class BreadcrumbsBuilder
18
+
19
+ attr_reader :view, :object_or_nested_array, :path
20
+
21
+ def initialize(view, object_or_nested_array = nil)
22
+ @view = view
23
+ @object_or_nested_array = object_or_nested_array || path_builder.nested_array
24
+ @path = path_builder.collection_url
25
+ end
26
+
27
+
28
+ def breadcrumbs
29
+ view.content_tag :ul, :class =>'breadcrumb' do
30
+ (breadcrumb_for_parent + breadcrumb_for_collection + breadcrumb_for_action).html_safe
31
+ end
32
+ end
33
+
34
+
35
+ def breadcrumb_for_object_link
36
+ breadcrumb_for link_to_object
37
+ end
38
+
39
+
40
+ private
41
+
42
+ def breadcrumb_for_parent
43
+ parent = path_builder.parent
44
+ return ''.html_safe unless parent
45
+ BreadcrumbsBuilder.new(view, parent).breadcrumb_for_object_link
46
+ end
47
+
48
+
49
+ def breadcrumb_for_collection
50
+ breadcrumb_for view.link_to(object.class.table_name.titleize, path)
51
+ end
52
+
53
+
54
+ def breadcrumb_for_action
55
+ case action
56
+ when 'new' then breadcrumb_for('New', :class => 'active')
57
+ when 'edit' then breadcrumb_for_object + breadcrumb_for('Edit', :class => 'active')
58
+ else breadcrumb_for_object_name
59
+ end
60
+ end
61
+
62
+
63
+ def breadcrumb_for_object
64
+ if can_show?
65
+ breadcrumb_for_object_link
66
+ else
67
+ breadcrumb_for_object_name
68
+ end
69
+ end
70
+
71
+
72
+ def breadcrumb_for(content, options = {})
73
+ view.content_tag :li, content, options
74
+ end
75
+
76
+
77
+ def breadcrumb_for_object_name
78
+ breadcrumb_for object_name.html_safe
79
+ end
80
+
81
+
82
+ def link_to_object
83
+ view.link_to object_name.html_safe, object_or_nested_array
84
+ end
85
+
86
+
87
+ def can_show?
88
+ view.controller.respond_to?(:show)
89
+ end
90
+
91
+
92
+ def action
93
+ return 'new' if object.new_record?
94
+ view.params[:action]
95
+ end
96
+
97
+
98
+ def object_name
99
+ if object.respond_to?(:name) && object.name.present?
100
+ object.name
101
+ else
102
+ object.to_s
103
+ end
104
+ end
105
+
106
+
107
+ def object
108
+ @object ||= Array(object_or_nested_array).last
109
+ end
110
+
111
+
112
+ def path_builder
113
+ @path_builder ||= if inherited_resources?
114
+ InheritedResourcesPathBuilder.new(view)
115
+ else
116
+ PathBuilder.new(view)
117
+ end
118
+ end
119
+
120
+
121
+ def inherited_resources?
122
+ defined?(InheritedResources) && view.controller.responder == InheritedResources::Responder
123
+ end
124
+
125
+ end
126
+
127
+
128
+ PathBuilder = Struct.new(:view) do
129
+ def nested_array
130
+ (namespaces + [parent] + [view.current_object]).compact
131
+ end
132
+
133
+ def collection_url
134
+ view.objects_path
135
+ end
136
+
137
+ def parent
138
+ view.parent_object
139
+ end
140
+
141
+ def namespaces
142
+ return [] unless view.respond_to?(:namespaces)
143
+ Array(view.namespaces)
144
+ end
145
+ end
146
+
147
+
148
+ InheritedResourcesPathBuilder = Struct.new(:view) do
149
+ def nested_array
150
+ if view.respond_to? :calculate_nested_array
151
+ view.calculate_nested_array
152
+ else
153
+ [parent, view.resource].compact
154
+ end
155
+ end
156
+
157
+ def collection_url
158
+ view.collection_url
159
+ end
160
+
161
+ def parent
162
+ return view.parent.becomes(view.parent.class.base_class) if view.respond_to?(:parent)
163
+ view.parent_object
164
+ end
165
+ end
166
+
167
+ end
168
+ end
169
+
170
+ ActionView::Base.send(:include, RailsCoreExtensions::Breadcrumb) if defined?(ActionView::Base)
@@ -0,0 +1,17 @@
1
+ module CachesActionWithoutHost
2
+
3
+ def self.included(controller)
4
+ controller.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def caches_action_without_host(*args)
9
+ options = args.extract_options!
10
+ options ||= {}
11
+ options[:cache_path] ||= proc{|c| c.url_for(c.params).split(/\//, 4).last}
12
+ args << options
13
+ caches_action(*args)
14
+ end
15
+ end
16
+
17
+ end
@@ -0,0 +1,152 @@
1
+ module Concurrency
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+
6
+ def concurrency_safe(*methods)
7
+ options = methods.extract_options!
8
+ methods.each do |method|
9
+ add_concurrency_check(method, options)
10
+ end
11
+ end
12
+
13
+
14
+ def concurrency_safe_method_locked?(method)
15
+ concurrency_cache.read(concurrency_safe_method_cache_name(method)) == 'locked'
16
+ end
17
+
18
+
19
+ def concurrency_cache
20
+ @concurrency_cache ||= ::Rails.cache
21
+ end
22
+
23
+
24
+ def concurrency_cache=(cache)
25
+ [:read,:write,:delete].each do |method|
26
+ raise ConcurrencyCacheException, "#{cache} does not implement #{method}" unless cache.respond_to?(method)
27
+ end
28
+ @concurrency_cache = cache
29
+ end
30
+
31
+
32
+ private
33
+
34
+ def add_concurrency_check(method, options = {})
35
+ method_definition = <<-DEFINITION
36
+ def #{method}_with_concurrency_lock(*args)
37
+ if concurrency_safe_method_locked?(:#{method})
38
+ raise ConcurrentCallException.new(self,:#{method}), "#{self.name}.#{method} is already running"
39
+ end
40
+ lock_concurrency_safe_method(:#{method})
41
+ return_value = nil
42
+ begin
43
+ return_value = #{method}_without_concurrency_lock(*args)
44
+ ensure
45
+ unlock_concurrency_safe_method(:#{method})
46
+ end
47
+ return_value
48
+ end
49
+
50
+ alias_method_chain :#{method}, :concurrency_lock
51
+ DEFINITION
52
+
53
+ if method_type(method, options[:type]) == 'class'
54
+ method_definition = <<-DEFINITION
55
+ class << self
56
+ #{method_definition}
57
+ end
58
+ DEFINITION
59
+ end
60
+
61
+ module_eval method_definition
62
+ end
63
+
64
+
65
+ def lock_concurrency_safe_method(method)
66
+ concurrency_cache.write(concurrency_safe_method_cache_name(method), 'locked')
67
+ end
68
+
69
+
70
+ def unlock_concurrency_safe_method(method)
71
+ concurrency_cache.delete(concurrency_safe_method_cache_name(method))
72
+ end
73
+
74
+
75
+ def concurrency_safe_method_cache_name(method)
76
+ "#{self.name.underscore}_concurrency_safe_class_method_#{method}"
77
+ end
78
+
79
+
80
+ def method_type(method, type = nil)
81
+ types = method_types(method, type)
82
+ raise AmbiguousMethodException.new(self, method), "#{method} for #{self.name} is ambiguous. Please specify the type (instance/class) option" if types.count == 2
83
+ raise NoMethodException.new(self, method), "#{method} is not not a valid method for #{self.name}." if types.blank?
84
+ types.first
85
+ end
86
+
87
+
88
+ def method_types(method, type = nil)
89
+ ['class', 'instance'].select do |mt|
90
+ (type.blank? || type.to_s == mt) && self.send("#{mt}_method?", method)
91
+ end
92
+ end
93
+
94
+
95
+ def class_method?(method)
96
+ self.respond_to?(method, true)
97
+ end
98
+
99
+
100
+ def instance_method?(method)
101
+ (self.instance_methods + self.private_instance_methods).map(&:to_s).include?(method.to_s)
102
+ end
103
+
104
+ end
105
+
106
+
107
+ module InstanceMethods
108
+
109
+ def concurrency_cache
110
+ self.class.concurrency_cache
111
+ end
112
+
113
+
114
+ def concurrency_safe_method_locked?(method)
115
+ concurrency_cache.read(concurrency_safe_method_cache_name(method)) == 'locked'
116
+ end
117
+
118
+
119
+ private
120
+
121
+ def lock_concurrency_safe_method(method)
122
+ concurrency_cache.write(concurrency_safe_method_cache_name(method), 'locked')
123
+ end
124
+
125
+
126
+ def unlock_concurrency_safe_method(method)
127
+ concurrency_cache.delete(concurrency_safe_method_cache_name(method))
128
+ end
129
+
130
+
131
+ def concurrency_safe_method_cache_name(method)
132
+ "#{self.class.name.underscore}_concurrency_safe_instance_method_#{method}"
133
+ end
134
+
135
+ end
136
+
137
+
138
+ class ConcurrencyException < ::Exception; end
139
+ class ConcurrencyCacheException < ConcurrencyException; end
140
+ class MethodException < ConcurrencyException
141
+ attr_reader :object, :method
142
+
143
+ def initialize(object, method)
144
+ @object = object
145
+ @method = method
146
+ end
147
+ end
148
+ class ConcurrentCallException < MethodException; end
149
+ class NoMethodException < MethodException; end
150
+ class AmbiguousMethodException < MethodException; end
151
+
152
+ end