massive_record 0.2.1 → 0.2.2.rc1

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 (135) hide show
  1. data/CHANGELOG.md +58 -2
  2. data/Gemfile.lock +17 -17
  3. data/README.md +98 -41
  4. data/lib/massive_record.rb +2 -1
  5. data/lib/massive_record/adapters/thrift/hbase/hbase.rb +2425 -2154
  6. data/lib/massive_record/adapters/thrift/hbase/hbase_constants.rb +3 -3
  7. data/lib/massive_record/adapters/thrift/hbase/hbase_types.rb +195 -195
  8. data/lib/massive_record/adapters/thrift/row.rb +35 -4
  9. data/lib/massive_record/adapters/thrift/table.rb +49 -12
  10. data/lib/massive_record/orm/attribute_methods.rb +77 -5
  11. data/lib/massive_record/orm/attribute_methods/cast_numbers_on_write.rb +24 -0
  12. data/lib/massive_record/orm/attribute_methods/dirty.rb +18 -0
  13. data/lib/massive_record/orm/attribute_methods/time_zone_conversion.rb +24 -3
  14. data/lib/massive_record/orm/attribute_methods/write.rb +8 -1
  15. data/lib/massive_record/orm/base.rb +62 -8
  16. data/lib/massive_record/orm/column.rb +7 -11
  17. data/lib/massive_record/orm/default_id.rb +1 -1
  18. data/lib/massive_record/orm/embedded.rb +66 -0
  19. data/lib/massive_record/orm/errors.rb +17 -0
  20. data/lib/massive_record/orm/finders.rb +124 -71
  21. data/lib/massive_record/orm/finders/rescue_missing_table_on_find.rb +1 -1
  22. data/lib/massive_record/orm/finders/scope.rb +58 -34
  23. data/lib/massive_record/orm/id_factory.rb +22 -105
  24. data/lib/massive_record/orm/id_factory/atomic_incrementation.rb +117 -0
  25. data/lib/massive_record/orm/id_factory/timestamp.rb +60 -0
  26. data/lib/massive_record/orm/identity_map.rb +256 -0
  27. data/lib/massive_record/orm/log_subscriber.rb +18 -0
  28. data/lib/massive_record/orm/observer.rb +69 -0
  29. data/lib/massive_record/orm/persistence.rb +47 -119
  30. data/lib/massive_record/orm/persistence/operations.rb +100 -0
  31. data/lib/massive_record/orm/persistence/operations/atomic_operation.rb +71 -0
  32. data/lib/massive_record/orm/persistence/operations/destroy.rb +17 -0
  33. data/lib/massive_record/orm/persistence/operations/embedded/destroy.rb +26 -0
  34. data/lib/massive_record/orm/persistence/operations/embedded/insert.rb +27 -0
  35. data/lib/massive_record/orm/persistence/operations/embedded/operation_helpers.rb +66 -0
  36. data/lib/massive_record/orm/persistence/operations/embedded/reload.rb +39 -0
  37. data/lib/massive_record/orm/persistence/operations/embedded/update.rb +29 -0
  38. data/lib/massive_record/orm/persistence/operations/insert.rb +19 -0
  39. data/lib/massive_record/orm/persistence/operations/reload.rb +26 -0
  40. data/lib/massive_record/orm/persistence/operations/suppress.rb +15 -0
  41. data/lib/massive_record/orm/persistence/operations/table_operation_helpers.rb +106 -0
  42. data/lib/massive_record/orm/persistence/operations/update.rb +25 -0
  43. data/lib/massive_record/orm/query_instrumentation.rb +26 -49
  44. data/lib/massive_record/orm/raw_data.rb +47 -0
  45. data/lib/massive_record/orm/relations.rb +4 -0
  46. data/lib/massive_record/orm/relations/interface.rb +134 -0
  47. data/lib/massive_record/orm/relations/metadata.rb +58 -12
  48. data/lib/massive_record/orm/relations/proxy.rb +17 -12
  49. data/lib/massive_record/orm/relations/proxy/embedded_in.rb +54 -0
  50. data/lib/massive_record/orm/relations/proxy/embedded_in_polymorphic.rb +15 -0
  51. data/lib/massive_record/orm/relations/proxy/embeds_many.rb +215 -0
  52. data/lib/massive_record/orm/relations/proxy/references_many.rb +112 -88
  53. data/lib/massive_record/orm/relations/proxy/references_one.rb +1 -1
  54. data/lib/massive_record/orm/relations/proxy/references_one_polymorphic.rb +1 -1
  55. data/lib/massive_record/orm/relations/proxy_collection.rb +84 -0
  56. data/lib/massive_record/orm/schema/column_family.rb +3 -2
  57. data/lib/massive_record/orm/schema/{column_interface.rb → embedded_interface.rb} +38 -4
  58. data/lib/massive_record/orm/schema/field.rb +2 -0
  59. data/lib/massive_record/orm/schema/table_interface.rb +19 -2
  60. data/lib/massive_record/orm/single_table_inheritance.rb +37 -2
  61. data/lib/massive_record/orm/timestamps.rb +17 -7
  62. data/lib/massive_record/orm/validations.rb +4 -0
  63. data/lib/massive_record/orm/validations/associated.rb +50 -0
  64. data/lib/massive_record/rails/railtie.rb +31 -0
  65. data/lib/massive_record/version.rb +1 -1
  66. data/lib/massive_record/wrapper/cell.rb +8 -1
  67. data/massive_record.gemspec +4 -4
  68. data/spec/adapter/thrift/atomic_increment_spec.rb +16 -0
  69. data/spec/adapter/thrift/table_find_spec.rb +14 -2
  70. data/spec/adapter/thrift/table_spec.rb +6 -6
  71. data/spec/adapter/thrift/utf8_encoding_of_id_spec.rb +71 -0
  72. data/spec/orm/cases/attribute_methods_spec.rb +215 -22
  73. data/spec/orm/cases/auto_generate_id_spec.rb +1 -1
  74. data/spec/orm/cases/change_id_spec.rb +62 -0
  75. data/spec/orm/cases/default_id_spec.rb +25 -6
  76. data/spec/orm/cases/default_values_spec.rb +6 -3
  77. data/spec/orm/cases/dirty_spec.rb +150 -102
  78. data/spec/orm/cases/embedded_spec.rb +250 -0
  79. data/spec/orm/cases/{finder_default_scope.rb → finder_default_scope_spec.rb} +4 -0
  80. data/spec/orm/cases/finder_scope_spec.rb +96 -29
  81. data/spec/orm/cases/finders_spec.rb +57 -10
  82. data/spec/orm/cases/id_factory/atomic_incrementation_spec.rb +72 -0
  83. data/spec/orm/cases/id_factory/timestamp_spec.rb +61 -0
  84. data/spec/orm/cases/identity_map/identity_map_spec.rb +357 -0
  85. data/spec/orm/cases/identity_map/middleware_spec.rb +74 -0
  86. data/spec/orm/cases/log_subscriber_spec.rb +15 -2
  87. data/spec/orm/cases/observing_spec.rb +61 -0
  88. data/spec/orm/cases/persistence_spec.rb +151 -60
  89. data/spec/orm/cases/raw_data_spec.rb +58 -0
  90. data/spec/orm/cases/single_table_inheritance_spec.rb +58 -2
  91. data/spec/orm/cases/table_spec.rb +3 -3
  92. data/spec/orm/cases/time_zone_awareness_spec.rb +27 -0
  93. data/spec/orm/cases/timestamps_spec.rb +23 -109
  94. data/spec/orm/cases/validation_spec.rb +9 -0
  95. data/spec/orm/models/address.rb +5 -1
  96. data/spec/orm/models/address_with_timestamp.rb +12 -0
  97. data/spec/orm/models/car.rb +5 -0
  98. data/spec/orm/models/person.rb +13 -1
  99. data/spec/orm/models/person_with_timestamp.rb +4 -2
  100. data/spec/orm/models/test_class.rb +1 -0
  101. data/spec/orm/persistence/operations/atomic_operation_spec.rb +58 -0
  102. data/spec/orm/persistence/operations/destroy_spec.rb +22 -0
  103. data/spec/orm/persistence/operations/embedded/destroy_spec.rb +71 -0
  104. data/spec/orm/persistence/operations/embedded/insert_spec.rb +59 -0
  105. data/spec/orm/persistence/operations/embedded/operation_helpers_spec.rb +92 -0
  106. data/spec/orm/persistence/operations/embedded/reload_spec.rb +67 -0
  107. data/spec/orm/persistence/operations/embedded/update_spec.rb +60 -0
  108. data/spec/orm/persistence/operations/insert_spec.rb +31 -0
  109. data/spec/orm/persistence/operations/reload_spec.rb +48 -0
  110. data/spec/orm/persistence/operations/suppress_spec.rb +17 -0
  111. data/spec/orm/persistence/operations/table_operation_helpers_spec.rb +98 -0
  112. data/spec/orm/persistence/operations/update_spec.rb +25 -0
  113. data/spec/orm/persistence/operations_spec.rb +58 -0
  114. data/spec/orm/relations/interface_spec.rb +188 -0
  115. data/spec/orm/relations/metadata_spec.rb +92 -15
  116. data/spec/orm/relations/proxy/embedded_in_polymorphic_spec.rb +37 -0
  117. data/spec/orm/relations/proxy/embedded_in_spec.rb +66 -0
  118. data/spec/orm/relations/proxy/embeds_many_spec.rb +651 -0
  119. data/spec/orm/relations/proxy/references_many_spec.rb +466 -2
  120. data/spec/orm/schema/column_family_spec.rb +21 -0
  121. data/spec/orm/schema/embedded_interface_spec.rb +181 -0
  122. data/spec/orm/schema/field_spec.rb +7 -0
  123. data/spec/orm/schema/table_interface_spec.rb +31 -1
  124. data/spec/shared/orm/id_factories.rb +44 -0
  125. data/spec/shared/orm/model_with_timestamps.rb +132 -0
  126. data/spec/shared/orm/persistence/a_persistence_embedded_operation_class.rb +3 -0
  127. data/spec/shared/orm/persistence/a_persistence_operation_class.rb +11 -0
  128. data/spec/shared/orm/persistence/a_persistence_table_operation_class.rb +11 -0
  129. data/spec/shared/orm/relations/proxy.rb +9 -2
  130. data/spec/spec_helper.rb +9 -0
  131. data/spec/support/mock_massive_record_connection.rb +2 -1
  132. metadata +106 -21
  133. data/spec/orm/cases/column_spec.rb +0 -49
  134. data/spec/orm/cases/id_factory_spec.rb +0 -92
  135. data/spec/orm/schema/column_interface_spec.rb +0 -136
