activeentity 0.0.1.beta14 → 0.0.1.beta15

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.
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