activeentity 0.0.1.beta17 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,505 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "concurrent/map"
3
4
  require "mutex_m"
4
5
  require "active_support/core_ext/enumerable"
5
6
 
6
7
  module ActiveEntity
8
+ module AMAttributeMethods
9
+ extend ActiveSupport::Concern
10
+
11
+ NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
12
+ CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
13
+
14
+ included do
15
+ class_attribute :attribute_aliases, instance_writer: false, default: {}
16
+ class_attribute :attribute_method_matchers, instance_writer: false, default: [ ClassMethods::AttributeMethodMatcher.new ]
17
+ end
18
+
19
+ module ClassMethods
20
+ # Declares a method available for all attributes with the given prefix.
21
+ # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
22
+ #
23
+ # #{prefix}#{attr}(*args, &block)
24
+ #
25
+ # to
26
+ #
27
+ # #{prefix}attribute(#{attr}, *args, &block)
28
+ #
29
+ # An instance method <tt>#{prefix}attribute</tt> must exist and accept
30
+ # at least the +attr+ argument.
31
+ #
32
+ # class Person
33
+ # include ActiveModel::AttributeMethods
34
+ #
35
+ # attr_accessor :name
36
+ # attribute_method_prefix 'clear_'
37
+ # define_attribute_methods :name
38
+ #
39
+ # private
40
+ #
41
+ # def clear_attribute(attr)
42
+ # send("#{attr}=", nil)
43
+ # end
44
+ # end
45
+ #
46
+ # person = Person.new
47
+ # person.name = 'Bob'
48
+ # person.name # => "Bob"
49
+ # person.clear_name
50
+ # person.name # => nil
51
+ def attribute_method_prefix(*prefixes)
52
+ self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
53
+ undefine_attribute_methods
54
+ end
55
+
56
+ # Declares a method available for all attributes with the given suffix.
57
+ # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
58
+ #
59
+ # #{attr}#{suffix}(*args, &block)
60
+ #
61
+ # to
62
+ #
63
+ # attribute#{suffix}(#{attr}, *args, &block)
64
+ #
65
+ # An <tt>attribute#{suffix}</tt> instance method must exist and accept at
66
+ # least the +attr+ argument.
67
+ #
68
+ # class Person
69
+ # include ActiveModel::AttributeMethods
70
+ #
71
+ # attr_accessor :name
72
+ # attribute_method_suffix '_short?'
73
+ # define_attribute_methods :name
74
+ #
75
+ # private
76
+ #
77
+ # def attribute_short?(attr)
78
+ # send(attr).length < 5
79
+ # end
80
+ # end
81
+ #
82
+ # person = Person.new
83
+ # person.name = 'Bob'
84
+ # person.name # => "Bob"
85
+ # person.name_short? # => true
86
+ def attribute_method_suffix(*suffixes)
87
+ self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
88
+ undefine_attribute_methods
89
+ end
90
+
91
+ # Declares a method available for all attributes with the given prefix
92
+ # and suffix. Uses +method_missing+ and <tt>respond_to?</tt> to rewrite
93
+ # the method.
94
+ #
95
+ # #{prefix}#{attr}#{suffix}(*args, &block)
96
+ #
97
+ # to
98
+ #
99
+ # #{prefix}attribute#{suffix}(#{attr}, *args, &block)
100
+ #
101
+ # An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
102
+ # accept at least the +attr+ argument.
103
+ #
104
+ # class Person
105
+ # include ActiveModel::AttributeMethods
106
+ #
107
+ # attr_accessor :name
108
+ # attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
109
+ # define_attribute_methods :name
110
+ #
111
+ # private
112
+ #
113
+ # def reset_attribute_to_default!(attr)
114
+ # send("#{attr}=", 'Default Name')
115
+ # end
116
+ # end
117
+ #
118
+ # person = Person.new
119
+ # person.name # => 'Gem'
120
+ # person.reset_name_to_default!
121
+ # person.name # => 'Default Name'
122
+ def attribute_method_affix(*affixes)
123
+ self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
124
+ undefine_attribute_methods
125
+ end
126
+
127
+ # Allows you to make aliases for attributes.
128
+ #
129
+ # class Person
130
+ # include ActiveModel::AttributeMethods
131
+ #
132
+ # attr_accessor :name
133
+ # attribute_method_suffix '_short?'
134
+ # define_attribute_methods :name
135
+ #
136
+ # alias_attribute :nickname, :name
137
+ #
138
+ # private
139
+ #
140
+ # def attribute_short?(attr)
141
+ # send(attr).length < 5
142
+ # end
143
+ # end
144
+ #
145
+ # person = Person.new
146
+ # person.name = 'Bob'
147
+ # person.name # => "Bob"
148
+ # person.nickname # => "Bob"
149
+ # person.name_short? # => true
150
+ # person.nickname_short? # => true
151
+ def alias_attribute(new_name, old_name)
152
+ self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
153
+ CodeGenerator.batch(self, __FILE__, __LINE__) do |owner|
154
+ attribute_method_matchers.each do |matcher|
155
+ matcher_new = matcher.method_name(new_name).to_s
156
+ matcher_old = matcher.method_name(old_name).to_s
157
+ define_proxy_call false, owner, matcher_new, matcher_old
158
+ end
159
+ end
160
+ end
161
+
162
+ # Is +new_name+ an alias?
163
+ def attribute_alias?(new_name)
164
+ attribute_aliases.key? new_name.to_s
165
+ end
166
+
167
+ # Returns the original name for the alias +name+
168
+ def attribute_alias(name)
169
+ attribute_aliases[name.to_s]
170
+ end
171
+
172
+ # Declares the attributes that should be prefixed and suffixed by
173
+ # <tt>ActiveModel::AttributeMethods</tt>.
174
+ #
175
+ # To use, pass attribute names (as strings or symbols). Be sure to declare
176
+ # +define_attribute_methods+ after you define any prefix, suffix or affix
177
+ # methods, or they will not hook in.
178
+ #
179
+ # class Person
180
+ # include ActiveModel::AttributeMethods
181
+ #
182
+ # attr_accessor :name, :age, :address
183
+ # attribute_method_prefix 'clear_'
184
+ #
185
+ # # Call to define_attribute_methods must appear after the
186
+ # # attribute_method_prefix, attribute_method_suffix or
187
+ # # attribute_method_affix declarations.
188
+ # define_attribute_methods :name, :age, :address
189
+ #
190
+ # private
191
+ #
192
+ # def clear_attribute(attr)
193
+ # send("#{attr}=", nil)
194
+ # end
195
+ # end
196
+ def define_attribute_methods(*attr_names)
197
+ CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
198
+ attr_names.flatten.each { |attr_name| define_attribute_method(attr_name, _owner: owner) }
199
+ end
200
+ end
201
+
202
+ # Declares an attribute that should be prefixed and suffixed by
203
+ # <tt>ActiveModel::AttributeMethods</tt>.
204
+ #
205
+ # To use, pass an attribute name (as string or symbol). Be sure to declare
206
+ # +define_attribute_method+ after you define any prefix, suffix or affix
207
+ # method, or they will not hook in.
208
+ #
209
+ # class Person
210
+ # include ActiveModel::AttributeMethods
211
+ #
212
+ # attr_accessor :name
213
+ # attribute_method_suffix '_short?'
214
+ #
215
+ # # Call to define_attribute_method must appear after the
216
+ # # attribute_method_prefix, attribute_method_suffix or
217
+ # # attribute_method_affix declarations.
218
+ # define_attribute_method :name
219
+ #
220
+ # private
221
+ #
222
+ # def attribute_short?(attr)
223
+ # send(attr).length < 5
224
+ # end
225
+ # end
226
+ #
227
+ # person = Person.new
228
+ # person.name = 'Bob'
229
+ # person.name # => "Bob"
230
+ # person.name_short? # => true
231
+ def define_attribute_method(attr_name, _owner: generated_attribute_methods)
232
+ CodeGenerator.batch(_owner, __FILE__, __LINE__) do |owner|
233
+ attribute_method_matchers.each do |matcher|
234
+ method_name = matcher.method_name(attr_name)
235
+
236
+ unless instance_method_already_implemented?(method_name)
237
+ generate_method = "define_method_#{matcher.target}"
238
+
239
+ if respond_to?(generate_method, true)
240
+ send(generate_method, attr_name.to_s, owner: owner)
241
+ else
242
+ define_proxy_call true, owner, method_name, matcher.target, attr_name.to_s
243
+ end
244
+ end
245
+ end
246
+ attribute_method_matchers_cache.clear
247
+ end
248
+ end
249
+
250
+ # Removes all the previously dynamically defined methods from the class.
251
+ #
252
+ # class Person
253
+ # include ActiveModel::AttributeMethods
254
+ #
255
+ # attr_accessor :name
256
+ # attribute_method_suffix '_short?'
257
+ # define_attribute_method :name
258
+ #
259
+ # private
260
+ #
261
+ # def attribute_short?(attr)
262
+ # send(attr).length < 5
263
+ # end
264
+ # end
265
+ #
266
+ # person = Person.new
267
+ # person.name = 'Bob'
268
+ # person.name_short? # => true
269
+ #
270
+ # Person.undefine_attribute_methods
271
+ #
272
+ # person.name_short? # => NoMethodError
273
+ def undefine_attribute_methods
274
+ generated_attribute_methods.module_eval do
275
+ undef_method(*instance_methods)
276
+ end
277
+ attribute_method_matchers_cache.clear
278
+ end
279
+
280
+ private
281
+ class CodeGenerator
282
+ class << self
283
+ def batch(owner, path, line)
284
+ if owner.is_a?(CodeGenerator)
285
+ yield owner
286
+ else
287
+ instance = new(owner, path, line)
288
+ result = yield instance
289
+ instance.execute
290
+ result
291
+ end
292
+ end
293
+ end
294
+
295
+ def initialize(owner, path, line)
296
+ @owner = owner
297
+ @path = path
298
+ @line = line
299
+ @sources = ["# frozen_string_literal: true\n"]
300
+ @renames = {}
301
+ end
302
+
303
+ def <<(source_line)
304
+ @sources << source_line
305
+ end
306
+
307
+ def rename_method(old_name, new_name)
308
+ @renames[old_name] = new_name
309
+ end
310
+
311
+ def execute
312
+ @owner.module_eval(@sources.join(";"), @path, @line - 1)
313
+ @renames.each do |old_name, new_name|
314
+ @owner.alias_method new_name, old_name
315
+ @owner.undef_method old_name
316
+ end
317
+ end
318
+ end
319
+ private_constant :CodeGenerator
320
+
321
+ def generated_attribute_methods
322
+ @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
323
+ end
324
+
325
+ def instance_method_already_implemented?(method_name)
326
+ generated_attribute_methods.method_defined?(method_name)
327
+ end
328
+
329
+ # The methods +method_missing+ and +respond_to?+ of this module are
330
+ # invoked often in a typical rails, both of which invoke the method
331
+ # +matched_attribute_method+. The latter method iterates through an
332
+ # array doing regular expression matches, which results in a lot of
333
+ # object creations. Most of the time it returns a +nil+ match. As the
334
+ # match result is always the same given a +method_name+, this cache is
335
+ # used to alleviate the GC, which ultimately also speeds up the app
336
+ # significantly (in our case our test suite finishes 10% faster with
337
+ # this cache).
338
+ def attribute_method_matchers_cache
339
+ @attribute_method_matchers_cache ||= Concurrent::Map.new(initial_capacity: 4)
340
+ end
341
+
342
+ def attribute_method_matchers_matching(method_name)
343
+ attribute_method_matchers_cache.compute_if_absent(method_name) do
344
+ attribute_method_matchers.map { |matcher| matcher.match(method_name) }.compact
345
+ end
346
+ end
347
+
348
+ # Define a method `name` in `mod` that dispatches to `send`
349
+ # using the given `extra` args. This falls back on `define_method`
350
+ # and `send` if the given names cannot be compiled.
351
+ def define_proxy_call(include_private, code_generator, name, target, *extra)
352
+ defn = if NAME_COMPILABLE_REGEXP.match?(name)
353
+ "def #{name}(*args)"
354
+ else
355
+ "define_method(:'#{name}') do |*args|"
356
+ end
357
+
358
+ extra = (extra.map!(&:inspect) << "*args").join(", ")
359
+
360
+ body = if CALL_COMPILABLE_REGEXP.match?(target)
361
+ "#{"self." unless include_private}#{target}(#{extra})"
362
+ else
363
+ "send(:'#{target}', #{extra})"
364
+ end
365
+
366
+ code_generator <<
367
+ defn <<
368
+ body <<
369
+ "end" <<
370
+ "ruby2_keywords(:'#{name}') if respond_to?(:ruby2_keywords, true)"
371
+ end
372
+
373
+ class AttributeMethodMatcher #:nodoc:
374
+ attr_reader :prefix, :suffix, :target
375
+
376
+ AttributeMethodMatch = Struct.new(:target, :attr_name)
377
+
378
+ def initialize(options = {})
379
+ @prefix, @suffix = options.fetch(:prefix, ""), options.fetch(:suffix, "")
380
+ @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
381
+ @target = "#{@prefix}attribute#{@suffix}"
382
+ @method_name = "#{prefix}%s#{suffix}"
383
+ end
384
+
385
+ def match(method_name)
386
+ if @regex =~ method_name
387
+ AttributeMethodMatch.new(target, $1)
388
+ end
389
+ end
390
+
391
+ def method_name(attr_name)
392
+ @method_name % attr_name
393
+ end
394
+ end
395
+ end
396
+
397
+ # Allows access to the object attributes, which are held in the hash
398
+ # returned by <tt>attributes</tt>, as though they were first-class
399
+ # methods. So a +Person+ class with a +name+ attribute can for example use
400
+ # <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use
401
+ # the attributes hash -- except for multiple assignments with
402
+ # <tt>ActiveRecord::Base#attributes=</tt>.
403
+ #
404
+ # It's also possible to instantiate related objects, so a <tt>Client</tt>
405
+ # class belonging to the +clients+ table with a +master_id+ foreign key
406
+ # can instantiate master through <tt>Client#master</tt>.
407
+ def method_missing(method, *args, &block)
408
+ if respond_to_without_attributes?(method, true)
409
+ super
410
+ else
411
+ match = matched_attribute_method(method.to_s)
412
+ match ? attribute_missing(match, *args, &block) : super
413
+ end
414
+ end
415
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
416
+
417
+ # +attribute_missing+ is like +method_missing+, but for attributes. When
418
+ # +method_missing+ is called we check to see if there is a matching
419
+ # attribute method. If so, we tell +attribute_missing+ to dispatch the
420
+ # attribute. This method can be overloaded to customize the behavior.
421
+ def attribute_missing(match, *args, &block)
422
+ __send__(match.target, match.attr_name, *args, &block)
423
+ end
424
+
425
+ # A +Person+ instance with a +name+ attribute can ask
426
+ # <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
427
+ # and <tt>person.respond_to?(:name?)</tt> which will all return +true+.
428
+ alias :respond_to_without_attributes? :respond_to?
429
+ def respond_to?(method, include_private_methods = false)
430
+ if super
431
+ true
432
+ elsif !include_private_methods && super(method, true)
433
+ # If we're here then we haven't found among non-private methods
434
+ # but found among all methods. Which means that the given method is private.
435
+ false
436
+ else
437
+ !matched_attribute_method(method.to_s).nil?
438
+ end
439
+ end
440
+
441
+ private
442
+ def attribute_method?(attr_name)
443
+ respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
444
+ end
445
+
446
+ # Returns a struct representing the matching attribute method.
447
+ # The struct's attributes are prefix, base and suffix.
448
+ def matched_attribute_method(method_name)
449
+ matches = self.class.send(:attribute_method_matchers_matching, method_name)
450
+ matches.detect { |match| attribute_method?(match.attr_name) }
451
+ end
452
+
453
+ def missing_attribute(attr_name, stack)
454
+ raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
455
+ end
456
+
457
+ def _read_attribute(attr)
458
+ __send__(attr)
459
+ end
460
+
461
+ module AttrNames # :nodoc:
462
+ DEF_SAFE_NAME = /\A[a-zA-Z_]\w*\z/
463
+
464
+ # We want to generate the methods via module_eval rather than
465
+ # define_method, because define_method is slower on dispatch.
466
+ # Evaluating many similar methods may use more memory as the instruction
467
+ # sequences are duplicated and cached (in MRI). define_method may
468
+ # be slower on dispatch, but if you're careful about the closure
469
+ # created, then define_method will consume much less memory.
470
+ #
471
+ # But sometimes the database might return columns with
472
+ # characters that are not allowed in normal method names (like
473
+ # 'my_column(omg)'. So to work around this we first define with
474
+ # the __temp__ identifier, and then use alias method to rename
475
+ # it to what we want.
476
+ #
477
+ # We are also defining a constant to hold the frozen string of
478
+ # the attribute name. Using a constant means that we do not have
479
+ # to allocate an object on each call to the attribute method.
480
+ # Making it frozen means that it doesn't get duped when used to
481
+ # key the @attributes in read_attribute.
482
+ def self.define_attribute_accessor_method(owner, attr_name, writer: false)
483
+ method_name = "#{attr_name}#{'=' if writer}"
484
+ if attr_name.ascii_only? && DEF_SAFE_NAME.match?(attr_name)
485
+ yield method_name, "'#{attr_name}'"
486
+ else
487
+ safe_name = attr_name.unpack1("h*")
488
+ const_name = "ATTR_#{safe_name}"
489
+ const_set(const_name, attr_name) unless const_defined?(const_name)
490
+ temp_method_name = "__temp__#{safe_name}#{'=' if writer}"
491
+ attr_name_expr = "::ActiveEntity::AMAttributeMethods::AttrNames::#{const_name}"
492
+ yield temp_method_name, attr_name_expr
493
+ owner.rename_method(temp_method_name, method_name)
494
+ end
495
+ end
496
+ end
497
+ end
498
+
7
499
  # = Active Entity Attribute Methods