@@ -20,7 +20,7 @@ module MassiveRecord
20
20
 
21
21
  private
22
22
 
23
- def find_proxy_target
23
+ def find_proxy_target(options = {})
24
24
  proxy_target_class.find(proxy_owner.send(metadata.foreign_key))
25
25
  end
26
26
 
@@ -20,7 +20,7 @@ module MassiveRecord
20
20
 
21
21
  private
22
22
 
23
- def find_proxy_target
23
+ def find_proxy_target(options = {})
24
24
  proxy_target_class.find(proxy_owner.send(metadata.foreign_key))
25
25
  end
26
26
 
@@ -0,0 +1,84 @@
1
+ module MassiveRecord
2
+ module ORM
3
+ module Relations
4
+
5
+ #
6
+ # Proxy class for relations representing a collection
7
+ #
8
+ class ProxyCollection < Proxy
9
+ #
10
+ # Loading proxy_targets will merge it with records found currently in proxy,
11
+ # to make sure we don't remove any pushed proxy_targets only cause we load the
12
+ # proxy_targets.
13
+ #
14
+ def load_proxy_target(options = {})
15
+ proxy_target_before_load = proxy_target
16
+ proxy_target_after_load = super
17
+
18
+ self.proxy_target = (proxy_target_before_load + proxy_target_after_load).uniq
19
+ end
20
+
21
+ def reset(force = false)
22
+ super
23
+ @proxy_target = []
24
+ end
25
+
26
+ def replace(*records)
27
+ records.flatten!
28
+
29
+ if records.length == 1 and records.first.nil?
30
+ reset
31
+ else
32
+ delete_all
33
+ concat(records)
34
+ end
35
+ end
36
+
37
+ def first
38
+ limit(1).first
39
+ end
40
+
41
+ def empty?
42
+ length == 0
43
+ end
44
+
45
+ #
46
+ # Destroy record(s) from the collection
47
+ # Each record will be asked to destroy itself as well
48
+ #
49
+ def destroy(*records)
50
+ delete_or_destroy *records, :destroy
51
+ end
52
+
53
+
54
+ #
55
+ # Deletes record(s) from the collection
56
+ #
57
+ def delete(*records)
58
+ delete_or_destroy *records, :delete
59
+ end
60
+
61
+ #
62
+ # Destroys all records
63
+ #
64
+ def destroy_all
65
+ destroy(load_proxy_target).tap do
66
+ reset
67
+ loaded!
68
+ end
69
+ end
70
+
71
+ #
72
+ # Deletes all records from the relationship.
73
+ # Does not destroy the records
74
+ #
75
+ def delete_all
76
+ delete(load_proxy_target).tap do
77
+ reset
78
+ loaded!
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -5,7 +5,7 @@ module MassiveRecord
5
5
  include ActiveModel::Validations
