active_layer 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ # Version 0.0.9
2
+ * Completed the initial scope including the following layers persistence, validations, and attribute guarding
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Adam Cooper
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # ActiveLayer
2
+
3
+ ActiveLayer provides validations, attribute filtering, and persistence in a layer sitting on top of a model.
4
+
5
+ This was born out of frustration with overly complex ActiveRecord validations and model contortions.
6
+
7
+ # Quick Example
8
+
9
+ class User < ActiveRecord::Base; end
10
+ # Every user field you want..
11
+
12
+ # This layer uses the ActiveRecord adapter which pulls in several other pieces
13
+ class UserLayer
14
+ include ActiveLayer::ActiveRecord
15
+
16
+ attr_accessible :name, :email, :address
17
+
18
+ validates :name, :email, :presence => true
19
+ validates :address_validation
20
+
21
+ before_validate :normalize_address
22
+
23
+ def normalize_address; end
24
+ def address_validation
25
+ errors.add(:address, :invalid) if address.blank?
26
+ end
27
+ end
28
+
29
+ # Now how to use it:
30
+ user = User.find(1)
31
+ layer = UserLayer.new(user)
32
+ layer.update_attributes( {:name => '', :admin => true, :address => 'on a busy road'} ) # => false
33
+ layer.errors[:name].present # => true
34
+ user.errors[:name].present => true
35
+ user.admin # => false - was filtered out
36
+
37
+ # Some of the players
38
+
39
+ * ActiveLayer::Proxy
40
+ * ActiveLayer::Validations
41
+ * ActiveLayer::Attributes
42
+ * ActiveLayer::Persistence
43
+
44
+ # ActiveLayer::Proxy
45
+
46
+ This module provides the ability to delegate method calls down to the object and typically you would not use this element directly. However, it does provide one key method *active__layer__object* which provides access to the underlying object/model. This may be necessary in certain cases.
47
+
48
+ # ActiveLayer::Validations
49
+
50
+ Currently the majority of the functionality resides in this module and provides the ability for composable validation classes to be layered together. This module is completely compatible with the wonderful ActiveModel::Validation module and in fact just wraps it and provides extra functionality.
51
+
52
+ Currently the following is supported:
53
+
54
+ * Standard ActiveModel::Validators support (Each, With, Custom, etc.)
55
+ * Custom validation methods
56
+ * before_validation and after_validation hooks (Just like ActiveRecord)
57
+ * Nested ActiveLayer validations (This is neat!)
58
+ * All existing custom validators should 'just work'
59
+
60
+ ## Examples
61
+
62
+ class AddressValidator
63
+ include ActiveLayer::Validations
64
+
65
+ before_validation :normalize_address
66
+ validates :address, :presence => true
67
+ validate :custom_method
68
+
69
+ def normalize_address; end
70
+ def custom_method; end
71
+ end
72
+
73
+ class EmailValidator
74
+ include ActiveLayer::Validations
75
+
76
+ validates :email, :presence => true
77
+ end
78
+
79
+ class UserValidator
80
+ include ActiveLayer::Validations
81
+
82
+ validates :name, :presence => true
83
+ validates_with EmailValidator
84
+ validates_with AddressValidator, :attributes => [:addresses]
85
+ end
86
+
87
+ # the EmailValidator will get passed the user object
88
+ # the AddressValidator will get passed each address of the user and merge the errors back up
89
+ user = User.find(1)
90
+ validator = UserValidator.new(user)
91
+ validator.valid? # => true/false
92
+
93
+
94
+ # ActiveLayer::Attributes
95
+
96
+ This module provides the ability to bulk assign attributes with attribute protection via att_accessible. By default all attributes are not assignable and you must declare which attributes are accessible. Note, each attribute is available for individual assigning or reading.
97
+
98
+ ## Examples
99
+
100
+ class FlawedUserFilter
101
+ all_attributes_accessible!
102
+ # All attributes are now assignable
103
+ end
104
+
105
+ class UserFilter
106
+ attr_accessible :name, :address
107
+ end
108
+
109
+ user = User.find(1)
110
+ filter = UserFilter.new(user)
111
+ filter.attributes = {:name => 'Bob', 'admin' => true}
112
+ user.admin # => false
113
+
114
+
115
+
116
+
117
+ # ActiveLayer::Persistence
118
+
119
+ This layer is responsible for providing the save and update_attributes methods that function very similar to ActiveRecord. Both layers invoke the validations in the layer, if currently mixed in and ultimately delegate to the proxy object for saving. If the Attributes module is mixed in then the guards will be used however, if it is not mixed then active_layer_object.attributes=(hash) will be invoked.
120
+
121
+ Note: If you plan to use Persistence with Attributes then you must mix-in the Attributes module *after* the Persistence module
122
+
123
+ ## Examples
124
+
125
+ class UserPersistor
126
+ include ActiveLayer::Persistence
127
+ end
128
+
129
+ user = User.find(1)
130
+ persistor = UserPersistor.new(user)
131
+ persistor.save
132
+ persistor.save! # will raise an ActiveLayer::RecordInvalidError with a #record method
133
+ persistor.update_attributes(:name => 'new')
134
+ persistor.update_attributes!(:name => 'new')
135
+
136
+
137
+
138
+ # TODO / Plans
139
+
140
+ * Ensure that the valid? method calls the underlying proxy object.valid?
141
+ * Nested attribute support - similar to Rails - but in an adaptable manner for use in other ORMs
142
+ * Add support for additional ORMs
143
+ * Ideas?
144
+
145
+ # License
146
+
147
+ ActiveLayer is available under the terms of the MIT license (see
148
+ LICENSE.txt for details).
149
+
@@ -0,0 +1,29 @@
1
+ require 'activerecord'
2
+
3
+ module ActiveLayer
4
+ module ActiveRecord
5
+ # override the initialize because the submodels should handle their own translations
6
+ # This will allow drop in compatibility with existing functionality
7
+ class RecordInvalidError < ::ActiveRecord::RecordInvalidError
8
+ attr_reader :record
9
+ def initialize(record)
10
+ @record = record
11
+ super(@record.errors.full_messages.join(", "))
12
+ end
13
+ end
14
+
15
+ module Persistence
16
+ extend ActiveSupport::Concern
17
+
18
+ module InstanceMethods
19
+
20
+ def save!
21
+ unless save
22
+ raise RecordInvalidError.new(self)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ module ActiveLayer
2
+ # This is a helper module that is only responsible for pulling in all the modules
3
+ module ActiveRecord
4
+ autoload :Persistence, 'active_layer/active_record/persistence'
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ include ::ActiveLayer::Validations
9
+ include ::ActiveLayer::Persistence
10
+ include ::ActiveLayer::Attributes # hook in after the Persistence layer
11
+
12
+ # we only need this if ActiveRecord is actually defined.
13
+ include ::ActiveLayer::ActiveRecord::Persistence if defined?(::ActiveRecord)
14
+
15
+ end
16
+ end
@@ -0,0 +1,58 @@
1
+ require 'active_support/core_ext/class/attribute'
2
+ require 'active_support/core_ext/hash/keys'
3
+
4
+ module ActiveLayer
5
+ class UnknownAttributeError < StandardError; end
6
+
7
+ # Adds the ability to pass attributes to the ActiveLayer Object.
8
+ # - Provides support for 'attr_accessible' class method to only allow certain attributes through
9
+ # - By default all attributes are filter out -> opt in model
10
+ # - You can enable all attributes passed through with 'all_attributes_accessible!'
11
+ # - Hooks into the ActiveLayer::Persistence module to tie in with the update_attributes
12
+ # - To enable this functionality, it must be inluded after the Persistence module
13
+ module Attributes
14
+ extend ActiveSupport::Concern
15
+ include ActiveLayer::Proxy
16
+
17
+ included do
18
+ class_attribute :accessible_attributes
19
+ self.accessible_attributes = []
20
+ end
21
+
22
+ module ClassMethods
23
+ def attr_accessible(*attributes)
24
+ self.accessible_attributes = Set.new(attributes.map(&:to_s)) + (accessible_attributes || [])
25
+ end
26
+
27
+ def all_attributes_accessible!
28
+ self.accessible_attributes = nil
29
+ end
30
+
31
+ end
32
+
33
+ module InstanceMethods
34
+
35
+ def attributes=(new_attributes)
36
+ return if new_attributes.nil?
37
+ attributes = new_attributes.stringify_keys
38
+
39
+ safe_attributes = if accessible_attributes.nil?
40
+ attributes
41
+ else
42
+ attributes.reject { |key, value| !accessible_attributes.include?(key.gsub(/\(.+/, "")) }
43
+ end
44
+
45
+ safe_attributes.each do |k, v|
46
+ respond_to?(:"#{k}=") ? send(:"#{k}=", v) : raise(UnknownAttributeError, "unknown attribute: #{k}")
47
+ end
48
+ end
49
+
50
+ # override persistence saving to pull in the guard functionality
51
+ def active_layer_attributes_setting(new_attributes)
52
+ attributes = new_attributes
53
+ end
54
+
55
+
56
+ end # InstanceMethods
57
+ end # Attributes
58
+ end # ActiveLayer
@@ -0,0 +1,4 @@
1
+ module ActiveLayer
2
+ class RecordNotSaved < ::ActiveRecord::RecordNotSaved
3
+ end
4
+ end
@@ -0,0 +1,68 @@
1
+ module ActiveLayer
2
+ # This module introduces save and update_attribute methods delegating to the proxy object
3
+ #
4
+ # This module provides two hook methods to allow the including class to control how the attributes get set on the proxy object
5
+ # and how the proxy object gets saved.
6
+ #
7
+ # Here are the defaults
8
+ #
9
+ # def active_layer_save
10
+ # active_layer_object.save
11
+ # end
12
+ #
13
+ # def active_layer_attributes_setting(new_attributes)
14
+ # active_layer_object.attributes = new_attributes
15
+ # end
16
+ #
17
+ module Persistence
18
+ extend ActiveSupport::Concern
19
+ include ActiveLayer::Proxy
20
+
21
+ module InstanceMethods
22
+ def save
23
+ valid = self.respond_to?(:valid?, false) ? valid? : true
24
+
25
+ if valid
26
+ active_layer_save
27
+ else
28
+ false
29
+ end
30
+ end
31
+
32
+ def save!
33
+ unless save
34
+ raise RecordInvalidError.new(self)
35
+ end
36
+ end
37
+
38
+ def update_attributes(new_attributes)
39
+ active_layer_attributes_setting(new_attributes)
40
+ save
41
+ end
42
+
43
+ def update_attributes!(new_attributes)
44
+ active_layer_attributes_setting(new_attributes)
45
+ save!
46
+ end
47
+
48
+ # hook method to override saving behaviour
49
+ def active_layer_save
50
+ active_layer_object.save
51
+ end
52
+
53
+ # another hook method to control attributes=
54
+ def active_layer_attributes_setting(new_attributes)
55
+ active_layer_object.attributes = new_attributes
56
+ end
57
+
58
+ end
59
+ end
60
+
61
+ class RecordInvalidError < RuntimeError
62
+ attr_reader :record
63
+ def initialize(record)
64
+ @record = record
65
+ super(@record.errors.full_messages.join(", "))
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,47 @@
1
+ module ActiveLayer
2
+ # This class provides the delegation to the actual object.
3
+ # You probably don't want to use this object directly but rather the other classes depend on it.
4
+ module Proxy
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ attr_writer :active_layer_object
9
+ end
10
+
11
+ module InstanceMethods
12
+ def initialize(*args, &block)
13
+ self.active_layer_object = args.first
14
+ super()
15
+ end
16
+
17
+ def active_layer_object
18
+ raise "active_layer_object was not set" if @active_layer_object.nil?
19
+ @active_layer_object
20
+ end
21
+
22
+ def method_missing(method, *args, &block)
23
+ # puts "passing: #{method} + #{args.inspect} to #{active_layer_object.inspect}"
24
+ unless @active_layer_object.nil?
25
+ active_layer_object.send(method, *args, &block)
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def read_attribute_for_validation(key)
32
+ if active_layer_object.respond_to?(:read_attribute_for_validation)
33
+ active_layer_object.read_attribute_for_validation(key)
34
+ else
35
+ active_layer_object.send(key)
36
+ end
37
+ end
38
+
39
+ def respond_to?(method, use_proxy = true)
40
+ super(method) || ( use_proxy && @active_layer_object && @active_layer_object.respond_to?(method) )
41
+ end
42
+
43
+
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveLayer
2
+ module Validations
3
+ #
4
+ # This class hooks into the standard validations and allows the new ActiveLayer::Validations to function
5
+ # as a NestedValidator
6
+ #
7
+ # class MyValidator
8
+ # include AciveLayer::Validations
9
+ # validates MyOtherValidator, :attributes => :sub_models
10
+ # end
11
+ #
12
+ # In the above case this class will be used to adapt the MyOtherValidator to look like an ActiveModel::Validator
13
+ #
14
+ class EachValidator < ActiveModel::EachValidator
15
+ attr_accessor :active_layer_validator
16
+
17
+ #
18
+ # The parent_validations will be the parent class that has mixed in the ActiveLayer::Validations module
19
+ #
20
+ def validate_each(parent_validations, attribute, value)
21
+ nested_attribute = parent_validations.read_attribute_for_validation(attribute)
22
+
23
+ Array.wrap(nested_attribute).each do |element|
24
+ active_layer_validator.active_layer_object = element
25
+ active_layer_validator.valid?
26
+ parent_validations.merge_errors(active_layer_validator.errors, "#{attribute}.")
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,19 @@
1
+ #
2
+ # This class hooks into the standard validations and allows the new ModelValidator to function
3
+ # as a NestedValidator
4
+ #
5
+ module ActiveLayer
6
+ module Validations
7
+ class ObjectValidator < ActiveModel::Validator
8
+ attr_accessor :active_layer_validator
9
+
10
+ def validate(record)
11
+ # need to save the original errors because they get wiped during validation
12
+ record.keep_errors do
13
+ active_layer_validator.active_layer_object = record
14
+ active_layer_validator.valid?
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,64 @@
1
+ module ActiveLayer
2
+ module Validations
3
+ # This module allows the ActiveLayer::Validations class to adapt to become
4
+ # an ActiveModel Validator
5
+ module Validator
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attr_accessor :_validator
10
+ end
11
+
12
+ module InstanceMethods
13
+
14
+ def initialize(*args, &block)
15
+ if args.first.is_a?(Hash)
16
+ options = args.first
17
+ if options.has_key?(:attributes)
18
+ self._validator = EachValidator.new(options)
19
+ else
20
+ self._validator = ObjectValidator.new(options)
21
+ end
22
+ _validator.active_layer_validator = self
23
+ end
24
+ super
25
+ end
26
+
27
+ # adapter methods
28
+ def setup(record)
29
+ # nothing to do...
30
+ end
31
+
32
+ # adapter methods
33
+ def attributes
34
+ _validator && _validator.respond_to?(:attributes) ? _validator.attributes : []
35
+ end
36
+
37
+ def errors
38
+ if _validator
39
+ @errors ||= ActiveModel::Errors.new(self)
40
+ else
41
+ super
42
+ end
43
+ end
44
+
45
+ # This method is called in two contexts, as part of the validation callbacks when it's setup
46
+ # as an adapter and to register a standard callback
47
+ #
48
+ def validate(*args, &block)
49
+ # puts "calling validate on #{self.class.name}"
50
+ if _validator
51
+ _validator.validate(*args, &block)
52
+ else
53
+ super
54
+ end
55
+ end
56
+
57
+
58
+ end
59
+
60
+
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,54 @@
1
+ module ActiveLayer
2
+ module Validations
3
+ autoload :Validator, 'active_layer/validations/validator'
4
+ autoload :EachValidator, 'active_layer/validations/each_validator'
5
+ autoload :ObjectValidator, 'active_layer/validations/object_validator'
6
+
7
+ extend ActiveSupport::Concern
8
+ include ActiveLayer::Proxy
9
+ include ActiveModel::Validations
10
+ include ActiveLayer::Validations::Validator
11
+
12
+ included do
13
+ extend ActiveModel::Callbacks
14
+ define_model_callbacks :validation
15
+ end
16
+
17
+ module InstanceMethods
18
+
19
+ # model validation methods
20
+ def errors
21
+ if active_layer_object.respond_to?(:errors)
22
+ active_layer_object.errors
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+
29
+ def keep_errors(prefix = "")
30
+ original_errors = errors.dup
31
+ result = yield
32
+ merge_errors(original_errors, prefix)
33
+ result
34
+ end
35
+
36
+ def merge_errors(other_errors, prefix = nil)
37
+ other_errors.each do |child_attribute, message|
38
+ attribute = "#{prefix}#{child_attribute}"
39
+ errors[attribute] << message
40
+ errors[attribute].uniq!
41
+ end
42
+ end
43
+
44
+ def valid?(*)
45
+ _run_validation_callbacks do
46
+ super
47
+ #keep_errors { active_layer_object.valid? }
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ end # Validations
54
+ end # ActiveLayer
@@ -0,0 +1,3 @@
1
+ module ActiveLayer
2
+ VERSION = "0.0.9"
3
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_support'
2
+ require 'active_model'
3
+ require 'active_layer/version'
4
+
5
+ module ActiveLayer
6
+ autoload :ActiveRecord, 'active_layer/active_record'
7
+ autoload :Attributes, 'active_layer/attributes'
8
+ autoload :Persistence, 'active_layer/persistence'
9
+ autoload :Proxy, 'active_layer/proxy'
10
+ autoload :Validations, 'active_layer/validations'
11
+ end
12
+
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_layer
3
+ version: !ruby/object:Gem::Version
4
+ hash: 13
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 9
10
+ version: 0.0.9
11
+ platform: ruby
12
+ authors:
13
+ - Adam Cooper
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-06-12 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: -1848230021
30
+ segments:
31
+ - 3
32
+ - 0
33
+ - 0
34
+ - beta3
35
+ version: 3.0.0.beta3
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
39
+ name: activemodel
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: -1848230021
47
+ segments:
48
+ - 3
49
+ - 0
50
+ - 0
51
+ - beta3
52
+ version: 3.0.0.beta3
53
+ type: :runtime
54
+ version_requirements: *id002
55
+ description: Subscribing to single responsibility principility ActiveLayer aims to provide a layer of context over top a model.
56
+ email:
57
+ - adam.cooper@gmail.com
58
+ executables: []
59
+
60
+ extensions: []
61
+
62
+ extra_rdoc_files: []
63
+
64
+ files:
65
+ - lib/active_layer/active_record/persistence.rb
66
+ - lib/active_layer/active_record.rb
67
+ - lib/active_layer/attributes.rb
68
+ - lib/active_layer/orm/active_record_hooks.rb
69
+ - lib/active_layer/persistence.rb
70
+ - lib/active_layer/proxy.rb
71
+ - lib/active_layer/validations/each_validator.rb
72
+ - lib/active_layer/validations/object_validator.rb
73
+ - lib/active_layer/validations/validator.rb
74
+ - lib/active_layer/validations.rb
75
+ - lib/active_layer/version.rb
76
+ - lib/active_layer.rb
77
+ - LICENSE
78
+ - CHANGELOG.md
79
+ - README.md
80
+ has_rdoc: true
81
+ homepage: http://github.com/adamcooper/active_layer
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options: []
86
+
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 23
104
+ segments:
105
+ - 1
106
+ - 3
107
+ - 6
108
+ version: 1.3.6
109
+ requirements: []
110
+
111
+ rubyforge_project: active_layer
112
+ rubygems_version: 1.3.7
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: ActiveLayer provides validations, attribute filtering, and persistence in a layer sitting on top of any model.
116
+ test_files: []
117
+