activeentity 0.0.1.beta14 → 0.0.1.beta15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +4 -4
  4. data/Rakefile +7 -7
  5. data/lib/active_entity.rb +29 -7
  6. data/lib/active_entity/aggregations.rb +2 -1
  7. data/lib/active_entity/associations.rb +46 -24
  8. data/lib/active_entity/associations/{embedded → embeds}/association.rb +2 -2
  9. data/lib/active_entity/associations/{embedded → embeds}/builder/association.rb +1 -1
  10. data/lib/active_entity/associations/{embedded → embeds}/builder/collection_association.rb +1 -1
  11. data/lib/active_entity/associations/{embedded → embeds}/builder/embedded_in.rb +1 -1
  12. data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_many.rb +1 -1
  13. data/lib/active_entity/associations/{embedded → embeds}/builder/embeds_one.rb +1 -1
  14. data/lib/active_entity/associations/{embedded → embeds}/builder/singular_association.rb +1 -1
  15. data/lib/active_entity/associations/{embedded → embeds}/collection_association.rb +1 -1
  16. data/lib/active_entity/associations/{embedded → embeds}/collection_proxy.rb +2 -2
  17. data/lib/active_entity/associations/{embedded → embeds}/embedded_in_association.rb +1 -1
  18. data/lib/active_entity/associations/{embedded → embeds}/embeds_many_association.rb +1 -1
  19. data/lib/active_entity/associations/{embedded → embeds}/embeds_one_association.rb +2 -1
  20. data/lib/active_entity/associations/{embedded → embeds}/singular_association.rb +1 -1
  21. data/lib/active_entity/attribute_assignment.rb +9 -3
  22. data/lib/active_entity/attribute_methods.rb +12 -11
  23. data/lib/active_entity/attribute_methods/before_type_cast.rb +1 -1
  24. data/lib/active_entity/attribute_methods/dirty.rb +13 -0
  25. data/lib/active_entity/attribute_methods/primary_key.rb +1 -1
  26. data/lib/active_entity/attribute_methods/query.rb +11 -4
  27. data/lib/active_entity/attribute_methods/read.rb +1 -3
  28. data/lib/active_entity/attribute_methods/time_zone_conversion.rb +2 -0
  29. data/lib/active_entity/attribute_methods/write.rb +4 -6
  30. data/lib/active_entity/attributes.rb +76 -2
  31. data/lib/active_entity/base.rb +3 -12
  32. data/lib/active_entity/core.rb +97 -39
  33. data/lib/active_entity/define_callbacks.rb +4 -0
  34. data/lib/active_entity/enum.rb +30 -4
  35. data/lib/active_entity/errors.rb +0 -11
  36. data/lib/active_entity/gem_version.rb +1 -1
  37. data/lib/active_entity/inheritance.rb +4 -106
  38. data/lib/active_entity/integration.rb +1 -1
  39. data/lib/active_entity/model_schema.rb +0 -12
  40. data/lib/active_entity/nested_attributes.rb +5 -12
  41. data/lib/active_entity/railtie.rb +61 -1
  42. data/lib/active_entity/readonly_attributes.rb +9 -1
  43. data/lib/active_entity/reflection.rb +22 -19
  44. data/lib/active_entity/serialization.rb +9 -6
  45. data/lib/active_entity/store.rb +51 -2
  46. data/lib/active_entity/type.rb +8 -8
  47. data/lib/active_entity/type/registry.rb +5 -5
  48. data/lib/active_entity/{validate_embedded_association.rb → validate_embeds_association.rb} +6 -6
  49. data/lib/active_entity/validations.rb +2 -6
  50. data/lib/active_entity/validations/associated.rb +1 -1
  51. data/lib/active_entity/validations/{uniqueness_in_embedding.rb → uniqueness_in_embeds.rb} +1 -1
  52. data/lib/active_entity/validations/uniqueness_on_active_record.rb +46 -40
  53. metadata +27 -30
  54. data/lib/active_entity/type/decimal_without_scale.rb +0 -15
  55. data/lib/active_entity/type/hash_lookup_type_map.rb +0 -25
  56. data/lib/active_entity/type/type_map.rb +0 -62
  57. data/lib/tasks/active_entity_tasks.rake +0 -6
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveEntity
4
+ # This module exists because ActiveEntity::AttributeMethods::Dirty needs to
5
+ # define callbacks, but continue to have its version of +save+ be the super
6
+ # method of ActiveEntity::Callbacks. This will be removed when the removal
7
+ # of deprecated code removes this need.
4
8
  module DefineCallbacks
