active_attr 0.2.2 → 0.3.0

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.

Potentially problematic release.


This version of active_attr might be problematic. Click here for more details.

data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ # ActiveAttr 0.3.0 (November 26, 2011) #
2
+
3
+ * Added BlockInitialization
4
+ * Added DangerousAttributeError
5
+ * Added Logger
6
+ * Added MassAssignmentSecurity
7
+ * Added QueryAttributes
8
+ * Added UnknownAttributeError
9
+ * Attributes now honors getters/setters when calling #read_attribute,
10
+ #write_attribute, #[], and #[]=
11
+ * Attributes now raises DangerousAttributeError when defining an attribute
12
+ whose methods would conflict with an existing method
13
+ * Attributes now raises UnknownAttributeError when getting/setting any
14
+ undefined attributes
15
+
1
16
  # ActiveAttr 0.2.2 (November 2, 2011) #
2
17
 
3
18
  * Fixed all instances of modules' #initialize not invoking its superclass
data/README.md CHANGED
@@ -24,10 +24,10 @@ the attributes of your model.
24
24
  attribute :last_name
25
25
  end
26
26
 
27
- p = Person.new
28
- p.first_name = "Chris"
29
- p.last_name = "Griego"
30
- p.attributes #=> {"first_name"=>"Chris", "last_name"=>"Griego"}
27
+ person = Person.new
28
+ person.first_name = "Chris"
29
+ person.last_name = "Griego"
30
+ person.attributes #=> {"first_name"=>"Chris", "last_name"=>"Griego"}
31
31
 
32
32
  ### BasicModel ###
33
33
 
@@ -39,9 +39,48 @@ required for your model to meet the ActiveModel API requirements.
39
39
  end
40
40
 
41
41
  Person.model_name.plural #=> "people"
42
- p = Person.new
43
- p.valid? #=> true
44
- p.errors.full_messages #=> []
42
+ person = Person.new
43
+ person.valid? #=> true
44
+ person.errors.full_messages #=> []
45
+
46
+ ### BlockInitialization ###
47
+
48
+ Including the BlockInitialization module into your class will yield the model
49
+ instance to a block passed to when creating a new instance.
50
+
51
+ class Person
52
+ include ActiveAttr::BlockInitialization
53
+ attr_accessor :first_name, :last_name
54
+ end
55
+
56
+ person = Person.new do |p|
57
+ p.first_name = "Chris"
58
+ p.last_name = "Griego"
59
+ end
60
+
61
+ person.first_name #=> "Chris"
62
+ person.last_name #=> "Griego"
63
+
64
+ ### Logger ###
65
+
66
+ Including the Logger module into your class will give you access to a
67
+ configurable logger in model classes and instances. Your preferred logger can
68
+ be configured on an instance, subclass, class, parent class, and globally by
69
+ setting ActiveAttr::Logger.logger. When using Rails, the Rails framework
70
+ logger will be configured by default.
71
+
72
+ class Person
73
+ include ActiveAttr::Logger
74
+ end
75
+
76
+ Person.logger = Logger.new(STDOUT)
77
+ Person.logger? #=> true
78
+ Person.logger.info "Logging an informational message"
79
+
80
+ person = Person.new
81
+ person.logger? #=> true
82
+ person.logger = Logger.new(STDERR)
83
+ person.logger.warn "Logging a warning message"
45
84
 
46
85
  ### MassAssignment ###
47
86
 
@@ -56,10 +95,43 @@ attribute.
56
95
  attr_accessor :first_name, :last_name
57
96
  end
58
97
 