8
500
  module AttributeMethods
9
501
  extend ActiveSupport::Concern
10
- include ActiveModel::AttributeMethods
502
+ include AMAttributeMethods # Some AM modules will include AM::AttributeMethods, we want override it
11
503
 
12
504
  included do
13
505
  initialize_generated_modules
@@ -27,6 +519,17 @@ module ActiveEntity
27
519
  include Mutex_m
28
520
  end
29
521
 
522
+ class << self
523
+ def dangerous_attribute_methods # :nodoc:
524
+ @dangerous_attribute_methods ||= (
525
+ Base.instance_methods +
526
+ Base.private_instance_methods -
527
+ Base.superclass.instance_methods -
528
+ Base.superclass.private_instance_methods
529
+ ).map { |m| -m.to_s }.to_set.freeze
530
+ end
531
+ end
532
+
30
533
  module ClassMethods
31
534
  def inherited(child_class) #:nodoc:
32
535
  child_class.initialize_generated_modules
@@ -96,7 +599,7 @@ module ActiveEntity
96
599
  # A method name is 'dangerous' if it is already (re)defined by Active Entity, but
97
600
  # not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
98
601
  def dangerous_attribute_method?(name) # :nodoc:
99
- method_defined_within?(name, Base)
602
+ ::ActiveEntity::AttributeMethods.dangerous_attribute_methods.include?(name.to_s)
100
603
  end