6
6
 
7
7
  attr_accessor :column_families, :autoload_fields
8
- attr_reader :name, :fields
8
+ attr_reader :name, :fields, :options_for_autoload_created_fields
9
9
 
10
10
 
11
11
  validates_presence_of :name
@@ -91,8 +91,9 @@ module MassiveRecord
91
91
  end
92
92
 
93
93
  # Internal DSL method
94
- def autoload_fields
94
+ def autoload_fields(field_args = {})
95
95
  @autoload_fields = true
96
+ @options_for_autoload_created_fields = field_args
96
97
  end
97
98
 
98
99
  def autoload
@@ -3,7 +3,7 @@ require 'massive_record/orm/schema/common_interface'
3
3
  module MassiveRecord
4
4
  module ORM
5
5
  module Schema
6
- module ColumnInterface
6
+ module EmbeddedInterface
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
@@ -17,7 +17,7 @@ module MassiveRecord
17
17
  #
18
18
  # DSL method exposed into class. Makes it possible to do:
19
19
  #
20
- # class Person < MassiveRecord::ORM::Column
20
+ # class Person < MassiveRecord::ORM::Embedded
21
21
  # field :name
22
22
  # field :age, :integer, :default => 0
23
23
  # field :points, :integer, :column => :number_of_points
@@ -52,6 +52,32 @@ module MassiveRecord
52
52
  end