5
9
  extend ActiveSupport::Concern
6
10
 
@@ -27,6 +27,32 @@ module ActiveEntity
27
27
  # conversation.status.nil? # => true
28
28
  # conversation.status # => nil
29
29
  #
30
+ # Scopes based on the allowed values of the enum field will be provided
31
+ # as well. With the above example:
32
+ #
33
+ # Conversation.active
34
+ # Conversation.not_active
35
+ # Conversation.archived
36
+ # Conversation.not_archived
37
+ #
38
+ # Of course, you can also query them directly if the scopes don't fit your
39
+ # needs:
40
+ #
41
+ # Conversation.where(status: [:active, :archived])
42
+ # Conversation.where.not(status: :active)
43
+ #
44
+ # Defining scopes can be disabled by setting +:_scopes+ to +false+.
45
+ #
46
+ # class Conversation < ActiveEntity::Base
47
+ # enum status: [ :active, :archived ], _scopes: false
48
+ # end
49
+ #
50
+ # You can set the default value from the database declaration, like:
51
+ #
52
+ # create_table :conversations do |t|
53
+ # t.column :status, :integer, default: 0
54
+ # end
55
+ #
30
56
  # Good practice is to let the first declared status be the default.
31
57
  #
32
58
  # Finally, it's also possible to explicitly map the relation between attribute and
@@ -97,12 +123,12 @@ module ActiveEntity
97
123
  end
98
124
 
99
125
  def cast(value)
100
- return if value.blank?
101
-
102
126
  if mapping.has_key?(value)
103
127
  value.to_s
104
128
  elsif mapping.has_value?(value)
105
129
  mapping.key(value)
130
+ elsif value.blank?
131
+ nil
106
132
  else
107
133
  assert_valid_value(value)
108
134
  end
@@ -124,6 +150,7 @@ module ActiveEntity
124
150
  end
125
151
 
126
152
  private
153
+
127
154
  attr_reader :name, :mapping, :subtype
128
155
  end
129
156
 
@@ -182,6 +209,7 @@ module ActiveEntity
182
209
  end
183
210
 
184
211
  private
212
+
185
213
  def _enum_methods_module
186
214
  @_enum_methods_module ||= begin
187
215
  mod = Module.new
@@ -212,8 +240,6 @@ module ActiveEntity
212
240
  def detect_enum_conflict!(enum_name, method_name, klass_method = false)
213
241
  if klass_method && dangerous_class_method?(method_name)
214
242
  raise_conflict_error(enum_name, method_name, type: "class")
215
- # elsif klass_method && method_defined_within?(method_name, Relation)
216
- # raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name)
217
243
  elsif !klass_method && dangerous_attribute_method?(method_name)
218
244
  raise_conflict_error(enum_name, method_name)
219
245
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
@@ -7,13 +7,6 @@ module ActiveEntity
7
7
  class ActiveEntityError < StandardError
8
8
  end
9
9
 
10
- # Raised when the single-table inheritance mechanism fails to locate the subclass
11
- # (for example due to improper usage of column that
12
- # {ActiveEntity::Base.inheritance_attribute}[rdoc-ref:ModelSchema::ClassMethods#inheritance_attribute]
13
- # points to).
14
- class SubclassNotFound < ActiveEntityError
15
- end
16
-
17
10
  # Raised when an object assigned to an association has an incorrect type.
18
11
  #
19
12
  # class Ticket < ActiveEntity::Base
@@ -41,10 +34,6 @@ module ActiveEntity
41
34
  class ConfigurationError < ActiveEntityError
42
35
  end
43
36
 
44
- # Raised on attempt to update record that is instantiated as read only.
45
- class ReadOnlyRecord < ActiveEntityError
46
- end
47
-
48
37
  # Raised when attribute has a name reserved by Active Entity (when attribute
49
38
  # has name of one of Active Entity instance methods).
50
39
  class DangerousAttributeError < ActiveEntityError
@@ -10,7 +10,7 @@ module ActiveEntity
10
10
  MAJOR = 0
11
11
  MINOR = 0
12
12
  TINY = 1