101
604
 
102
605
  def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
@@ -148,25 +651,52 @@ module ActiveEntity
148
651
  # class Person < ActiveEntity::Base
149
652
  # end
150
653
  #
151
- # Person.has_attribute?('name') # => true
152
- # Person.has_attribute?(:age) # => true
153
- # Person.has_attribute?(:nothing) # => false
654
+ # Person.has_attribute?('name') # => true
655
+ # Person.has_attribute?('new_name') # => true
656
+ # Person.has_attribute?(:age) # => true
657
+ # Person.has_attribute?(:nothing) # => false
154
658
  def has_attribute?(attr_name)
155
- attribute_types.key?(attr_name.to_s)
659
+ attr_name = attr_name.to_s
660
+ attr_name = attribute_aliases[attr_name] || attr_name
661
+ attribute_types.key?(attr_name)
662
+ end
663
+
664
+ def _has_attribute?(attr_name) # :nodoc:
665
+ attribute_types.key?(attr_name)
666
+ end
667
+
668
+ # https://github.com/rails/rails/blob/df475877efdcf74d7524f734ab8ad1d4704fd187/activemodel/lib/active_model/attribute_methods.rb#L208-L217
669
+ def alias_attribute(new_name, old_name)
670
+ self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
671
+ CodeGenerator.batch(self, __FILE__, __LINE__) do |owner|
672
+ attribute_method_matchers.each do |matcher|
673
+ matcher_new = matcher.method_name(new_name).to_s
674
+ matcher_old = matcher.method_name(old_name).to_s
675
+ define_proxy_call false, owner, matcher_new, matcher_old
676
+ end
677
+ end
156
678
  end