53
53
 
54
54
 
55
+ #
56
+ # Returns attributes in embedded object from raw data. Raw
57
+ # data's keys might different from a field name, if :column
58
+ # option has been used.
59
+ #
60
+ def transpose_raw_data_to_record_attributes_and_raw_data(id, raw_data)
61
+ attributes = {:id => id}
62
+ attributes['updated_at'] = raw_data.created_at
63
+
64
+ raw_attributes = if raw_data.value.is_a? String
65
+ Base.coder.load(raw_data.value)
66
+ else
67
+ raw_data.value
68
+ end
69
+
70
+ raw_data = Hash[raw_attributes.collect do |attr, value|
71
+ [attr, RawData.new(value: value, created_at: raw_data.created_at)]
72
+ end]
73
+
74
+ attributes_schema.each do |attr_name, orm_field|
75
+ value = raw_attributes.has_key?(orm_field.column) ? raw_attributes[orm_field.column] : nil
76
+ attributes[attr_name] = value.nil? ? nil : orm_field.decode(raw_attributes[orm_field.column])
77
+ end
78
+
79
+ [attributes, raw_data]
80
+ end
55
81
 
56
82
 
57
83
  private
@@ -77,10 +103,13 @@ module MassiveRecord
77
103
  send(method, new_field.default) if respond_to? method