13
- PRE = "beta14"
13
+ PRE = "beta15"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -6,7 +6,7 @@ module ActiveEntity
6
6
  # == Single table inheritance
7
7
  #
8
8
  # Active Entity allows inheritance by storing the name of the class in a column that by
9
- # default is named "type" (can be changed by overwriting <tt>Base.inheritance_attribute</tt>).
9
+ # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
10
10
  # This means that an inheritance looking like this:
11
11
  #
12
12
  # class Company < ActiveEntity::Base; end
@@ -37,12 +37,6 @@ module ActiveEntity
37
37
  module Inheritance
38
38
  extend ActiveSupport::Concern
39
39
 
40
- included do
41
- # Determines whether to store the full constant name including namespace when using STI.
42
- # This is true, by default.
43
- class_attribute :store_full_sti_class, instance_writer: false, default: true
44
- end
45
-
46
40
  module ClassMethods
47
41
  # Determines if one of the attributes passed in is the inheritance column,
48
42
  # and if the inheritance column is attr accessible, it initializes an
@@ -52,19 +46,7 @@ module ActiveEntity
52
46
  raise NotImplementedError, "#{self} is an abstract class and cannot be instantiated."
53
47
  end
54
48
 
55
- if has_attribute?(inheritance_attribute)
56
- subclass = subclass_from_attributes(attributes)
57
-
58
- if subclass.nil? && base_class?
59
- subclass = subclass_from_attributes(_default_attributes)
60
- end
61
- end
62
-
63
- if subclass && subclass != self
64
- subclass.new(attributes, &block)
65
- else
66
- super
67
- end
49
+ super
68
50
  end
69
51
 
70
52
  # Returns +true+ if this does not need STI type condition. Returns
@@ -75,15 +57,10 @@ module ActiveEntity
75
57
  elsif superclass.abstract_class?
76
58
  superclass.descends_from_active_entity?
77
59
  else
78
- superclass == Base || !has_attribute?(inheritance_attribute)
60
+ superclass == Base
79
61
  end
80
62
  end
81
63
 
82
- def finder_needs_type_condition? #:nodoc:
83
- # This is like this because benchmarking justifies the strange :false stuff
84
- :true == (@finder_needs_type_condition ||= descends_from_active_entity? ? :false : :true)
85
- end
86
-
87
64
  # Returns the class descending directly from ActiveEntity::Base, or
88
65
  # an abstract class, if any, in the inheritance hierarchy.
89
66
  #
@@ -94,7 +71,7 @@ module ActiveEntity
94
71
  # and C.base_class would return B as the answer since A is an abstract_class.
95
72
  def base_class
96
73
  unless self < Base
97
- raise ActiveEntityError, "#{name} doesn't belong in a hierarchy descending from Active Entity"
74
+ raise ActiveEntityError, "#{name} doesn't belong in a hierarchy descending from ActiveEntity"
98
75
  end
99
76
 
100
77
  if superclass == Base || superclass.abstract_class?
@@ -158,10 +135,6 @@ module ActiveEntity
158
135
  defined?(@abstract_class) && @abstract_class == true
159
136
  end
160
137
 
161
- def sti_name
162
- store_full_sti_class ? name : name.demodulize
163
- end
164
-
165
138
  def inherited(subclass)
166
139
  subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
167
140
  super
@@ -198,81 +171,6 @@ module ActiveEntity
198
171
  raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
199
172
  end
200
173
  end
