active_tools 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: acfe4b265f95ccad971ff61c48084979dd50b044
4
- data.tar.gz: 9d6eecba7d6887738b49cc1df2227e4366f08bf2
3
+ metadata.gz: 6884fd00e3b38429d219a9d533116fa46c824de0
4
+ data.tar.gz: 5fa48a6d56370b0e68729b71ac24977f6c327bfd
5
5
  SHA512:
6
- metadata.gz: 8c3567fda0e3798d24c27483bc433d4cb72283ad9bccd366c48b7ef3f29f569af4b82b997755b35312dfcf5f92aa13020f401510ee4cf524d11a23201ce4bb93
7
- data.tar.gz: 8242b93fed275505b7cfaf7be8a3eb77c08f0440234ad4c8bac53d442830d7f15635f3831d9f611d5a23a5b6860eb845b38e390d8975fd58204a23e3ce453cc2
6
+ metadata.gz: 478603a760745f50193cbe24358e39fefef37792a766b0d31bad36f686b127812deb76f80e9d8d11cdc963a20d3d773287350855d1af7a8db1cb93c44fddf342
7
+ data.tar.gz: c8f2fbae68a570892e9f212a0bc9c1c611867d064e0212eb8668fe4ed3515aa096482dae36dd95ea30ef872a59a5a8dccb10a6bb4767676c0fabfdbc6d7281ce
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .idea
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ delegate_attributes
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.0.0-p247
data/README.md CHANGED
@@ -1,3 +1,58 @@
1
+ # DOCS ARE UNDER CONSTRUCTION
2
+
3
+ ### For now, the most usable feature of active tools is 'custom counters' - my implementation of <t>counter through</t> solution
4
+
5
+ #### Look here! Typical data structure
6
+
7
+ Country
8
+
9
+ class Country # has 'products_count' column
10
+ ...
11
+ end
12
+
13
+ Made in ...
14
+
15
+ class MadeIn # has 'products_count' column
16
+ belongs_to :country
17
+
18
+ custom_counter_cache_for :country => {:made_ins_count => 1, :products_count => :products_count}
19
+
20
+ # So, when MadeIn created/deleted, Country's 'made_ins_count' incremented/decremented by 1 and 'products_count' by MadeIn's 'products_count' value
21
+ ...
22
+ end
23
+
24
+ Category (has parent)
25
+
26
+ class Category # has 'products_count' column
27
+ acts_as_nested_set # has parent and children (!)
28
+
29
+ custom_counter_cache_for "parent*" => {:children_count => 1, :products_count => :products_count}
30
+
31
+ # So, when Category created/deleted, parent's 'children_count' incremented/decremented by 1 and 'products_count' by Category's 'products_count' value
32
+
33
+ end
34
+
35
+ Brand name
36
+
37
+ class Brand # has 'products_count' column
38
+ ...
39
+ end
40
+
41
+ Product itself
42
+
43
+ class Product < ActiveRecord::Base
44
+ belongs_to :category
45
+ belongs_to :brand
46
+ belongs_to :made_in
47
+
48
+ custom_counter_cache_for :made_in => {:products_count => 1, :country => {:products_count => 1}}, :category => {:products_count => 1, "parent*" => {:products_count => 1}}, :brand => {:products_count => 1}
49
+
50
+ # You can use nested options... it is very very useful :)
51
+
52
+ end
53
+
54
+ Thanks
55
+
1
56
  # ActiveTools
2
57
 
3
58
  TODO: Write a gem description
data/active_tools.gemspec CHANGED
@@ -15,6 +15,7 @@ Gem::Specification.new do |gem|
15
15
  gem.rubyforge_project = "active_tools"
16
16
 
17
17
  gem.add_dependency "rails"
18
+ gem.add_development_dependency "rspec"
18
19
 
19
20
  gem.files = `git ls-files`.split($/)
20
21
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
data/copy ADDED
@@ -0,0 +1,2 @@
1
+ #/bin/sh
2
+ rm -rfv /Volumes/library/"All for Ruby"/"Rails Projects"/active_tools && cp -Rv ~/active_tools /Volumes/library/"All for Ruby"/"Rails Projects"/
@@ -2,6 +2,8 @@ module ActiveTools
2
2
  module ActionPack