59
- p = Person.new(:first_name => "Chris")
60
- p.attributes = { :last_name => "Griego" }
61
- p.first_name #=> "Chris"
62
- p.last_name #=> "Griego"
98
+ person = Person.new(:first_name => "Chris")
99
+ person.attributes = { :last_name => "Griego" }
100
+ person.first_name #=> "Chris"
101
+ person.last_name #=> "Griego"
102
+
103
+ ### MassAssignmentSecurity ###
104
+
105
+ Including the MassAssignmentSecurity module into your class extends the
106
+ MassAssignment methods to honor any declared mass assignment permission
107
+ blacklists or whitelists including support for mass assignment roles.
108
+
109
+ class Person
110
+ include ActiveAttr::MassAssignment
111
+ attr_accessor :first_name, :last_name
112
+ attr_protected :last_name
113
+ end
114
+
115
+ person = Person.new(:first_name => "Chris", :last_name => "Griego")
116
+ person.first_name #=> "Chris"
117
+ person.last_name #=> nil
118
+
119
+ ### QueryAttributes ###
120
+
121
+ Including the QueryAttributes module into your class builds on Attributes by
122
+ providing instance methods for querying your attributes
123
+
124
+ class Person
125
+ include ActiveAttr::QueryAttributes
126
+
127
+ attribute :first_name
128
+ attribute :last_name
129
+ end
130
+
131
+ person = Person.new
132
+ person.first_name = "Chris"
133
+ person.first_name? #=> true
134
+ person.last_name? #=> false
63
135
 
64
136
  ## RSpec Integration ##
65
137
 
data/active_attr.gemspec CHANGED
@@ -21,7 +21,8 @@ Gem::Specification.new do |s|
21
21
  s.add_runtime_dependency "activemodel", "~> 3.1"
22
22
  s.add_runtime_dependency "activesupport", "~> 3.1"
23
23
 
24
- s.add_development_dependency "bundler", "~> 1.0"
25
- s.add_development_dependency "rake", "~> 0.9.0"
26
- s.add_development_dependency "rspec", "~> 2.6"
24
+ s.add_development_dependency "bundler", "~> 1.0"
25
+ s.add_development_dependency "factory_girl", "~> 2.2"
26
+ s.add_development_dependency "rake", "~> 0.9.0"
27
+ s.add_development_dependency "rspec", "~> 2.6"
27
28
  end
data/lib/active_attr.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require "active_attr/railtie" if defined?(Rails)
1
2
  require "active_support/dependencies/autoload"
2
3
 
3
4
  # ActiveAttr is a set of modules to enhance Plain Old Ruby Objects (POROs)
@@ -13,8 +14,12 @@ module ActiveAttr
13
14
  autoload :BasicModel
14
15
  autoload :ChainableInitialization
15
16
  autoload :Error
17
+ autoload :Logger
16
18
  autoload :MassAssignment
19
+ autoload :MassAssignmentSecurity
20
+ autoload :QueryAttributes
17
21
  autoload :StrictMassAssignment
22
+ autoload :UnknownAttributeError
18
23
  autoload :UnknownAttributesError
19
24
  autoload :VERSION
20
25
  end
@@ -1,5 +1,7 @@
1
1
  require "active_attr/attribute_definition"
2
2
  require "active_attr/chainable_initialization"
3
+ require "active_attr/dangerous_attribute_error"
4
+ require "active_attr/unknown_attribute_error"
3
5
  require "active_model"
4
6
  require "active_support/concern"
5
7
 
@@ -22,6 +24,10 @@ module ActiveAttr
22
24
  include ActiveAttr::ChainableInitialization
23
25
  include ActiveModel::AttributeMethods
24
26
 
27
+ # Methods deprecated on the Object class which can be safely overridden
28
+ # @since 0.3.0
29
+ DEPRECATED_OBJECT_METHODS = %w(id type)
30
+
25
31
  included do
26
32
  attribute_method_suffix ""
27
33
  attribute_method_suffix "="
@@ -42,7 +48,7 @@ module ActiveAttr
42
48
  attributes == other.attributes
43
49
  end
44
50
 
45
- # Returns the raw attributes Hash
51
+ # Returns a Hash of all attributes
46
52
  #
47
53
  # @example Get attributes
48
54
  # person.attributes # => {"name"=>"Ben Poweski"}
@@ -70,48 +76,79 @@ module ActiveAttr
70
76
  #
71
77
  # @since 0.2.0
72
78
  def inspect
73
- attribute_descriptions = self.class.attributes.sort.map do |attribute|
74
- "#{attribute.name.to_s}: #{read_attribute(attribute.name).inspect}"
75
- end.join(", ")
76
-
77
- attribute_descriptions = " " + attribute_descriptions unless attribute_descriptions.empty?
78
-
79
- "#<#{self.class.name}#{attribute_descriptions}>"
79
+ attribute_descriptions = self.attributes.sort.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
80
+ separator = " " unless attribute_descriptions.empty?
81
+ "#<#{self.class.name}#{separator}#{attribute_descriptions}>"
80
82
  end
