massive_record 0.2.1 → 0.2.2.rc1

Sign up to get free protection for your applications and to get access to all the features.
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