3
3
  module ActionDispatch
4
4
  module FlashStack
5
+ # Doesn't work with Rails 4.0>
6
+
5
7
  extend ActiveSupport::Concern
6
8
 
7
9
  included do
@@ -0,0 +1,38 @@
1
+ module ActiveTools
2
+ module ActionPack
3
+ module ActionView
4
+ module PerformAsTree
5
+ def self.line(item, children_method, depth = 0, &block)
6
+ yield item, depth
7
+ item.send(children_method).each do |child|
8
+ line(child, children_method, depth+1, &block)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ module OnLoadActionView
16
+
17
+ def perform_as_tree(scope, options = {}, &block)
18
+ options = options.with_indifferent_access
19
+ children_method = options[:children_method]||:children
20
+ parent_method = options[:parent_method]||:parent
21
+ id_key = options[:id]||nil
22
+ scope = case scope
23
+ when ::ActiveRecord::Relation then
24
+ parent_key = scope.klass.reflections[children_method].foreign_key
25
+ scope.where(parent_key => id_key)
26
+ when ::Array, ::Set then
27
+ scope.select {|item| item.send(parent_method) == id_key}
28
+ else
29
+ raise(TypeError, "ActiveRecord::Relation, Array or Set expected, #{scope.class.name} passed!")
30
+ end
31
+ scope.each do |item|
32
+ ActionPack::ActionView::PerformAsTree.line(item, children_method, 0, &block)
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -1,4 +1,5 @@
1
1
  require 'active_tools/action_pack/action_view/tag_attributes'
2
+ require 'active_tools/action_pack/action_view/perform_as_tree'
2
3
 
3
4
  module ActiveTools
4
5
  module ActionPack
@@ -3,57 +3,33 @@ module ActiveTools
3
3
  module DelegateAttributes
4
4
  extend ::ActiveSupport::Concern
5
5
 
6
- included do
7
- class FakeErrors < ::ActiveModel::Errors
8
- private
9
- def normalize_message(attribute, message, options)
10
- message ||= :invalid
11
- end
12
- end
13
-
14
- end
15
-
16
6
  module ClassMethods
17
7
  def delegate_attributes(*args)
18
8
  options = args.extract_options!
19
9
  errors_option = options.delete(:errors)
20
10
  writer_option = options.delete(:writer)
11
+ prefix_option = options.delete(:prefix)
21
12
 
22
13
  writer_regexp = /=\z/
23
14
  readers = args.select {|a| a.to_s !=~ writer_regexp}
24
15
  writers = args.select {|a| a.to_s =~ writer_regexp}
25
16
  if writer_option == true
26
- writers += readers.map {|a| "#{a}="}
17
+ writers |= readers.map {|a| "#{a}="}
27
18
  end
28
19
 
29
20
  class_eval do
30
21
  delegate *(readers + writers), options.dup
22
+ unless errors_option == false
23
+ valid_with options[:to], :attributes => Hash[readers.map {|a| [a, a]}], :fit => errors_option.to_s == "fit", :prefix => prefix_option
24
+ end
31
25
  end