157
679
  end
158
680
 
159
681
  # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+.
160
682
  #
161
683
  # class Person < ActiveEntity::Base
684
+ # alias_attribute :new_name, :name
162
685
  # end
163
686
  #
164
687
  # person = Person.new
165
- # person.has_attribute?(:name) # => true
166
- # person.has_attribute?('age') # => true
167
- # person.has_attribute?(:nothing) # => false
688
+ # person.has_attribute?(:name) # => true
689
+ # person.has_attribute?(:new_name) # => true
690
+ # person.has_attribute?('age') # => true
691
+ # person.has_attribute?(:nothing) # => false
168
692
  def has_attribute?(attr_name)
169
- @attributes.key?(attr_name.to_s)
693
+ attr_name = attr_name.to_s
694
+ attr_name = self.class.attribute_aliases[attr_name] || attr_name
695
+ @attributes.key?(attr_name)
696
+ end
697
+
698
+ def _has_attribute?(attr_name) # :nodoc:
699
+ @attributes.key?(attr_name)
170
700
  end
171
701
 
172
702
  # Returns an array of names for the attributes available on this object.
@@ -210,6 +740,7 @@ module ActiveEntity
210
740
  # person.attribute_for_inspect(:tag_ids)
211
741
  # # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
212
742
  def attribute_for_inspect(attr_name)