81
83
 
82
- # Read a value from the model's attributes. If the value does not exist
83
- # it will return nil.
84
+ # Read a value from the model's attributes.
84
85
  #
85
- # @example Read an attribute.
86
+ # @example Read an attribute with read_attribute
86
87
  # person.read_attribute(:name)
88
+ # @example Rean an attribute with bracket syntax
89
+ # person[:name]
87
90
  #
88
- # @param [String, Symbol] name The name of the attribute to get.
91
+ # @param [String, Symbol, #to_s] name The name of the attribute to get.
89
92
  #
90
93
  # @return [Object] The value of the attribute.
91
94
  #
95
+ # @raise [UnknownAttributeError] if the attribute is unknown
96
+ #
92
97
  # @since 0.2.0
93
98
  def read_attribute(name)
94
- @attributes[name.to_s]
99
+ if respond_to? name
100
+ send name.to_s
101
+ else
102
+ raise UnknownAttributeError, "unknown attribute: #{name}"
103
+ end
95
104
  end
96
105
  alias_method :[], :read_attribute
97
- alias_method :attribute, :read_attribute
98
- private :attribute
99
106
 
100
107
  # Write a single attribute to the model's attribute hash.
101
108
  #
102
- # @example Write the attribute.
109
+ # @example Write the attribute with write_attribute
103
110
  # person.write_attribute(:name, "Benjamin")
111
+ # @example Write an attribute with bracket syntax
112
+ # person[:name] = "Benjamin"
104
113
  #
105
- # @param [String, Symbol] name The name of the attribute to update.
114
+ # @param [String, Symbol, #to_s] name The name of the attribute to update.
106
115
  # @param [Object] value The value to set for the attribute.
107
116
  #
117
+ # @raise [UnknownAttributeError] if the attribute is unknown
118
+ #
108
119
  # @since 0.2.0
109
120
  def write_attribute(name, value)
110
- @attributes[name.to_s] = value
121
+ if respond_to? "#{name}="
122
+ send "#{name}=", value
123
+ else
124
+ raise UnknownAttributeError, "unknown attribute: #{name}"
125
+ end
111
126
  end
112
127
  alias_method :[]=, :write_attribute
113
- alias_method :attribute=, :write_attribute
114
- private :attribute=
128
+
129
+ protected
130
+
131
+ # Overrides ActiveModel::AttributeMethods
132
+ # @private
133
+ def attribute_method?(attr_name)
134
+ self.class.attributes.map { |definition| definition.name.to_s }.include? attr_name.to_s
135
+ end
136
+
137
+ private
138
+
139
+ # Read an attribute from the attributes hash
140
+ #
141
+ # @since 0.2.1
142
+ def attribute(name)
143
+ @attributes[name.to_s]
144
+ end
145
+
146
+ # Write an attribute to the attributes hash
147
+ #
148
+ # @since 0.2.1
149
+ def attribute=(name, value)
150
+ @attributes[name.to_s] = value
151
+ end
115
152
 
116
153
  module ClassMethods
117
154
  # Defines all the attributes that are to be returned from the attributes instance method.
@@ -124,11 +161,15 @@ module ActiveAttr
124
161
  #
125
162
  # @param (see AttributeDefinition#initialize)
126
163
  #
164
+ # @raise [DangerousAttributeError] if the attribute name conflicts with existing methods
165
+ #
127
166
  # @since 0.2.0
128
167
  def attribute(name, options={})
129
168
  AttributeDefinition.new(name, options).tap do |attribute_definition|
130
- attributes << attribute_definition unless attributes.include? attribute_definition
131
- define_attribute_method attribute_definition.name
169
+ unless attributes.include? attribute_definition
170
+ define_attribute_method attribute_definition.name
171
+ attributes << attribute_definition
172
+ end
132
173
  end
133
174
  end
134
175
 
@@ -169,12 +210,19 @@ module ActiveAttr
169
210
  @attributes = attributes
170
211
  end
171
212
 
213
+ # Overrides ActiveModel::AttributeMethods
214
+ # @private
215
+ def instance_method_already_implemented?(method_name)
216
+ deprecated_object_method = DEPRECATED_OBJECT_METHODS.include?(method_name.to_s)
217
+ already_implemented = !deprecated_object_method && self.allocate.respond_to?(method_name, true)
218
+ raise DangerousAttributeError, %{an attribute method named "#{method_name}" would conflict with an existing method} if already_implemented
219
+ false
220
+ end
221
+
172
222
  private