32
-
33
- unless errors_option == false
34
- class_eval <<-EOV
35
- validate do
36
- object = #{options[:to]}
37
- #{"object.instance_variable_set(:@errors, FakeErrors.new(object))" if errors_option.to_s == "fit"}
38
- if !object.valid?
39
- object.errors.messages.each do |attribute, suberrors|
40
- if attribute.to_s.in? %w{#{readers.join(" ")}}
41
- suberrors.each do |suberror|
42
- errors.add(attribute, suberror)
43
- end
44
- end
45
- end
46
- end
47
- end
48
- EOV
49
- end
50
26
  end
51
27
  end
52
28
 
53
29
 
54
30
  end
55
31
 
56
- ::ActiveModel::Validations.send(:include, ActiveModel::DelegateAttributes)
32
+ ::ActiveModel::Validations.send(:include, ActiveModel::DelegateAttributes)
57
33
 
58
34
  end
59
35
 
@@ -0,0 +1,12 @@
1
+ module ActiveTools
2
+ module ActiveModel
3
+ module ValidWith
4
+ class FakeErrors < ::ActiveModel::Errors
5
+ private
6
+ def normalize_message(attribute, message, options)
7
+ message ||= :invalid
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,53 @@
1
+ require 'active_tools/active_model/valid_with/fake_errors'
2
+ module ActiveTools
3
+ module ActiveModel
4
+ module ValidWith
5
+ extend ::ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def valid_with(*args)
9
+ options = args.extract_options!
10
+ object_name = args.first
11
+ passed_attr_map = options.delete(:attributes)
12
+ prefix = options.delete(:prefix)
13
+ raise(TypeError, "Option :attributes must be a Hash. #{passed_attr_map.class} passed!") unless passed_attr_map.is_a?(Hash)
14
+ attr_map_name = :"_valid_with_#{object_name}"
15
+ unless respond_to?(attr_map_name)
16
+ class_attribute attr_map_name
17
+ self.send("#{attr_map_name}=", passed_attr_map.with_indifferent_access)
18
+ else
19
+ self.send(attr_map_name).merge!(passed_attr_map)
20
+ end
21
+
22
+ if passed_attr_map.any?
23
+ class_eval <<-EOV
24
+ validate do
25
+ if object = #{object_name}
26
+ #{"object.instance_variable_set(:@errors, ActiveTools::ActiveModel::ValidWith::FakeErrors.new(object))" if options[:fit] == true}
27
+ if !object.valid?
28
+ object.errors.messages.each do |attribute, suberrors|
29
+ if local_attribute = self.#{attr_map_name}[attribute]
30
+ suberrors.each do |suberror|
31
+ errors.add(["#{prefix}", local_attribute].select(&:present?).join("_"), suberror)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ EOV
39
+ end
40
+ end
41
+ end
42
+
43
+
44
+ end
45
+
46
+ ::ActiveModel::Validations.send(:include, ActiveModel::ValidWith)
47
+
48
+ end
49
+
50
+ module OnLoadActiveRecord
51
+ end
52
+
53
+ end
@@ -0,0 +1,195 @@
1
+ module ActiveTools
2
+ module ActiveRecord
3
+ module AdaptiveBelongsTo
4
+ class Adapter
5
+ attr_reader :association, :options
6
+
7
+ delegate :target, :target_id, :klass, :owner, :reflection, :to => :association
8
+
9
+ def initialize(association, options = {})
10
+ @association = association
11
+ @options = options.with_indifferent_access
12
+ @foreign_key = reflection.foreign_key
13
+ @remote_attributes = @options[:remote_attributes]
14
+ @init_proc = @options[:init_proc]
15
+ @update_if = @options[:update_if]
16
+ @destroy_if = @options[:destroy_if]
17
+ @uniq_by = Array(@options[:uniq_by]).map(&:to_s)
18
+ @association.load_target
19
+ end
20
+
21
+ def read(name)
22
+ valid_attribute?(name)
23
+ target.send(name) if target
24
+ end
25
+
26
+ def write(name, value)
27
+ valid_attribute?(name)
28
+ if value != read(name)
29
+ store_backup!
30
+ create_template!
31
+ target.send("#{name}=", value)
32
+ if same_as_backup?
33
+ restore_backup!
34
+ end
35
+ end
36
+ end
37
+
38
+ def try_commit
39
+ try_commit_existed || try_update
40
+ end
41
+
42
+ def try_destroy
43
+ try_destroy_backup
44
+ try_destroy_target
45
+ end
46
+
47
+ def try_update
48
+ if updateable_backup?
49
+ begin
50
+ @backup.update(attributes(@template, *@remote_attributes))
51
+ rescue ::ActiveRecord::StaleObjectError
52
+ @backup.reload
53
+ try_update
54
+ end
55
+ self.target = @backup
56
+ end
57
+ end
58
+
59
+ def try_commit_existed
60
+ if @template.present? && @uniq_by.any? && existed = detect_existed
61
+ self.target = existed
62
+ try_destroy_updateable_backup
63
+ true
64
+ end
65
+ end
66
+
67
+ def try_destroy_backup
68
+ if destroyable_backup?
69
+ begin
70
+ @backup.destroy
71
+ rescue ::ActiveRecord::StaleObjectError
72
+ @backup.reload
73
+ try_destroy_backup
74
+ end
75
+ end
76
+ end
77
+
78
+ def try_destroy_updateable_backup
79
+ if updateable_backup?
80
+ begin
81
+ @backup.destroy
82
+ rescue ::ActiveRecord::StaleObjectError
83
+ @backup.reload
84
+ try_destroy_updateable_backup
85
+ end
86
+ end
87
+ end
88
+
89
+ def try_destroy_target(force = false)
90
+ if destroyable_target?
91
+ begin
92
+ target.destroy
93
+ rescue ::ActiveRecord::StaleObjectError
94
+ target.reload
95
+ try_destroy_target
96
+ end
97
+ end
98
+ end
99
+
100
+ def clear!
101
+ @template = nil
102
+ @backup = nil
103
+ end
104
+
105
+ private
106
+
107
+ def detect_existed
108
+ outer_values = {}
109
+ where_values = {}
110
+ @uniq_by.each do |attribute|
111
+ relation_options_call = "#{attribute}_relation_options"
112
+ if klass.respond_to?(relation_options_call)
113
+ values = @template.send(relation_options_call)
114
+ outer_values.merge!(values[:outer_values])
115
+ where_values.merge!(values[:where_values])
116
+ else
117
+ where_values[attribute] = @template.send(attribute)
118
+ end
119
+ end
120
+ klass.includes(outer_values).where(where_values).limit(1).first
121
+ end
122
+
123
+ def updateable_backup?
124
+ @backup.present? && @update_if.try(:call, @backup)
125
+ end
126
+
127
+ def destroyable_backup?
128
+ @backup.present? && !@backup.destroyed? && @destroy_if.try(:call, @backup)
129
+ end
130
+
131
+ def destroyable_target?
132
+ target.try(:persisted?) && !target.destroyed? && @destroy_if.try(:call, target)
133
+ end
134
+
135
+ def attributes(object, *attrs)
136
+ Hash[attrs.map {|a| [a, object.send(a)]}]
137
+ end
138
+
139
+ def create_template!
140
+ if target.nil? || @template.nil?
141
+ self.target = template
142
+ end
143
+ end
144
+
145
+ def restore_backup!
146
+ if @backup
147
+ self.target = @backup
148
+ @backup = nil
149
+ end
150
+ end
151
+
152
+ def store_backup!
153
+ if target.try(:persisted?)
154
+ @backup ||= target
155
+ end
156
+ end
157
+
158
+ def same_as_backup?
159
+ @backup.present? && eval(@remote_attributes.map {|a| "@backup.send(:#{a}) == target.send(:#{a})"}.join(" && "))
160
+ end
161
+
162
+ def valid_attribute?(name)
163
+ raise(NameError, "Undefined remote attribute :#{name}!") unless @remote_attributes.include?(name.to_s)
164
+ end
165
+
166
+ def target=(record)
167
+ if owner.persisted?
168
+ association.send(:replace_keys, record)
169
+ association.set_inverse_instance(record)
170
+ association.instance_variable_set(:@updated, true) if record != @backup
171
+ association.target = record
172
+ else
173
+ association.replace(record)
174
+ end
175
+ end
176
+
177
+ def template
178
+ @template ||=
179
+ if target.try(:persisted?)
180
+ klass.new(attributes(target, *@remote_attributes))
181
+ elsif target.nil?
182
+ klass.new
183
+ elsif target.try(:new_record?)
184
+ target.dup
185
+ end
186
+ @template.tap do |t|
187
+ @init_proc.try(:call, t)
188
+ end
189
+ end
190
+ end
191
+
192
+ end
193
+ end
194
+
195
+ end
@@ -0,0 +1,120 @@
1
+ require 'active_tools/active_record/adaptive_belongs_to/adapter'
2
+ module ActiveTools
3
+ module ActiveRecord
4
+ module AdaptiveBelongsTo
5
+ extend ::ActiveSupport::Concern
6
+
7
+ included do
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def relation_options_under(*args)
13
+ path = args.extract_options!
14
+ local_attribute = args.first
15
+ local_method = "#{local_attribute}_relation_options"
16
+
17
+ define_singleton_method local_method do |instance = nil|
18
+ outer_values = {}
19
+ where_values = {}
20
+ path.each do |assoc_name, remote_attributes|
21
+ reflection = reflections[assoc_name]
22
+ target = instance.try(reflection.name)
23
+ outer_values[reflection.name] = {}
24
+ Array(remote_attributes).each do |remote_attribute|
25
+ remote_method = "#{remote_attribute}_relation_options"
26
+ if reflection.klass.respond_to?(remote_method)
27
+ deeper = reflection.klass.send(remote_method, target)
28
+ outer_values[reflection.name].merge!(deeper[:outer_values])
29
+ where_values.merge!(deeper[:where_values])
30
+ else
31
+ where_values[reflection.table_name] ||= {}.with_indifferent_access
32
+ where_values[reflection.table_name][remote_attribute] = target.try(remote_attribute)
33
+ end
34
+ end
35
+ end
36
+ {:outer_values => outer_values, :where_values => where_values}
37
+ end
38
+
39
+ class_eval do
40
+ define_method local_method do
41
+ self.class.send(local_method, self)
42
+ end
43
+ end
44
+
45
+ end
46
+
47
+ def adaptive_belongs_to(*args)
48
+ options = args.extract_options!
49
+ assoc_name = args.first
50
+ unless reflection = reflections[assoc_name]
51
+ raise(ArgumentError, ":#{assoc_name} method doesn't look like an association accessor!")
52
+ end
53
+ adapter_name = "#{assoc_name}_"
54
+ config_name = "#{assoc_name}_adapter_options"
55
+
56
+ raise(TypeError, "Option :attributes must be a Hash. #{options[:attributes].class} passed!") unless options[:attributes].is_a?(Hash)
57
+ attr_map = options.delete(:attributes).with_indifferent_access
58
+
59
+ valid_with assoc_name, :attributes => attr_map
60
+
61
+ class_attribute config_name
62
+ self.send("#{config_name}=", options.merge(:remote_attributes => attr_map.keys))
63
+
64
+ class_eval <<-EOV
65
+ after_validation do
66
+ #{adapter_name}.try_commit
67
+ end
68
+
69
+ after_commit do
70
+ #{adapter_name}.try_destroy_backup
71
+ #{adapter_name}.clear!
72
+ end
73
+
74
+ after_destroy do
75
+ #{adapter_name}.try_destroy
76
+ end
77
+
78
+ def #{adapter_name}
79
+ @#{adapter_name} ||= ActiveTools::ActiveRecord::AdaptiveBelongsTo::Adapter.new(association(:#{assoc_name}), #{config_name})
80
+ end
81
+ EOV
82
+
83
+ attr_map.each do |remote_attribute, local_attribute|
84
+ relation_options_under(local_attribute, assoc_name => remote_attribute)
85
+ class_eval do
86
+ define_method local_attribute do
87
+ send(adapter_name).read(remote_attribute)
88
+ end
89
+
90
+ define_method "#{local_attribute}=" do |value|
91
+ send(adapter_name).write(remote_attribute, value)
92
+ end
93
+ end
94
+ end
95
+
96
+ # attr_map.each do |remote_attribute, local_attribute|
97
+ # class_eval <<-EOV
98
+ # def #{local_attribute}_adaptive
99
+ # [{:#{assoc_name} => {}}, {:#{reflections[assoc_name].table_name} => {:#{remote_attribute} => #{local_attribute}}}]
100
+ # end
101
+ #
102
+ # def #{local_attribute}
103
+ # #{adapter_name}.read(:#{remote_attribute})
104
+ # end
105
+ #
106
+ # def #{local_attribute}=(value)
107
+ # #{adapter_name}.write(:#{remote_attribute}, value)
108
+ # end
109
+ # EOV
110
+ # end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ module OnLoadActiveRecord
117
+ include ActiveRecord::AdaptiveBelongsTo
118
+ end
119
+
120
+ end
@@ -0,0 +1,51 @@
1
+ module ActiveTools
2
+ module ActiveRecord
3
+ module CustomCounterCache
4
+ module InstanceMethods
5
+ def custom_counter_cache_after_create(assoc_name, reflection, assoc_mapping)
6
+ if record = send(assoc_name)
7
+ ActiveRecord::CustomCounterCache.digger(self, record, assoc_mapping) do |parent, cache_column, value|
8
+ parent.class.update_counters(parent.id, cache_column => value)
9
+ end
10
+ @_after_create_custom_counter_called = true
11
+ end
12
+ end
13
+
14
+ def custom_counter_cache_before_destroy(assoc_name, reflection, assoc_mapping)
15
+ foreign_key = reflection.foreign_key.to_sym
16
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
17
+ record = send(assoc_name)
18
+ if record && !self.destroyed?
19
+ ActiveRecord::CustomCounterCache.digger(self, record, assoc_mapping) do |parent, cache_column, value|
20
+ parent.class.update_counters(parent.id, cache_column => -value)
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ def custom_counter_cache_after_update(assoc_name, reflection, assoc_mapping)
27
+ foreign_key = reflection.foreign_key
28
+ if (@_after_create_custom_counter_called ||= false)
29
+ @_after_create_custom_counter_called = false
30
+ elsif send(:attribute_changed?, foreign_key) && !new_record? && (Rails.version >= "4.1.0" ? association(assoc_name).constructable? : defined?(reflection.klass.to_s.camelize))
31
+ model = reflection.klass
32
+ foreign_key_was = attribute_was foreign_key
33
+ foreign_key = attribute foreign_key
34
+
35
+ if foreign_key && model.respond_to?(:increment_counter) && to_increment = model.find_by_id(foreign_key)
36
+ ActiveRecord::CustomCounterCache.digger(self, to_increment, assoc_mapping) do |parent, cache_column, value|
37
+ parent.class.update_counters(parent.id, cache_column => value)
38
+ end
39
+ end
40
+ if foreign_key_was && model.respond_to?(:decrement_counter) && to_decrement = model.find_by_id(foreign_key_was)
41
+ ActiveRecord::CustomCounterCache.digger(self, to_decrement, assoc_mapping) do |parent, cache_column, value|
42
+ parent.class.update_counters(parent.id, cache_column => -value)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,64 @@
1
+ require 'active_tools/active_record/custom_counter_cache/instance_methods'
2
+
3
+ module ActiveTools
4
+ module ActiveRecord
5
+ module CustomCounterCache
6
+ extend ::ActiveSupport::Concern
7
+
8
+ included do
9
+ end
10
+
11
+ module ClassMethods
12
+ def custom_counter_cache_for(*args)
13
+ mapping = args.extract_options!
14
+ mapping.each do |assoc_name, value|
15
+ assoc_name = assoc_name.to_s
16
+ if assoc_name.last == "*"
17
+ if value.is_a?(Hash)
18
+ assoc_mapping = value.merge(assoc_name => value)
19
+ end
20
+ assoc_name = assoc_name[0..-2]
21
+ else
22
+ assoc_mapping = value
23
+ end
24
+ reflection = reflections[assoc_name.to_sym]
25
+
26
+ unless method_defined? :custom_counter_cache_after_create
27
+ include ActiveRecord::CustomCounterCache::InstanceMethods
28
+ end
29
+
30
+ after_create lambda { |record|
31
+ record.custom_counter_cache_after_create(assoc_name, reflection, assoc_mapping)
32
+ }
33
+
34
+ before_destroy lambda { |record|
35
+ record.custom_counter_cache_before_destroy(assoc_name, reflection, assoc_mapping)
36
+ }
37
+
38
+ after_update lambda { |record|
39
+ record.custom_counter_cache_after_update(assoc_name, reflection, assoc_mapping)
40
+ }
41
+ end
42
+ end
43
+ end
44
+
45
+ def self.digger(owner, object, mapping)
46
+ object.method_digger(mapping) do |object, key, response, value|
47
+ if response && !response.is_a?(::ActiveRecord::Base)
48
+ count = case value
49
+ when String, Symbol then owner.send(value)
50
+ when Fixnum then value
51
+ end
52
+ yield object, key, count
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ module OnLoadActiveRecord
61
+ include ActiveRecord::CustomCounterCache
62
+ end
63
+
64
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveTools
2
- module ActiveModel
2
+ module ActiveRecord
3
3
  module RecordId
4
4
  extend ::ActiveSupport::Concern
5
5
 
@@ -10,18 +10,18 @@ module ActiveTools
10
10
  end
11
11
 
12
12
  def record_id
13
- "#{self.class.model_name.singular}_#{try(:id)||uniq_id}"
13
+ "#{self.class.model_name.singular}_#{try(self.class.primary_key)||uniq_id}"
14
14
  end
15
15
 
16
16
  def uniq_id
17
- Base64.urlsafe_encode64(Time.now._dump)
17
+ Base64.urlsafe_encode64(Time.now.send(:_dump))
18
18
  end
19
19
 
20
20
  end
21
21
  end
22
22
 
23
23
  module OnLoadActiveRecord
24
- include ActiveModel::RecordId
24
+ include ActiveRecord::RecordId
25
25
  end
26
26
 
27
27
  end
@@ -1,3 +1,4 @@
1
+ require 'active_tools/active_model/valid_with'
1
2
  require 'active_tools/active_model/delegate_attributes'
2
3
 
3
4
  module ActiveTools
@@ -1,4 +1,6 @@
1
1
  require 'active_tools/active_record/record_id'
2
+ require 'active_tools/active_record/adaptive_belongs_to'
3
+ require 'active_tools/active_record/custom_counter_cache'
2
4
 
3
5
  module ActiveTools
4
6
  module ActiveRecord
@@ -0,0 +1,42 @@
1
+ module ActiveTools
2
+ module CoreExtension
3
+
4
+ module MethodDigger
5
+ module ObjectExtension
6
+ def method_digger(tree, &block)
7
+ tree.stringify_keys!
8
+ tree.each do |method, value|
9
+ if method.last == "*"
10
+ method = method[0..-2]
11
+ cycle_call(method) do |nested|
12
+ yield self, method, nested, value
13
+ if value.is_a?(Hash) && !nested.nil?
14
+ nested.method_digger(value, &block)
15
+ end
16
+ end
17
+ else
18
+ response = try(:send, method)
19
+ yield self, method, response, value
20
+ if value.is_a?(Hash) && !response.nil?
21
+ response.method_digger(value, &block)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ def cycle_call(method, &block)
28
+ object = self
29
+ export = []
30
+ while object = object.try(:send, method)
31
+ yield object if block_given?
32
+ export << object
33
+ end
34
+ export
35
+ end
36
+
37
+ end
38
+
39
+ ::Object.send(:include, ObjectExtension)
40
+ end
41
+ end
42
+ end
@@ -3,6 +3,7 @@ require 'active_tools/core_extension/deep_merge'
3
3
  require 'active_tools/core_extension/hashup'
4
4
  require 'active_tools/core_extension/merge_hashup'
5
5
  require 'active_tools/core_extension/kabuki'
6
+ require 'active_tools/core_extension/method_digger'
6
7
 
7
8
  module ActiveTools
8
9
  module CoreExtension
@@ -1,3 +1,3 @@
1
1
  module ActiveTools
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveTools::ActiveModel::DelegateAttributes do
4
+ class Parent
5
+ include ActiveModel::Validations
6
+ include ActiveTools::ActiveModel::DelegateAttributes
7
+
8
+ attr_accessor :child
9
+
10
+ delegate_attributes :name, to: :child, writer: true
11
+ delegate_attributes :name, to: :child, prefix: :prefixed, writer: true
12
+ end
13
+
14
+ class Child
15
+ include ActiveModel::Validations
16
+ include ActiveTools::ActiveModel::DelegateAttributes
17
+
18
+ attr_accessor :name
19
+
20
+ validates_presence_of :name
21
+ end
22
+
23
+ let(:teh_object) { Parent.new.tap{|parent| parent.child = teh_child} }
24
+ let(:teh_child) { Child.new }
25
+
26
+ it "delegates the given attribute from parent to child" do
27
+ teh_object.name = "Foo"
28
+
29
+ expect(teh_child.name).to eq("Foo")
30
+ expect(teh_object.name).to eq("Foo")
31
+ end
32
+
33
+ it "delegates the given attribute with a prefix from parent to child" do
34
+ teh_object.prefixed_name = "Bar"
35
+
36
+ expect(teh_child.name).to eq("Bar")
37
+ expect(teh_object.prefixed_name).to eq("Bar")
38
+ end
39
+
40
+ it "forwards the errors from child to parent" do
41
+ expect(teh_object.valid?).to be_false
42
+ expect(teh_object.errors.messages[:name]).to eq(["can't be blank"])
43
+ end
44
+
45
+ it "forwards the errors from child to parent via prefix" do
46
+ expect(teh_object.valid?).to be_false
47
+ expect(teh_object.errors.messages[:prefixed_name]).to eq(["can't be blank"])
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ require 'rspec'
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ require 'active_model'
6
+ require 'rails'
7
+ require 'active_tools'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Valery Kvon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-04-23 00:00:00.000000000 Z
11
+ date: 2013-11-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description: Missing tools for Rails developers
28
42
  email:
29
43
  - addagger@gmail.com
@@ -32,20 +46,30 @@ extensions: []
32
46
  extra_rdoc_files: []
33
47
  files:
34
48
  - .gitignore
49
+ - .ruby-gemset
50
+ - .ruby-version
35
51
  - Gemfile
36
52
  - LICENSE.txt
37
53
  - README.md
38
54
  - Rakefile
39
55
  - active_tools.gemspec
56
+ - copy
40
57
  - lib/active_tools.rb
41
58
  - lib/active_tools/action_pack/action_controller.rb
42
59
  - lib/active_tools/action_pack/action_controller/path_helper.rb
43
60
  - lib/active_tools/action_pack/action_dispatch.rb
44
61
  - lib/active_tools/action_pack/action_dispatch/flash_stack.rb
45
62
  - lib/active_tools/action_pack/action_view.rb
63
+ - lib/active_tools/action_pack/action_view/perform_as_tree.rb
46
64
  - lib/active_tools/action_pack/action_view/tag_attributes.rb
47
65
  - lib/active_tools/actionpack.rb
48
66
  - lib/active_tools/active_model/delegate_attributes.rb
67
+ - lib/active_tools/active_model/valid_with.rb
68
+ - lib/active_tools/active_model/valid_with/fake_errors.rb
69
+ - lib/active_tools/active_record/adaptive_belongs_to.rb
70
+ - lib/active_tools/active_record/adaptive_belongs_to/adapter.rb
71
+ - lib/active_tools/active_record/custom_counter_cache.rb
72
+ - lib/active_tools/active_record/custom_counter_cache/instance_methods.rb
49
73
  - lib/active_tools/active_record/record_id.rb
50
74
  - lib/active_tools/activemodel.rb
51
75
  - lib/active_tools/activerecord.rb
@@ -60,12 +84,15 @@ files:
60
84
  - lib/active_tools/core_extension/kabuki/dump.rb
61
85
  - lib/active_tools/core_extension/kabuki/zip.rb
62
86
  - lib/active_tools/core_extension/merge_hashup.rb
87
+ - lib/active_tools/core_extension/method_digger.rb
63
88
  - lib/active_tools/engine.rb
64
89
  - lib/active_tools/misc.rb
65
90
  - lib/active_tools/misc/input_source.rb
66
91
  - lib/active_tools/misc/script_flow.rb
67
92
  - lib/active_tools/railtie.rb
68
93
  - lib/active_tools/version.rb
94
+ - spec/active_tools/active_model/delegate_attributes_spec.rb
95
+ - spec/spec_helper.rb
69
96
  homepage: http://vkvon.ru/projects/active_tools
70
97
  licenses: []
71
98
  metadata: {}
@@ -85,9 +112,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
112
  version: '0'
86
113
  requirements: []
87
114
  rubyforge_project: active_tools
88
- rubygems_version: 2.0.0
115
+ rubygems_version: 2.0.4
89
116
  signing_key:
90
117
  specification_version: 4
91
118
  summary: ActionDispatch, ActionController, ActiveModel, ActiveRecord, ActiveSupport,
92
119
  ActionView and core extensions
93
- test_files: []
120
+ test_files:
121
+ - spec/active_tools/active_model/delegate_attributes_spec.rb
122
+ - spec/spec_helper.rb