78
104
  end
79
105
 
106
+
80
107
  #
81
- # TODO : Need to be cleaned up after we implement the has_many method
108
+ # Returns attributes as a hash which has correct keys
109
+ # based on it's field definition. For instance, you can
110
+ # have a class with a field :attr_name, :column => :stored_as_this
82
111
  #
83
- def attributes_to_row_values_hash(only_attr_names = [])
112
+ def attributes_db_raw_data_hash(only_attr_names = [])
84
113
  values = Hash.new
85
114
 
86
115
  attributes_schema.each do |attr_name, orm_field|
@@ -90,6 +119,11 @@ module MassiveRecord
90
119
 
91
120
  values
92
121
  end
122
+
123
+ def attributes_to_row_values_hash(only_attr_names = [])
124
+ ActiveSupport::Deprecation.warn("attributes_to_row_values_hash is deprecated. Please use attributes_db_raw_data_hash")
125
+ attributes_db_raw_data_hash(only_attr_names)
126
+ end
93
127
  end
94
128
  end
95
129
  end
@@ -126,6 +126,8 @@ module MassiveRecord
126
126
  coder.load(value)
127
127
  end
128
128
  when :integer
129
+ value = value.force_encoding(Encoding::BINARY)
130
+
129
131
  if value =~ /\A\d*\Z/
130
132
  coder.load(value) if value.present?
131
133
  else
@@ -28,8 +28,15 @@ module MassiveRecord
28
28
  #
29
29
  #
30
30
  def column_family(name, &block)
31
+ add_column_family(name).instance_eval(&block)
32
+ end
33
+
34
+ #
35
+ # Adds a column family to your class
36
+ #
37
+ def add_column_family(name)
31
38
  ensure_column_families_exists
32
- column_families.family_by_name_or_new(name).instance_eval(&block)
39
+ column_families.family_by_name_or_new(name)
33
40
  end
34
41
 
35
42
  #
@@ -54,15 +61,25 @@ module MassiveRecord
54
61
  # It should be on a unique and complete form like ["info:name", "info:phone"]
55
62
  #
56
63
  def autoload_column_families_and_fields_with(column_names)
64
+ ensure_column_families_exists
65
+
57
66
  column_names.each do |column_family_and_column_name|
58
67
  family_name, column_name = column_family_and_column_name.split(":")
59
68
 
60
69
  if family = column_families.family_by_name(family_name) and family.autoload_fields?
61
- family.add? Field.new(:name => column_name)
70
+ family.add?(Field.new(
71
+ family.options_for_autoload_created_fields.merge(:name => column_name)
72
+ ))
62
73
  end
63
74
  end
64
75
  end
65
76
 
77
+ #
78
+ # Makes it a bit more convenient to get all the column family names
79
+ #
80
+ def known_column_family_names
81
+ (column_families || []).collect &:name
82
+ end
66
83
 
67
84
 
68
85
 
@@ -3,23 +3,54 @@ module MassiveRecord
3
3
  module SingleTableInheritance
4
4
  extend ActiveSupport::Concern
5
5
 
6
+ #
7
+ # Raised if you call first on any sub class of your base class.
8
+ # Calling first() on a sub class can't be easily done through
9
+ # thrift as we can't apply any column filter on the STI type column.
10
+ #
11
+ # What you need to do is, the very inefficient, SubClass.all.first
12
+ # When doing an all, all records will be fetched, but at least the
13
+ # array or records will be filtered and only correct class will be
14
+ # returned.
15
+ #
16
+ class FirstUnsupported < MassiveRecordError; end
17
+
18
+
6
19
  included do
7
20
  after_initialize :ensure_proper_type
8
21
  end
9
22
 
10
23
 
11
24
  module ClassMethods
