rails_core_extensions 0.0.1

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