validates_timeliness 2.3.2 → 3.0.0.beta
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.
- data/CHANGELOG +12 -4
- data/LICENSE +1 -1
- data/README.rdoc +138 -280
- data/Rakefile +30 -16
- data/lib/generators/validates_timeliness/install_generator.rb +17 -0
- data/lib/generators/validates_timeliness/templates/en.yml +16 -0
- data/lib/generators/validates_timeliness/templates/validates_timeliness.rb +22 -0
- data/lib/validates_timeliness.rb +46 -52
- data/lib/validates_timeliness/attribute_methods.rb +51 -0
- data/lib/validates_timeliness/conversion.rb +69 -0
- data/lib/validates_timeliness/extensions.rb +14 -0
- data/lib/validates_timeliness/extensions/date_time_select.rb +45 -0
- data/lib/validates_timeliness/extensions/multiparameter_handler.rb +31 -0
- data/lib/validates_timeliness/helper_methods.rb +41 -0
- data/lib/validates_timeliness/orms/active_record.rb +14 -0
- data/lib/validates_timeliness/parser.rb +389 -17
- data/lib/validates_timeliness/validator.rb +37 -200
- data/lib/validates_timeliness/version.rb +1 -1
- data/spec/model_helpers.rb +27 -0
- data/spec/spec_helper.rb +74 -43
- data/spec/test_model.rb +56 -0
- data/spec/validates_timeliness/attribute_methods_spec.rb +36 -0
- data/spec/validates_timeliness/conversion_spec.rb +204 -0
- data/spec/validates_timeliness/extensions/date_time_select_spec.rb +178 -0
- data/spec/validates_timeliness/extensions/multiparameter_handler_spec.rb +21 -0
- data/spec/validates_timeliness/helper_methods_spec.rb +36 -0
- data/spec/{formats_spec.rb → validates_timeliness/parser_spec.rb} +105 -71
- data/spec/validates_timeliness/validator/after_spec.rb +59 -0
- data/spec/validates_timeliness/validator/before_spec.rb +59 -0
- data/spec/validates_timeliness/validator/is_at_spec.rb +63 -0
- data/spec/validates_timeliness/validator/on_or_after_spec.rb +59 -0
- data/spec/validates_timeliness/validator/on_or_before_spec.rb +59 -0
- data/spec/validates_timeliness/validator_spec.rb +172 -0
- data/validates_timeliness.gemspec +30 -0
- metadata +42 -40
- data/TODO +0 -8
- data/lib/validates_timeliness/action_view/instance_tag.rb +0 -52
- data/lib/validates_timeliness/active_record/attribute_methods.rb +0 -77
- data/lib/validates_timeliness/active_record/multiparameter_attributes.rb +0 -69
- data/lib/validates_timeliness/formats.rb +0 -368
- data/lib/validates_timeliness/locale/en.new.yml +0 -18
- data/lib/validates_timeliness/locale/en.old.yml +0 -18
- data/lib/validates_timeliness/matcher.rb +0 -1
- data/lib/validates_timeliness/spec/rails/matchers/validate_timeliness.rb +0 -162
- data/lib/validates_timeliness/validation_methods.rb +0 -46
- data/spec/action_view/instance_tag_spec.rb +0 -194
- data/spec/active_record/attribute_methods_spec.rb +0 -157
- data/spec/active_record/multiparameter_attributes_spec.rb +0 -118
- data/spec/ginger_scenarios.rb +0 -19
- data/spec/parser_spec.rb +0 -65
- data/spec/resources/application.rb +0 -2
- data/spec/resources/person.rb +0 -3
- data/spec/resources/schema.rb +0 -10
- data/spec/resources/sqlite_patch.rb +0 -19
- data/spec/spec/rails/matchers/validate_timeliness_spec.rb +0 -245
- data/spec/time_travel/MIT-LICENSE +0 -20
- data/spec/time_travel/time_extensions.rb +0 -33
- data/spec/time_travel/time_travel.rb +0 -12
- data/spec/validator_spec.rb +0 -723
@@ -1,230 +1,67 @@
|
|
1
|
-
|
1
|
+
require 'active_model/validator'
|
2
|
+
|
2
3
|
module ValidatesTimeliness
|
4
|
+
class Validator < ActiveModel::EachValidator
|
5
|
+
include Conversion
|
3
6
|
|
4
|
-
|
5
|
-
cattr_accessor :error_value_formats
|
6
|
-
cattr_accessor :ignore_restriction_errors
|
7
|
-
self.ignore_restriction_errors = false
|
7
|
+
attr_reader :type
|
8
8
|
|
9
|
-
|
9
|
+
RESTRICTIONS = {
|
10
10
|
:is_at => :==,
|
11
|
-
:equal_to => :==,
|
12
11
|
:before => :<,
|
13
12
|
:after => :>,
|
14
13
|
:on_or_before => :<=,
|
15
14
|
:on_or_after => :>=,
|
16
|
-
|
17
|
-
}
|
18
|
-
|
19
|
-
VALID_OPTION_KEYS = [
|
20
|
-
:on, :if, :unless, :allow_nil, :empty, :allow_blank,
|
21
|
-
:with_time, :with_date, :ignore_usec, :format,
|
22
|
-
:invalid_time_message, :invalid_date_message, :invalid_datetime_message
|
23
|
-
] + RESTRICTION_METHODS.keys.map {|option| [option, "#{option}_message".to_sym] }.flatten
|
24
|
-
|
25
|
-
DEFAULT_OPTIONS = { :on => :save, :type => :datetime, :allow_nil => false, :allow_blank => false, :ignore_usec => false }
|
26
|
-
|
27
|
-
attr_reader :configuration, :type
|
15
|
+
}.freeze
|
28
16
|
|
29
|
-
def
|
30
|
-
|
31
|
-
@type = @configuration.delete(:type)
|
32
|
-
validate_options(@configuration)
|
17
|
+
def self.kind
|
18
|
+
:timeliness
|
33
19
|
end
|
34
20
|
|
35
|
-
def
|
36
|
-
|
21
|
+
def initialize(options)
|
22
|
+
@type = options.delete(:type) || :datetime
|
23
|
+
@allow_nil, @allow_blank = options.delete(:allow_nil), options.delete(:allow_blank)
|
24
|
+
@restrictions_to_check = RESTRICTIONS.keys & options.keys
|
37
25
|
|
38
|
-
if
|
39
|
-
|
26
|
+
if range = options.delete(:between)
|
27
|
+
raise ArgumentError, ":between must be a Range or an Array" unless range.is_a?(Range) || range.is_a?(Array)
|
28
|
+
options[:on_or_after], options[:on_or_before] = range.first, range.last
|
40
29
|
end
|
41
|
-
|
42
|
-
return if (raw_value.nil? && configuration[:allow_nil]) || (raw_value.blank? && configuration[:allow_blank])
|
43
|
-
|
44
|
-
return add_error(record, attr_name, :blank) if raw_value.blank?
|
45
|
-
return add_error(record, attr_name, "invalid_#{type}".to_sym) if value.nil?
|
46
|
-
|
47
|
-
validate_restrictions(record, attr_name, value)
|
30
|
+
super
|
48
31
|
end
|
49
32
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def raw_value(record, attr_name)
|
57
|
-
record.send("#{attr_name}_before_type_cast") rescue nil
|
58
|
-
end
|
59
|
-
|
60
|
-
def validate_restrictions(record, attr_name, value)
|
61
|
-
if configuration[:with_time] || configuration[:with_date]
|
62
|
-
value = combine_date_and_time(value, record)
|
63
|
-
end
|
33
|
+
def validate_each(record, attr_name, value)
|
34
|
+
raw_value = record._timeliness_raw_value_for(attr_name) || value
|
35
|
+
return if (@allow_nil && raw_value.nil?) || (@allow_blank && raw_value.blank?)
|
64
36
|
|
65
|
-
|
37
|
+
@timezone_aware = record.class.timeliness_attribute_timezone_aware?(attr_name)
|
38
|
+
value = parse(value) if value.is_a?(String)
|
39
|
+
value = type_cast_value(value, @type)
|
66
40
|
|
67
|
-
return if value.
|
41
|
+
return record.errors.add(attr_name, :"invalid_#{@type}") if value.blank?
|
68
42
|
|
69
|
-
|
70
|
-
next unless restriction = configuration[option]
|
43
|
+
@restrictions_to_check.each do |restriction|
|
71
44
|
begin
|
72
|
-
|
73
|
-
next if restriction.nil?
|
74
|
-
restriction = self.class.type_cast_value(restriction, implied_type, configuration[:ignore_usec])
|
45
|
+
restriction_value = type_cast_value(evaluate_option_value(options[restriction], record), @type)
|
75
46
|
|
76
|
-
unless
|
77
|
-
|
47
|
+
unless value.send(RESTRICTIONS[restriction], restriction_value)
|
48
|
+
return record.errors.add(attr_name, restriction, :message => options[:"#{restriction}_message"], :restriction => format_error_value(restriction_value))
|
78
49
|
end
|
79
|
-
rescue
|
80
|
-
unless
|
81
|
-
|
50
|
+
rescue => e
|
51
|
+
unless ValidatesTimeliness.ignore_restriction_errors
|
52
|
+
record.errors[attr_name] = "Error occurred validating #{attr_name} for #{restriction.inspect} restriction:\n#{e.message}"
|
82
53
|
end
|
83
54
|
end
|
84
55
|
end
|
85
56
|
end
|
86
57
|
|
87
|
-
def
|
88
|
-
format =
|
89
|
-
|
90
|
-
|
91
|
-
if defined?(I18n)
|
92
|
-
interpolations = {}
|
93
|
-
keys = restriction.size == 1 ? [:restriction] : [:earliest, :latest]
|
94
|
-
keys.each_with_index {|key, i| interpolations[key] = restriction[i].strftime(format) }
|
95
|
-
interpolations
|
96
|
-
else
|
97
|
-
restriction.map {|r| r.strftime(format) }
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def evaluate_restriction(restriction, value, comparator)
|
102
|
-
return true if restriction.nil?
|
103
|
-
|
104
|
-
case comparator
|
105
|
-
when Symbol
|
106
|
-
value.send(comparator, restriction)
|
107
|
-
when Proc
|
108
|
-
comparator.call(value, restriction)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
def add_error(record, attr_name, message, interpolate=nil)
|
113
|
-
if defined?(I18n)
|
114
|
-
custom = custom_error_messages[message]
|
115
|
-
record.errors.add(attr_name, message, { :default => custom }.merge(interpolate || {}))
|
116
|
-
else
|
117
|
-
message = error_messages[message] if message.is_a?(Symbol)
|
118
|
-
record.errors.add(attr_name, message % interpolate)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
def custom_error_messages
|
123
|
-
@custom_error_messages ||= configuration.inject({}) {|msgs, (k, v)|
|
124
|
-
if md = /(.*)_message$/.match(k.to_s)
|
125
|
-
msgs[md[1].to_sym] = v
|
126
|
-
end
|
127
|
-
msgs
|
128
|
-
}
|
129
|
-
end
|
130
|
-
|
131
|
-
def combine_date_and_time(value, record)
|
132
|
-
if type == :date
|
133
|
-
date = value
|
134
|
-
time = configuration[:with_time]
|
135
|
-
else
|
136
|
-
date = configuration[:with_date]
|
137
|
-
time = value
|
138
|
-
end
|
139
|
-
date, time = self.class.evaluate_option_value(date, :date, record), self.class.evaluate_option_value(time, :time, record)
|
140
|
-
return if date.nil? || time.nil?
|
141
|
-
ValidatesTimeliness::Parser.make_time([date.year, date.month, date.day, time.hour, time.min, time.sec, time.usec])
|
142
|
-
end
|
143
|
-
|
144
|
-
def validate_options(options)
|
145
|
-
if options.key?(:equal_to)
|
146
|
-
::ActiveSupport::Deprecation.warn("ValidatesTimeliness :equal_to option is deprecated due to clash with a default Rails option. Use :is_at instead. You will need to fix any I18n error message references to this option date/time attributes now.")
|
147
|
-
options[:is_at] = options.delete(:equal_to)
|
148
|
-
options[:is_at_message] = options.delete(:equal_to_message)
|
149
|
-
end
|
150
|
-
|
151
|
-
invalid_for_type = ([:time, :date, :datetime] - [type]).map {|k| "invalid_#{k}_message".to_sym }
|
152
|
-
invalid_for_type << :with_date unless type == :time
|
153
|
-
invalid_for_type << :with_time unless type == :date
|
154
|
-
options.assert_valid_keys(VALID_OPTION_KEYS - invalid_for_type)
|
155
|
-
end
|
156
|
-
|
157
|
-
def implied_type
|
158
|
-
@implied_type ||= configuration[:with_date] || configuration[:with_time] ? :datetime : type
|
159
|
-
end
|
160
|
-
|
161
|
-
# class methods
|
162
|
-
class << self
|
163
|
-
|
164
|
-
def error_value_format_for(type)
|
165
|
-
if defined?(I18n)
|
166
|
-
# work around for syntax check in vendored I18n for Rails <= 2.3.3
|
167
|
-
I18n.t('validates_timeliness.error_value_formats')[type] || error_value_formats[type]
|
168
|
-
else
|
169
|
-
error_value_formats[type]
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
def evaluate_option_value(value, type, record)
|
174
|
-
case value
|
175
|
-
when Time, Date
|
176
|
-
value
|
177
|
-
when Symbol
|
178
|
-
evaluate_option_value(record.send(value), type, record)
|
179
|
-
when Proc
|
180
|
-
result = value.arity > 0 ? value.call(record) : value.call
|
181
|
-
evaluate_option_value(result, type, record)
|
182
|
-
when Array
|
183
|
-
value.map {|r| evaluate_option_value(r, type, record) }.sort
|
184
|
-
when Range
|
185
|
-
evaluate_option_value([value.first, value.last], type, record)
|
186
|
-
else
|
187
|
-
ValidatesTimeliness::Parser.parse(value, type, :strict => false)
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def type_cast_value(value, type, ignore_usec=false)
|
192
|
-
if value.is_a?(Array)
|
193
|
-
value.map {|v| type_cast_value(v, type, ignore_usec) }
|
194
|
-
else
|
195
|
-
value = case type
|
196
|
-
when :time
|
197
|
-
dummy_time(value)
|
198
|
-
when :date
|
199
|
-
value.to_date
|
200
|
-
when :datetime
|
201
|
-
if value.is_a?(Time) || value.is_a?(DateTime)
|
202
|
-
value.to_time
|
203
|
-
else
|
204
|
-
value.to_time(ValidatesTimeliness.default_timezone)
|
205
|
-
end
|
206
|
-
else
|
207
|
-
nil
|
208
|
-
end
|
209
|
-
if ignore_usec && value.is_a?(Time)
|
210
|
-
ValidatesTimeliness::Parser.make_time(Array(value).reverse[4..9])
|
211
|
-
else
|
212
|
-
value
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def dummy_time(value)
|
218
|
-
if value.is_a?(Time) || value.is_a?(DateTime)
|
219
|
-
time = [value.hour, value.min, value.sec]
|
220
|
-
else
|
221
|
-
time = [0,0,0]
|
222
|
-
end
|
223
|
-
dummy_date = ValidatesTimeliness::Formats.dummy_date_for_time_type
|
224
|
-
ValidatesTimeliness::Parser.make_time(dummy_date + time)
|
225
|
-
end
|
226
|
-
|
58
|
+
def format_error_value(value)
|
59
|
+
format = I18n.t(@type, :scope => 'validates_timeliness.error_value_formats')
|
60
|
+
value.strftime(format)
|
227
61
|
end
|
228
62
|
|
229
63
|
end
|
230
64
|
end
|
65
|
+
|
66
|
+
# Compatibility with ActiveModel validates method which matches option keys to their validator class
|
67
|
+
TimelinessValidator = ValidatesTimeliness::Validator
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module ModelHelpers
|
2
|
+
|
3
|
+
# Some test helpers from Rails source
|
4
|
+
def invalid!(attr_name, values, error = nil)
|
5
|
+
with_each_person_value(attr_name, values) do |record, value|
|
6
|
+
record.should be_invalid
|
7
|
+
record.errors[attr_name].size.should >= 1
|
8
|
+
record.errors[attr_name].first.should == error if error
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def valid!(attr_name, values)
|
13
|
+
with_each_person_value(attr_name, values) do |record, value|
|
14
|
+
record.should be_valid
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_each_person_value(attr_name, values)
|
19
|
+
record = Person.new
|
20
|
+
values = [values] unless values.is_a?(Array)
|
21
|
+
values.each do |value|
|
22
|
+
record.send("#{attr_name}=", value)
|
23
|
+
yield record, value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,58 +1,89 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
$:.unshift(File.dirname(__FILE__) + '/resources')
|
1
|
+
require 'rspec'
|
2
|
+
require 'rspec/autorun'
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
require '
|
8
|
-
require '
|
4
|
+
require 'active_model'
|
5
|
+
require 'active_model/validations'
|
6
|
+
require 'active_record'
|
7
|
+
require 'action_view'
|
8
|
+
require 'timecop'
|
9
|
+
require 'rspec_tag_matchers'
|
10
|
+
require 'model_helpers'
|
9
11
|
|
10
|
-
|
12
|
+
require 'validates_timeliness'
|
13
|
+
require 'test_model'
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
rescue LoadError
|
18
|
-
end
|
19
|
-
if ENV['VERSION']
|
20
|
-
gem 'rails', ENV['VERSION']
|
21
|
-
else
|
22
|
-
gem 'rails'
|
23
|
-
end
|
15
|
+
ValidatesTimeliness.setup do |c|
|
16
|
+
c.extend_orms = [ :active_record ]
|
17
|
+
c.enable_date_time_select_extension!
|
18
|
+
c.enable_multiparameter_extension!
|
19
|
+
c.default_timezone = :utc
|
24
20
|
end
|
25
21
|
|
26
|
-
|
22
|
+
Time.zone = 'Australia/Melbourne'
|
27
23
|
|
28
|
-
|
29
|
-
|
30
|
-
require 'active_record/version'
|
31
|
-
require 'action_controller'
|
32
|
-
require 'action_view'
|
33
|
-
require 'action_mailer'
|
24
|
+
LOCALE_PATH = File.expand_path(File.dirname(__FILE__) + '/../lib/generators/validates_timeliness/templates/en.yml')
|
25
|
+
I18n.load_path.unshift(LOCALE_PATH)
|
34
26
|
|
35
|
-
|
36
|
-
|
27
|
+
# Extend TestModel as you would another ORM/ODM module
|
28
|
+
module TestModel
|
29
|
+
include ValidatesTimeliness::HelperMethods
|
30
|
+
include ValidatesTimeliness::AttributeMethods
|
37
31
|
|
38
|
-
|
39
|
-
|
40
|
-
|
32
|
+
def self.included(base)
|
33
|
+
base.extend HookMethods
|
34
|
+
end
|
41
35
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
36
|
+
module HookMethods
|
37
|
+
# Hook method for attribute method generation
|
38
|
+
def define_attribute_methods(attr_names)
|
39
|
+
super
|
40
|
+
define_timeliness_methods
|
41
|
+
end
|
46
42
|
|
47
|
-
|
48
|
-
|
43
|
+
# Hook into native time zone handling check, if any
|
44
|
+
def timeliness_attribute_timezone_aware?(attr_name)
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
49
|
|
50
|
-
|
50
|
+
class Person
|
51
|
+
include TestModel
|
52
|
+
self.model_attributes = :birth_date, :birth_time, :birth_datetime
|
53
|
+
validates_date :birth_date
|
54
|
+
validates_time :birth_time
|
55
|
+
validates_datetime :birth_datetime
|
56
|
+
define_attribute_methods model_attributes
|
57
|
+
end
|
51
58
|
|
52
|
-
ActiveRecord::Migration.verbose = false
|
53
59
|
ActiveRecord::Base.establish_connection({:adapter => 'sqlite3', :database => ':memory:'})
|
60
|
+
ActiveRecord::Migration.verbose = false
|
61
|
+
ActiveRecord::Schema.define(:version => 1) do
|
62
|
+
create_table :employees, :force => true do |t|
|
63
|
+
t.string :first_name
|
64
|
+
t.string :last_name
|
65
|
+
t.datetime :birth_date
|
66
|
+
t.datetime :birth_time
|
67
|
+
t.datetime :birth_datetime
|
68
|
+
end
|
69
|
+
end
|
54
70
|
|
55
|
-
|
71
|
+
class Employee < ActiveRecord::Base
|
72
|
+
validates_date :birth_date
|
73
|
+
validates_time :birth_time
|
74
|
+
validates_datetime :birth_datetime
|
75
|
+
define_attribute_methods
|
76
|
+
end
|
56
77
|
|
57
|
-
|
58
|
-
|
78
|
+
Rspec.configure do |c|
|
79
|
+
c.mock_with :rspec
|
80
|
+
c.include(RspecTagMatchers)
|
81
|
+
c.before do
|
82
|
+
Person.reset_callbacks(:validate)
|
83
|
+
Person.timeliness_validated_attributes = {}
|
84
|
+
Person._validators.clear
|
85
|
+
Employee.reset_callbacks(:validate)
|
86
|
+
Employee.timeliness_validated_attributes = {}
|
87
|
+
Employee._validators.clear
|
88
|
+
end
|
89
|
+
end
|
data/spec/test_model.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module TestModel
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
extend ActiveModel::Translation
|
6
|
+
include ActiveModel::Validations
|
7
|
+
include ActiveModel::AttributeMethods
|
8
|
+
include DynamicMethods
|
9
|
+
|
10
|
+
attribute_method_suffix ""
|
11
|
+
attribute_method_suffix "="
|
12
|
+
cattr_accessor :model_attributes
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def define_method_attribute=(attr_name)
|
17
|
+
generated_attribute_methods.module_eval("def #{attr_name}=(new_value); @attributes['#{attr_name}']=new_value ; end", __FILE__, __LINE__)
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_method_attribute(attr_name)
|
21
|
+
generated_attribute_methods.module_eval("def #{attr_name}; @attributes['#{attr_name}']; end", __FILE__, __LINE__)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module DynamicMethods
|
26
|
+
def method_missing(method_id, *args, &block)
|
27
|
+
if !self.class.attribute_methods_generated?
|
28
|
+
self.class.define_attribute_methods self.class.model_attributes.map(&:to_s)
|
29
|
+
method_name = method_id.to_s
|
30
|
+
send(method_id, *args, &block)
|
31
|
+
else
|
32
|
+
super
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(attributes = nil)
|
38
|
+
@attributes = self.class.model_attributes.inject({}) do |hash, column|
|
39
|
+
hash[column.to_s] = nil
|
40
|
+
hash
|
41
|
+
end
|
42
|
+
self.attributes = attributes unless attributes.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def attributes
|
46
|
+
@attributes.keys
|
47
|
+
end
|
48
|
+
|
49
|
+
def attributes=(new_attributes={})
|
50
|
+
new_attributes.each do |key, value|
|
51
|
+
send "#{key}=", value
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|