25
+ def do_find(*args)
26
+ result = super
27
+ single_table_inheritance_enabled? ? ensure_only_class_or_subclass_of_self_are_returned(result) : result
28
+ end
29
+
30
+ def first(*args)
31
+ if base_class == self
32
+ super
33
+ else
34
+ raise FirstUnsupported.new("Sorry, first() on '#{self}' (sub class of the base class '#{base_class}') is unsupported due to unable to efficiently filter this through Thrift.")
35
+ end
36
+ end
12
37
 
13
38
  private
14
39
 
40
+ def ensure_only_class_or_subclass_of_self_are_returned(result)
41
+ multiple_result = result.is_a? Array
42
+ filtered_results = (multiple_result ? result : [result]).select { |result| result.kind_of? self }
43
+ multiple_result ? filtered_results : filtered_results.first
44
+ end
45
+
15
46
  #
16
47
  # In Rails development environment class files are not required before they are needed.
17
48
  #
18
- # transpose_hbase_columns_to_record_attributes uses attributes_schema and
49
+ # transpose_hbase_row_to_record_attributes_and_raw_data uses attributes_schema and
19
50
  # for attributes_schema to have loaded all of it's fields correctly we need
20
51
  # to make sure Rails loads class file before attributes_schema renders it's schema.
21
52
  #
22
- def transpose_hbase_columns_to_record_attributes(row) # :nodoc:
53
+ def transpose_hbase_row_to_record_attributes_and_raw_data(row) # :nodoc:
23
54
  if field = attributes_schema[inheritance_attribute]
24
55
  if cell_with_record_sti_class = row.columns[field.unique_name] and cell_with_record_sti_class.present?
25
56
  if klass = field.decode(cell_with_record_sti_class.value) and klass.present?
@@ -34,6 +65,10 @@ module MassiveRecord
34
65
  def ensure_sti_class_is_loaded(klass) # :nodoc:
35
66
  klass.constantize
36
67
  end
68
+
69
+ def single_table_inheritance_enabled?
70
+ !!attributes_schema[inheritance_attribute]
71
+ end
37
72
  end
38
73
 
39
74
 
@@ -9,6 +9,10 @@ module MassiveRecord
9
9
  raise "created_at must be of type time" if attributes_schema['created_at'].type != :time
10
10
  @attributes['created_at'] = Time.now
11
11
  end
12
+
13
+ before_create do
14
+ @attributes['updated_at'] = @attributes['created_at'] || Time.now
15
+ end
12
16
  end
13
17
 
14
18
 
@@ -17,10 +21,11 @@ module MassiveRecord
17
21
  module ClassMethods
18
22
  private
19
23
 
20
- def transpose_hbase_columns_to_record_attributes(row)
21
- attributes = super
22
- attributes['updated_at'] = row.updated_at
23
- attributes
24
+ def transpose_hbase_row_to_record_attributes_and_raw_data(row)
25
+ super.tap do |attributes, raw_values|
26
+ attributes['updated_at'] = row.updated_at
27
+ attributes
28
+ end
24
29
  end
25
30
  end
26
31
 
@@ -31,6 +36,10 @@ module MassiveRecord
31
36
  self.class.time_zone_aware_attributes ? self['updated_at'].try(:in_time_zone) : self['updated_at']
32
37
  end
33
38
 
39
+ def updated_at=(time)
40
+ write_attribute :updated_at, time
41
+ end
42
+
34
43
  def write_attribute(attr_name, value)
35
44
  attr_name = attr_name.to_s
36
45
 
@@ -46,9 +55,10 @@ module MassiveRecord
46
55
  private
47
56
 
48
57
  def update(*)
49
- # Not 100% accurat, as we might should re-read the saved row from
50
- # the database to fetch exactly the correct updated at time, but
51
- # it should do for now as it takes an extra query to check the time stamp.
58
+ # Sets updated at to Time.now, even though the updated at is
59
+ # read from the cell's time stamp on relad. We do this after
60
+ # a successfully update to remove the need to do a query to
61
+ # the db again to get the updated at timestamp.
52
62
  @attributes['updated_at'] = Time.now if updated = super