743
+ attr_name = attr_name.to_s
213
744
  value = _read_attribute(attr_name)
214
745
  format_for_inspect(value)
215
746
  end
@@ -229,8 +760,9 @@ module ActiveEntity
229
760
  # task.is_done = true
230
761
  # task.attribute_present?(:title) # => true
231
762
  # task.attribute_present?(:is_done) # => true
232
- def attribute_present?(attribute)
233
- value = _read_attribute(attribute)
763
+ def attribute_present?(attr_name)
764
+ attr_name = attr_name.to_s
765
+ value = _read_attribute(attr_name)
234
766
  !value.nil? && !(value.respond_to?(:empty?) && value.empty?)
235
767
  end
236
768
 
@@ -201,6 +201,7 @@ module ActiveEntity
201
201
  )
202
202
  attribute_types[name] = cast_type
203
203
  define_default_attribute(name, default, cast_type)
204
+ define_attribute_method(name)
204
205
  end
205
206
 
206
207
  def load_schema! # :nodoc:
@@ -317,6 +317,7 @@ module ActiveEntity
317
317
  end
318
318
 
319
319
  def init_internals
320
+ @primary_key = self.class.primary_key
320
321
  @readonly = false
321
322
  @marked_for_destruction = false
322
323
 
@@ -7,10 +7,10 @@ module ActiveEntity
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 0
11
- MINOR = 0
12
- TINY = 1
13
- PRE = "beta17"
10
+ MAJOR = 6
11
+ MINOR = 3
12
+ TINY = 0
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -151,7 +151,7 @@ module ActiveEntity
151
151
  ActiveSupport::Dependencies.constantize(type_name)
152
152
  else
153
153
  type_candidate = @_type_candidates_cache[type_name]
154
- if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate)
154
+ if type_candidate && type_constant = type_candidate.safe_constantize
155
155
  return type_constant
156
156
  end
157
157
 
@@ -161,7 +161,7 @@ module ActiveEntity
161
161
  candidates << type_name
162
162
 
163
163
  candidates.each do |candidate|
164
- constant = ActiveSupport::Dependencies.safe_constantize(candidate)
164
+ constant = candidate.safe_constantize
165
165
  if candidate == constant.to_s
166
166
  @_type_candidates_cache[type_name] = candidate
167
167
  return constant