201
-
202
- private
203
-
204
- # Called by +instantiate+ to decide which class to use for a new
205
- # record instance. For single-table inheritance, we check the record
206
- # for a +type+ column and return the corresponding class.
207
- def discriminate_class_for_record(record)
208
- if using_single_table_inheritance?(record)
209
- find_sti_class(record[inheritance_attribute])
210
- else
211
- super
212
- end
213
- end
214
-
215
- def using_single_table_inheritance?(record)
216
- record[inheritance_attribute].present? && has_attribute?(inheritance_attribute)
217
- end
218
-
219
- def find_sti_class(type_name)
220
- type_name = base_class.type_for_attribute(inheritance_attribute).cast(type_name)
221
- subclass = begin
222
- if store_full_sti_class
223
- ActiveSupport::Dependencies.constantize(type_name)
224
- else
225
- compute_type(type_name)
226
- end
227
- rescue NameError
228
- raise SubclassNotFound,
229
- "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
230
- "This error is raised because the attribute '#{inheritance_attribute}' is reserved for storing the class in case of inheritance. " \
231
- "Please rename this attribute if you didn't intend it to be used for storing the inheritance class " \
232
- "or overwrite #{name}.inheritance_attribute to use another attribute for that information."
233
- end
234
- unless subclass == self || descendants.include?(subclass)
235
- raise SubclassNotFound, "Invalid single-table inheritance type: #{subclass.name} is not a subclass of #{name}"
236
- end
237
- subclass
238
- end
239
-
240
- # Detect the subclass from the inheritance column of attrs. If the inheritance column value
241
- # is not self or a valid subclass, raises ActiveEntity::SubclassNotFound
242
- def subclass_from_attributes(attrs)
243
- attrs = attrs.to_h if attrs.respond_to?(:permitted?)
244
- if attrs.is_a?(Hash)
245
- subclass_name = attrs[inheritance_attribute] || attrs[inheritance_attribute.to_sym]
246
-
247
- if subclass_name.present?
248
- find_sti_class(subclass_name)
249
- end
250
- end
251
- end
252
174
  end
253
-
254
- def initialize_dup(other)
255
- super
256
- ensure_proper_type
257
- end
258
-
259
- private
260
-
261
- def initialize_internals_callback
262
- super
263
- ensure_proper_type
264
- end
265
-
266
- # Sets the attribute used for single table inheritance to this class name if this is not the
267
- # ActiveEntity::Base descendant.
268
- # Considering the hierarchy Reply < Message < ActiveEntity::Base, this makes it possible to
269
- # do Reply.new without having to set <tt>Reply[Reply.inheritance_attribute] = "Reply"</tt> yourself.
270
- # No such attribute would be set for objects of the Message class in that example.
271
- def ensure_proper_type
272
- klass = self.class
273
- if klass.finder_needs_type_condition?
274
- _write_attribute(klass.inheritance_attribute, klass.sti_name)
275
- end
276
- end
277
175
  end
278
176
  end
@@ -30,7 +30,7 @@ module ActiveEntity
30
30
  # user_path(user) # => "/users/Phusion"
31
31
  def to_param
32
32
  # We can't use alias_method here, because method 'id' optimizes itself on the fly.
33
- id && id.to_s # Be sure to stringify the id for routes
33
+ id&.to_s # Be sure to stringify the id for routes
34
34
  end
35
35
 
36
36
  module ClassMethods
@@ -9,22 +9,10 @@ module ActiveEntity
9
9
  included do
10
10
  delegate :type_for_attribute, to: :class
11
11
 
12
- self.inheritance_attribute = "type"
13
-
14
12
  initialize_load_schema_monitor
15
13
  end
16
14
 
17
15
  module ClassMethods
18
- def inheritance_attribute
19
- (@inheritance_attribute ||= nil) || superclass.inheritance_attribute
20
- end
21
-
22
- # Sets the value of inheritance_attribute
23
- def inheritance_attribute=(value)
24
- @inheritance_attribute = value.to_s
25
- @explicit_inheritance_attribute = true
26
- end
27
-
28
16
  def attributes_builder # :nodoc:
29
17
  unless defined?(@attributes_builder) && @attributes_builder
30
18
  defaults = _default_attributes
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "active_support/core_ext/hash/except"
4
4
  require "active_support/core_ext/module/redefine_method"
5
- require "active_support/core_ext/object/try"
6
5
  require "active_support/core_ext/hash/indifferent_access"
7
6
 
8
7
  module ActiveEntity
@@ -289,7 +288,7 @@ module ActiveEntity
289
288
  # [:allow_destroy]
290
289
  # If true, destroys any members from the attributes hash with a
291
290
  # <tt>_destroy</tt> key and a value that evaluates to +true+
292
- # (eg. 1, '1', true, or 'true'). This option is off by default.
291
+ # (e.g. 1, '1', true, or 'true'). This option is off by default.
293
292
  # [:reject_if]
294
293
  # Allows you to specify a Proc or a Symbol pointing to a method
295
294
  # that checks whether a record should be built for a certain attribute
@@ -465,10 +464,10 @@ module ActiveEntity
465
464
  if attributes_collection.is_a? Hash
466
465
  keys = attributes_collection.keys
467
466
  attributes_collection = if keys.include?("id") || keys.include?(:id)
