active_tools 0.0.5 → 0.0.6

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.
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