53
63
  updated
54
64
  end
@@ -40,6 +40,10 @@ module MassiveRecord
40
40
  perform_validation(options) ? super : raise(RecordInvalid.new(self))
41
41
  end
42
42
 
43
+ def valid?(context = nil)
44
+ context ||= (new_record? ? :create : :update)
45
+ super
46
+ end
43
47
 
44
48
  private
45
49
 
@@ -0,0 +1,50 @@
1
+ module MassiveRecord
2
+ module ORM
3
+ module Validations
4
+ class AssociatedValidator < ActiveModel::EachValidator
5
+ def validate_each(record, attribute, value)
6
+ return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.blank? || r.valid? }.all?
7
+ record.errors.add(attribute, :invalid, options.merge(:value => value))
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
13
+ #
14
+ # class Book < MassiveRecord::ORM::Table
15
+ # has_many :pages
16
+ # belongs_to :library
17
+ #
18
+ # validates_associated :pages, :library
19
+ # end
20
+ #
21
+ # Warning: If, after the above definition, you then wrote:
22
+ #
23
+ # class Page < MassiveRecord::ORM::Table
24
+ # belongs_to :book
25
+ #
26
+ # validates_associated :book
27
+ # end
28
+ #
29
+ # this would specify a circular dependency and cause infinite recursion.
30
+ #
31
+ # NOTE: This validation will not fail if the association hasn't been assigned. If you want to
32
+ # ensure that the association is both present and guaranteed to be valid, you also need to
33
+ # use +validates_presence_of+.
34
+ #
35
+ # Configuration options:
36
+ # * <tt>:message</tt> - A custom error message (default is: "is invalid")
37
+ # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
38
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
39
+ # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
40
+ # method, proc or string should return or evaluate to a true or false value.
41
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
42
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
43
+ # method, proc or string should return or evaluate to a true or false value.
44
+ def validates_associated(*attr_names)
45
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,6 +1,8 @@
1
1
  module MassiveRecord
2
2
  module Rails
3
3
  class Railtie < ::Rails::Railtie
4
+ config.massive_record = ActiveSupport::OrderedOptions.new
5
+
4
6
  initializer "massive_record.logger" do
5
7
  MassiveRecord::ORM::Base.logger = ::Rails.logger
6
8
  end
@@ -20,12 +22,41 @@ module MassiveRecord
20
22
  end
21
23
  end
22
24
 
25
+ # Insert IdentityMap's middleware if enabled
26
+ initializer "massive_record.identity_map" do |app|
27
+ if config.massive_record.delete(:identity_map)
28
+ config.app_middleware.insert_after "::ActionDispatch::Callbacks", "MassiveRecord::ORM::IdentityMap::Middleware"
29
+ end
30
+ end
31
+
23
32
  initializer "massive_record.time_zone_awareness" do
24
33
  ActiveSupport.on_load(:massive_record) do
25
34
  self.time_zone_aware_attributes = true
26
35
  self.default_timezone = :utc
27
36
  end
28
37
  end
38
+
39
+ initializer "massive_record.set_configs" do |app|
40
+ ActiveSupport.on_load(:massive_record) do
41
+ app.config.massive_record.each { |k,v| send("#{k}=", v) }
42
+ end
43
+ end
44
+
45
+ config.after_initialize do
46
+ ActiveSupport.on_load(:massive_record) do
47
+ instantiate_observers
48
+
49
+ if ::Rails::VERSION::MAJOR >= 3 && ::Rails::VERSION::MINOR >= 1
50
+ ActionDispatch::Reloader.to_prepare do
51
+ MassiveRecord::ORM::Base.instantiate_observers
52
+ end
53
+ else
54
+ ActionDispatch::Callbacks.to_prepare(:massive_record_instantiate_observers) do
55
+ MassiveRecord::ORM::Base.instantiate_observers
56
+ end
57
+ end
58
+ end
59
+ end
29
60
  end
30
61
  end
31
62
  end