468
- [attributes_collection]
469
- else
470
- attributes_collection.values
471
- end
467
+ [attributes_collection]
468
+ else
469
+ attributes_collection.values
470
+ end
472
471
  end
473
472
 
474
473
  association = association(association_name)
@@ -551,11 +550,5 @@ module ActiveEntity
551
550
  def allow_destroy?(association_name)
552
551
  nested_attributes_options[association_name][:allow_destroy]
553
552
  end
554
-
555
- def raise_nested_attributes_record_not_found!(association_name, record_id)
556
- model = self.class._reflect_on_association(association_name).klass.name
557
- raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
558
- model, "id", record_id)
559
- end
560
553
  end
561
554
  end
@@ -2,15 +2,75 @@
2
2
 
3
3
  require "active_entity"
4
4
  require "rails"
5
+ require "active_support/core_ext/object/try"
5
6
  require "active_model/railtie"
6
7
 
8
+ # For now, action_controller must always be present with
9
+ # Rails, so let's make sure that it gets required before
10
+ # here. This is needed for correctly setting up the middleware.
11
+ # In the future, this might become an optional require.
12
+ require "action_controller/railtie"
13
+
7
14
  module ActiveEntity
8
15
  # = Active Entity Railtie
9
16
  class Railtie < Rails::Railtie # :nodoc:
17
+ config.active_entity = ActiveSupport::OrderedOptions.new
18
+
10
19
  config.eager_load_namespaces << ActiveEntity
11
20
 
21
+ # When loading console, force ActiveEntity::Base to be loaded
22
+ # to avoid cross references when loading a constant for the
23
+ # first time. Also, make it output to STDERR.
24
+ console do |_app|
25
+ require "active_entity/base"
26
+ unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT)
27
+ console = ActiveSupport::Logger.new(STDERR)
28
+ Rails.logger.extend ActiveSupport::Logger.broadcast console
29
+ end
30
+ end
31
+
12
32
  runner do
13
- require "active_record/base"
33
+ require "active_entity/base"
34
+ end
35
+
36
+ initializer "active_entity.initialize_timezone" do
37
+ ActiveSupport.on_load(:active_entity) do
38
+ self.time_zone_aware_attributes = true
39
+ self.default_timezone = :utc
40
+ end
41
+ end
42
+
43
+ initializer "active_entity.logger" do
44
+ ActiveSupport.on_load(:active_entity) { self.logger ||= ::Rails.logger }
45
+ end
46
+
47
+
48
+ initializer "active_entity.define_attribute_methods" do |app|
49
+ config.after_initialize do
50
+ ActiveSupport.on_load(:active_entity) do
51
+ if app.config.eager_load
52
+ descendants.each do |model|
53
+ model.define_attribute_methods
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ initializer "active_entity.set_configs" do |app|
61
+ ActiveSupport.on_load(:active_entity) do
62
+ configs = app.config.active_entity
63
+
64
+ configs.each do |k, v|
65
+ send "#{k}=", v
66
+ end
67
+ end
68
+ end
69
+
70
+ initializer "active_entity.set_filter_attributes" do
71
+ ActiveSupport.on_load(:active_entity) do
72
+ self.filter_attributes += Rails.application.config.filter_parameters
73
+ end
14
74
  end
15
75
  end
16
76
  end
@@ -16,7 +16,7 @@ module ActiveEntity
16
16
  @_attr_readonly_enabled = true
17
17
  end
18
18
 
19
- def enable_attr_readonly
19
+ def without_attr_readonly
20
20
  return unless block_given?
21
21
 
22
22
  disable_attr_readonly!
@@ -31,6 +31,10 @@ module ActiveEntity
31
31
  end
32
32
  alias attr_readonly_enabled? _attr_readonly_enabled
33
33
 
34
+ def readonly_attribute?(name)
35
+ self.class.readonly_attribute?(name)
36
+ end
37
+
34
38
  module ClassMethods
35
39
  # Attributes listed as readonly will be used to create a new record but update operations will
36
40
  # ignore these fields.
@@ -42,6 +46,10 @@ module ActiveEntity
42
46
  def readonly_attributes
43
47
  _attr_readonly
44
48
  end
49
+
50
+ def readonly_attribute?(name) # :nodoc:
51
+ _attr_readonly.include?(name)
52
+ end
45
53
  end
46
54
  end
47
55
  end