173
223
 
174
224
  # Ruby inherited hook to assign superclass attributes to subclasses
175
225
  #
176
- # @param [Class] subclass
177
- #
178
226
  # @since 0.2.2
179
227
  def inherited(subclass)
180
228
  super
@@ -0,0 +1,37 @@
1
+ require "active_attr/chainable_initialization"
2
+ require "active_support/concern"
3
+
4
+ module ActiveAttr
5
+ # BlockInitialization allows you to build an instance in a block
6
+ #
7
+ # Including the BlockInitialization module into your class will yield the
8
+ # model instance to a block passed to when creating a new instance.
9
+ #
10
+ # @example Usage
11
+ # class Person
12
+ # include ActiveAttr::BlockInitialization
13
+ # end
14
+ #
15
+ # @since 0.3.0
16
+ module BlockInitialization
17
+ extend ActiveSupport::Concern
18
+ include ChainableInitialization
19
+
20
+ # Initialize a model and build via a block
21
+ #
22
+ # @example
23
+ # person = Person.new do |p|
24
+ # p.first_name = "Chris"
25
+ # p.last_name = "Griego"
26
+ # end
27
+ #
28
+ # person.first_name #=> "Chris"
29
+ # person.last_name #=> "Griego"
30
+ #
31
+ # @since 0.3.0
32
+ def initialize(*)
33
+ super
34
+ yield self if block_given?
35
+ end
36
+ end
37
+ end
@@ -20,18 +20,20 @@ module ActiveAttr
20
20
  # A collection of Ruby base objects
21
21
  # [Object] on Ruby 1.8
22
22
  # [Object, BasicObject] on Ruby 1.9
23
- BASE_OBJECTS = begin
24
- base_objects = []
23
+ #
24
+ # @private
25
+ BASE_OBJECTS = [].tap do |base_objects|
25
26
  superclass = Class.new
26
27
  base_objects << superclass while superclass = superclass.superclass
27
- base_objects
28
28
  end
29
29
 
30
30
  # Only append the features of this module to the class that inherits
31
31
  # directly from one of the BASE_OBJECTS
32
+ #
33
+ # @private
32
34
  def append_features(base)
33
35
  if base.respond_to? :superclass
34
- base = base.superclass while !BASE_OBJECTS.include?(base.superclass)
36
+ base = base.superclass while !BASE_OBJECTS.include? base.superclass
35
37
  end
36
38
 
37
39
  super
@@ -0,0 +1,11 @@
1
+ require "active_attr/error"
2
+
3
+ module ActiveAttr
4
+ # This exception is raised if attempting to define an attribute whose name
5
+ # conflicts with methods that are already defined
6
+ #
7
+ # @since 0.3.0
8
+ class DangerousAttributeError < ScriptError
9
+ include Error
10
+ end
11
+ end
@@ -0,0 +1,46 @@
1
+ require "active_support/concern"
2
+ require "active_support/core_ext/class/attribute"
3
+
4
+ module ActiveAttr
5
+ # Provides access to a configurable logger in model classes and instances
6
+ #
7
+ # @example Usage
8
+ # class Person
9
+ # include ActiveAttr::Logger
10
+ # end
11
+ #
12
+ # @since 0.3.0
13
+ module Logger
14
+ extend ActiveSupport::Concern
15
+
16
+ # The global default logger
17
+ #
18
+ # @return [nil, Object] logger Configured global default logger
19
+ #
20
+ # @since 0.3.0
21
+ def self.logger
22
+ @logger
23
+ end
24
+
25
+ # Determin if a global default logger is configured
26
+ #
27
+ # @since 0.3.0
28
+ def self.logger?
29
+ !!logger
30
+ end
31
+
32
+ # Configure the global default logger
33
+ #
34
+ # @param [Logger, #debug] logger The new global default logger
35
+ #
36
+ # @since 0.3.0
37
+ def self.logger=(new_logger)
38
+ @logger = new_logger
39
+ end
40
+
41
+ included do
42
+ class_attribute :logger
43
+ self.logger = ActiveAttr::Logger.logger
44
+ end
45
+ end
46
+ end