hyper-model 0.6.0 → 0.99.0

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 (140) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +35 -41
  3. data/.rspec +2 -0
  4. data/.travis.yml +33 -0
  5. data/CHANGELOG.md +34 -0
  6. data/DOCS.md +735 -0
  7. data/Gemfile +7 -0
  8. data/Gemfile.lock +298 -224
  9. data/{LICENSE → LICENSE.txt} +6 -6
  10. data/README.md +51 -2
  11. data/Rakefile +18 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +7 -0
  14. data/codeship.database.yml +18 -0
  15. data/hyper-model.gemspec +62 -36
  16. data/lib/active_model_client_stubs.rb +16 -0
  17. data/lib/active_record_base.rb +331 -0
  18. data/{examples/chat-app/app/assets/images/.keep → lib/acts_as_string.rb} +0 -0
  19. data/lib/enumerable/pluck.rb +6 -0
  20. data/lib/hyper-model.rb +59 -8
  21. data/lib/hyper_model/version.rb +3 -0
  22. data/lib/hyper_react/input_tags.rb +47 -0
  23. data/lib/hyperloop/model/load.rb +1 -1
  24. data/lib/kernel/itself.rb +5 -0
  25. data/lib/object/tap.rb +7 -0
  26. data/lib/opal/equality_patches.rb +15 -0
  27. data/lib/opal/parse_patch.rb +14 -0
  28. data/lib/opal/set_patches.rb +8 -0
  29. data/lib/reactive_record/active_record/aggregations.rb +69 -0
  30. data/lib/reactive_record/active_record/associations.rb +118 -0
  31. data/lib/reactive_record/active_record/base.rb +10 -0
  32. data/lib/reactive_record/active_record/class_methods.rb +406 -0
  33. data/lib/reactive_record/active_record/error.rb +31 -0
  34. data/lib/reactive_record/active_record/errors.rb +374 -0
  35. data/lib/reactive_record/active_record/instance_methods.rb +187 -0
  36. data/lib/reactive_record/active_record/public_columns_hash.rb +44 -0
  37. data/lib/reactive_record/active_record/reactive_record/backing_record_inspector.rb +36 -0
  38. data/lib/reactive_record/active_record/reactive_record/base.rb +416 -0
  39. data/lib/reactive_record/active_record/reactive_record/collection.rb +558 -0
  40. data/lib/reactive_record/active_record/reactive_record/column_types.rb +75 -0
  41. data/lib/reactive_record/active_record/reactive_record/dummy_value.rb +236 -0
  42. data/lib/reactive_record/active_record/reactive_record/getters.rb +133 -0
  43. data/lib/reactive_record/active_record/reactive_record/isomorphic_base.rb +576 -0
  44. data/lib/reactive_record/active_record/reactive_record/lookup_tables.rb +54 -0
  45. data/lib/reactive_record/active_record/reactive_record/operations.rb +107 -0
  46. data/lib/reactive_record/active_record/reactive_record/scoped_collection.rb +62 -0
  47. data/lib/reactive_record/active_record/reactive_record/setters.rb +194 -0
  48. data/lib/reactive_record/active_record/reactive_record/unscoped_collection.rb +16 -0
  49. data/lib/reactive_record/active_record/reactive_record/while_loading.rb +343 -0
  50. data/lib/reactive_record/active_record_error.rb +4 -0
  51. data/lib/reactive_record/broadcast.rb +223 -0
  52. data/lib/reactive_record/engine.rb +11 -0
  53. data/lib/reactive_record/interval.rb +190 -0
  54. data/lib/reactive_record/permissions.rb +117 -0
  55. data/lib/reactive_record/pry.rb +13 -0
  56. data/lib/reactive_record/reactive_scope.rb +18 -0
  57. data/lib/reactive_record/scope_description.rb +121 -0
  58. data/lib/reactive_record/serializers.rb +7 -0
  59. data/lib/reactive_record/server_data_cache.rb +478 -0
  60. data/path_release_steps.md +9 -0
  61. metadata +399 -109
  62. data/CODE_OF_CONDUCT.md +0 -49
  63. data/examples/chat-app/.gitignore +0 -21
  64. data/examples/chat-app/Gemfile +0 -62
  65. data/examples/chat-app/Gemfile.lock +0 -309
  66. data/examples/chat-app/README.md +0 -3
  67. data/examples/chat-app/Rakefile +0 -6
  68. data/examples/chat-app/app/assets/config/manifest.js +0 -3
  69. data/examples/chat-app/app/assets/javascripts/application.js +0 -3
  70. data/examples/chat-app/app/assets/stylesheets/application.scss +0 -33
  71. data/examples/chat-app/app/controllers/application_controller.rb +0 -3
  72. data/examples/chat-app/app/controllers/home_controller.rb +0 -5
  73. data/examples/chat-app/app/hyperloop/components/app.rb +0 -12
  74. data/examples/chat-app/app/hyperloop/components/formatted_div.rb +0 -15
  75. data/examples/chat-app/app/hyperloop/components/input_box.rb +0 -26
  76. data/examples/chat-app/app/hyperloop/components/message.rb +0 -29
  77. data/examples/chat-app/app/hyperloop/components/messages.rb +0 -8
  78. data/examples/chat-app/app/hyperloop/components/nav.rb +0 -30
  79. data/examples/chat-app/app/hyperloop/models/application_record.rb +0 -3
  80. data/examples/chat-app/app/hyperloop/models/message.rb +0 -6
  81. data/examples/chat-app/app/hyperloop/operations/operations.rb +0 -13
  82. data/examples/chat-app/app/hyperloop/stores/message_store.rb +0 -17
  83. data/examples/chat-app/app/policies/application_policy.rb +0 -9
  84. data/examples/chat-app/app/views/layouts/application.html.erb +0 -12
  85. data/examples/chat-app/bin/bundle +0 -3
  86. data/examples/chat-app/bin/rails +0 -9
  87. data/examples/chat-app/bin/rake +0 -9
  88. data/examples/chat-app/bin/setup +0 -34
  89. data/examples/chat-app/bin/spring +0 -17
  90. data/examples/chat-app/bin/update +0 -29
  91. data/examples/chat-app/config.ru +0 -5
  92. data/examples/chat-app/config/application.rb +0 -12
  93. data/examples/chat-app/config/boot.rb +0 -3
  94. data/examples/chat-app/config/cable.yml +0 -9
  95. data/examples/chat-app/config/database.yml +0 -25
  96. data/examples/chat-app/config/environment.rb +0 -5
  97. data/examples/chat-app/config/environments/development.rb +0 -56
  98. data/examples/chat-app/config/environments/production.rb +0 -86
  99. data/examples/chat-app/config/environments/test.rb +0 -42
  100. data/examples/chat-app/config/initializers/application_controller_renderer.rb +0 -6
  101. data/examples/chat-app/config/initializers/assets.rb +0 -11
  102. data/examples/chat-app/config/initializers/backtrace_silencers.rb +0 -7
  103. data/examples/chat-app/config/initializers/cookies_serializer.rb +0 -5
  104. data/examples/chat-app/config/initializers/filter_parameter_logging.rb +0 -4
  105. data/examples/chat-app/config/initializers/hyperloop.rb +0 -6
  106. data/examples/chat-app/config/initializers/inflections.rb +0 -16
  107. data/examples/chat-app/config/initializers/mime_types.rb +0 -4
  108. data/examples/chat-app/config/initializers/new_framework_defaults.rb +0 -24
  109. data/examples/chat-app/config/initializers/session_store.rb +0 -3
  110. data/examples/chat-app/config/initializers/wrap_parameters.rb +0 -14
  111. data/examples/chat-app/config/locales/en.yml +0 -23
  112. data/examples/chat-app/config/puma.rb +0 -47
  113. data/examples/chat-app/config/routes.rb +0 -5
  114. data/examples/chat-app/config/secrets.yml +0 -22
  115. data/examples/chat-app/config/spring.rb +0 -6
  116. data/examples/chat-app/db/migrate/20170319194429_create_message.rb +0 -9
  117. data/examples/chat-app/db/schema.rb +0 -48
  118. data/examples/chat-app/db/seeds.rb +0 -7
  119. data/examples/chat-app/lib/assets/.keep +0 -0
  120. data/examples/chat-app/lib/tasks/.keep +0 -0
  121. data/examples/chat-app/log/.keep +0 -0
  122. data/examples/chat-app/public/404.html +0 -67
  123. data/examples/chat-app/public/422.html +0 -67
  124. data/examples/chat-app/public/500.html +0 -66
  125. data/examples/chat-app/public/apple-touch-icon-precomposed.png +0 -0
  126. data/examples/chat-app/public/apple-touch-icon.png +0 -0
  127. data/examples/chat-app/public/favicon.ico +0 -0
  128. data/examples/chat-app/public/robots.txt +0 -5
  129. data/examples/chat-app/test/controllers/.keep +0 -0
  130. data/examples/chat-app/test/fixtures/.keep +0 -0
  131. data/examples/chat-app/test/fixtures/files/.keep +0 -0
  132. data/examples/chat-app/test/helpers/.keep +0 -0
  133. data/examples/chat-app/test/integration/.keep +0 -0
  134. data/examples/chat-app/test/mailers/.keep +0 -0
  135. data/examples/chat-app/test/models/.keep +0 -0
  136. data/examples/chat-app/test/test_helper.rb +0 -10
  137. data/examples/chat-app/tmp/.keep +0 -0
  138. data/examples/chat-app/vendor/assets/javascripts/.keep +0 -0
  139. data/examples/chat-app/vendor/assets/stylesheets/.keep +0 -0
  140. data/lib/hyperloop/model/version.rb +0 -5
@@ -0,0 +1,31 @@
1
+ module ActiveModel
2
+ class Error
3
+
4
+ attr_reader :messages
5
+
6
+ def initialize(initial_msgs = {})
7
+ @messages = Hash.new { |h, k| h[k] = [] }
8
+ initial_msgs.each { |attr, msgs| @messages[attr] = msgs.uniq }
9
+ end
10
+
11
+ def [](attribute)
12
+ messages[attribute]
13
+ end
14
+
15
+ def delete(attribute)
16
+ messages.delete(attribute)
17
+ end
18
+
19
+ def empty?
20
+ messages.empty?
21
+ end
22
+
23
+ def clear
24
+ @messages.clear
25
+ end
26
+
27
+ def add(attribute, message = :invalid, _options = {})
28
+ @messages[attribute] << message unless @messages[attribute].include? message
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,374 @@
1
+ require 'active_support/core_ext/string/inflections'
2
+
3
+ module ActiveModel
4
+ class Errors
5
+ include Enumerable
6
+
7
+ CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
8
+ MESSAGE_OPTIONS = [:message]
9
+
10
+ attr_reader :messages, :details
11
+
12
+ # Pass in the instance of the object that is using the errors object.
13
+ #
14
+ # class Person
15
+ # def initialize
16
+ # @errors = ActiveModel::Errors.new(self)
17
+ # end
18
+ # end
19
+ def initialize(base = {})
20
+ @base = base
21
+ @messages = apply_default_array({})
22
+ @details = apply_default_array({})
23
+ reactive_empty! true
24
+ end
25
+
26
+ # When passed a symbol or a name of a method, returns an array of errors
27
+ # for the method.
28
+ #
29
+ # person.errors[:name] # => ["cannot be nil"]
30
+ # person.errors['name'] # => ["cannot be nil"]
31
+ def [](attribute)
32
+ messages[attribute]
33
+ end
34
+
35
+ # Iterates through each error key, value pair in the error messages hash.
36
+ # Yields the attribute and the error for that attribute. If the attribute
37
+ # has more than one error message, yields once for each error message.
38
+ #
39
+ # person.errors.add(:name, :blank, message: "can't be blank")
40
+ # person.errors.each do |attribute, error|
41
+ # # Will yield :name and "can't be blank"
42
+ # end
43
+ #
44
+ # person.errors.add(:name, :not_specified, message: "must be specified")
45
+ # person.errors.each do |attribute, error|
46
+ # # Will yield :name and "can't be blank"
47
+ # # then yield :name and "must be specified"
48
+ # end
49
+ def each
50
+ messages.each_key do |attribute|
51
+ messages[attribute].each { |error| yield attribute, error }
52
+ end
53
+ end
54
+
55
+ # Delete messages for +key+. Returns the deleted messages.
56
+ #
57
+ # person.errors[:name] # => ["cannot be nil"]
58
+ # person.errors.delete(:name) # => ["cannot be nil"]
59
+ # person.errors[:name] # => []
60
+ def delete(attribute)
61
+ details.delete(attribute)
62
+ messages.delete(attribute)
63
+ end
64
+
65
+ # Returns the number of error messages.
66
+ #
67
+ # person.errors.add(:name, :blank, message: "can't be blank")
68
+ # person.errors.size # => 1
69
+ # person.errors.add(:name, :not_specified, message: "must be specified")
70
+ # person.errors.size # => 2
71
+ def size
72
+ values.flatten.size
73
+ end
74
+ alias :count :size
75
+
76
+ # Returns all message keys.
77
+ #
78
+ # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
79
+ # person.errors.keys # => [:name]
80
+ def keys
81
+ messages.select do |key, value|
82
+ !value.empty?
83
+ end.keys
84
+ end
85
+
86
+ # Returns all message values.
87
+ #
88
+ # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
89
+ # person.errors.values # => [["cannot be nil", "must be specified"]]
90
+ def values
91
+ messages.select do |key, value|
92
+ !value.empty?
93
+ end.values
94
+ end
95
+
96
+ # NOTE: Doesn't use i18n
97
+ #
98
+ # Returns a full message for a given attribute.
99
+ #
100
+ # person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
101
+ def full_message(attribute, message)
102
+ return message if attribute == :base
103
+ # TODO: When opal_activesupport 0.3.2 is released, use `humanize`
104
+ # attr_name = attribute.to_s.tr('.', '_').humanize
105
+ attr_name =
106
+ attribute.to_s.tr('.', '_').tr('_', ' ').gsub(/_id$/, '').capitalize
107
+ # attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
108
+ # if @base.class.respond_to?(:human_attribute_name)
109
+ attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
110
+ # end
111
+ # I18n.t(:"errors.format",
112
+ # default: "%{attribute} %{message}",
113
+ # attribute: attr_name,
114
+ # message: message)
115
+ "#{attr_name} #{message}"
116
+ end
117
+
118
+ # Returns all the full error messages in an array.
119
+ #
120
+ # class Person
121
+ # validates_presence_of :name, :address, :email
122
+ # validates_length_of :name, in: 5..30
123
+ # end
124
+ #
125
+ # person = Person.create(address: '123 First St.')
126
+ # person.errors.full_messages
127
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
128
+ def full_messages
129
+ map { |attribute, message| full_message(attribute, message) }
130
+ end
131
+ alias :to_a :full_messages
132
+
133
+ # Returns all the full error messages for a given attribute in an array.
134
+ #
135
+ # class Person
136
+ # validates_presence_of :name, :email
137
+ # validates_length_of :name, in: 5..30
138
+ # end
139
+ #
140
+ # person = Person.create()
141
+ # person.errors.full_messages_for(:name)
142
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
143
+ def full_messages_for(attribute)
144
+ messages[attribute].map { |message| full_message(attribute, message) }
145
+ end
146
+
147
+ # Returns a Hash of attributes with their error messages. If +full_messages+
148
+ # is +true+, it will contain full messages (see +full_message+).
149
+ #
150
+ # person.errors.to_hash # => {:name=>["cannot be nil"]}
151
+ # person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
152
+ def to_hash(full_messages = false)
153
+ if full_messages
154
+ messages.each_with_object({}) do |(attribute, array), messages|
155
+ messages[attribute] = array.map { |message| full_message(attribute, message) }
156
+ end
157
+ else
158
+ without_default_proc(messages)
159
+ end
160
+ end
161
+
162
+ # Returns a Hash that can be used as the JSON representation for this
163
+ # object. You can pass the <tt>:full_messages</tt> option. This determines
164
+ # if the json object should contain full messages or not (false by default).
165
+ #
166
+ # person.errors.as_json # => {:name=>["cannot be nil"]}
167
+ # person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
168
+ def as_json(options = nil)
169
+ to_hash(options && options[:full_messages])
170
+ end
171
+
172
+ # NOTE: Doesn't actually do any of the below i18n lookups
173
+ #
174
+ # Translates an error message in its default scope
175
+ # (<tt>activemodel.errors.messages</tt>).
176
+ #
177
+ # Error messages are first looked up in <tt>activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
178
+ # if it's not there, it's looked up in <tt>activemodel.errors.models.MODEL.MESSAGE</tt> and if
179
+ # that is not there also, it returns the translation of the default message
180
+ # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
181
+ # name, translated attribute name and the value are available for
182
+ # interpolation.
183
+ #
184
+ # When using inheritance in your models, it will check all the inherited
185
+ # models too, but only if the model itself hasn't been found. Say you have
186
+ # <tt>class Admin < User; end</tt> and you wanted the translation for
187
+ # the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
188
+ # it looks for these translations:
189
+ #
190
+ # * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
191
+ # * <tt>activemodel.errors.models.admin.blank</tt>
192
+ # * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
193
+ # * <tt>activemodel.errors.models.user.blank</tt>
194
+ # * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
195
+ # * <tt>activemodel.errors.messages.blank</tt>
196
+ # * <tt>errors.attributes.title.blank</tt>
197
+ # * <tt>errors.messages.blank</tt>
198
+ def generate_message(attribute, type = :invalid, options = {})
199
+ options.delete(:message) || type
200
+ end
201
+
202
+ # Returns +true+ if no errors are found, +false+ otherwise.
203
+ # If the error message is a string it can be empty.
204
+ #
205
+ # person.errors.full_messages # => ["name cannot be nil"]
206
+ # person.errors.empty? # => false
207
+ def empty?
208
+ size.zero?
209
+ end
210
+ alias :blank? :empty?
211
+
212
+ def reactive_empty?
213
+ React::State.get_state(self, 'ERRORS?')
214
+ end
215
+
216
+ # Clear the error messages.
217
+ #
218
+ # person.errors.full_messages # => ["name cannot be nil"]
219
+ # person.errors.clear
220
+ # person.errors.full_messages # => []
221
+ def clear
222
+ messages.clear
223
+ details.clear.tap { reactive_empty! true }
224
+ end
225
+
226
+ # Merges the errors from <tt>other</tt>.
227
+ #
228
+ # other - The ActiveModel::Errors instance.
229
+ #
230
+ # Examples
231
+ #
232
+ # person.errors.merge!(other)
233
+ def merge!(other)
234
+ @messages.merge!(other.messages) { |_, ary1, ary2| ary1 + ary2 }
235
+ @details.merge!(other.details) { |_, ary1, ary2| ary1 + ary2 }.tap { reactive_empty! }
236
+ end
237
+
238
+ # Returns +true+ if the error messages include an error for the given key
239
+ # +attribute+, +false+ otherwise.
240
+ #
241
+ # person.errors.messages # => {:name=>["cannot be nil"]}
242
+ # person.errors.include?(:name) # => true
243
+ # person.errors.include?(:age) # => false
244
+ def include?(attribute)
245
+ attribute = attribute.to_sym
246
+ messages.key?(attribute) && messages[attribute].present?
247
+ end
248
+ alias :has_key? :include?
249
+ alias :key? :include?
250
+
251
+ # NOTE: strict option isn't ported yet
252
+ #
253
+ # Adds +message+ to the error messages and used validator type to +details+ on +attribute+.
254
+ # More than one error can be added to the same +attribute+.
255
+ # If no +message+ is supplied, <tt>:invalid</tt> is assumed.
256
+ #
257
+ # person.errors.add(:name)
258
+ # # => ["is invalid"]
259
+ # person.errors.add(:name, :not_implemented, message: "must be implemented")
260
+ # # => ["is invalid", "must be implemented"]
261
+ #
262
+ # person.errors.messages
263
+ # # => {:name=>["is invalid", "must be implemented"]}
264
+ #
265
+ # person.errors.details
266
+ # # => {:name=>[{error: :not_implemented}, {error: :invalid}]}
267
+ #
268
+ # If +message+ is a symbol, it will be translated using the appropriate
269
+ # scope (see +generate_message+).
270
+ #
271
+ # If +message+ is a proc, it will be called, allowing for things like
272
+ # <tt>Time.now</tt> to be used within an error.
273
+ #
274
+ # If the <tt>:strict</tt> option is set to +true+, it will raise
275
+ # ActiveModel::StrictValidationFailed instead of adding the error.
276
+ # <tt>:strict</tt> option can also be set to any other exception.
277
+ #
278
+ # person.errors.add(:name, :invalid, strict: true)
279
+ # # => ActiveModel::StrictValidationFailed: Name is invalid
280
+ # person.errors.add(:name, :invalid, strict: NameIsInvalid)
281
+ # # => NameIsInvalid: Name is invalid
282
+ #
283
+ # person.errors.messages # => {}
284
+ #
285
+ # +attribute+ should be set to <tt>:base</tt> if the error is not
286
+ # directly associated with a single attribute.
287
+ #
288
+ # person.errors.add(:base, :name_or_email_blank,
289
+ # message: "either name or email must be present")
290
+ # person.errors.messages
291
+ # # => {:base=>["either name or email must be present"]}
292
+ # person.errors.details
293
+ # # => {:base=>[{error: :name_or_email_blank}]}
294
+ def add(attribute, message = :invalid, options = {})
295
+ message = message.call if message.respond_to?(:call)
296
+ detail = normalize_detail(message, options)
297
+ message = normalize_message(attribute, message, options)
298
+ # if exception = options[:strict]
299
+ # exception = ActiveModel::StrictValidationFailed if exception == true
300
+ # raise exception, full_message(attribute, message)
301
+ # end
302
+ details[attribute.to_sym] << detail
303
+ (messages[attribute.to_sym] << message).tap { reactive_empty! false }
304
+ end
305
+
306
+ # NOTE: Due to Opal not supporting Symbol this isn't identical,
307
+ # but probably still works fine in most cases.
308
+ #
309
+ # Returns +true+ if an error on the attribute with the given message is
310
+ # present, or +false+ otherwise. +message+ is treated the same as for +add+.
311
+ #
312
+ # person.errors.add :name, :blank
313
+ # person.errors.added? :name, :blank # => true
314
+ # person.errors.added? :name, "can't be blank" # => true
315
+ #
316
+ # If the error message requires an option, then it returns +true+ with
317
+ # the correct option, or +false+ with an incorrect or missing option.
318
+ #
319
+ # person.errors.add :name, :too_long, { count: 25 }
320
+ # person.errors.added? :name, :too_long, count: 25 # => true
321
+ # person.errors.added? :name, "is too long (maximum is 25 characters)" # => true
322
+ # person.errors.added? :name, :too_long, count: 24 # => false
323
+ # person.errors.added? :name, :too_long # => false
324
+ # person.errors.added? :name, "is too long" # => false
325
+ def added?(attribute, message = :invalid, options = {})
326
+ # if message.is_a? Symbol
327
+ # self.details[attribute].map { |e| e[:error] }.include? message
328
+ # else
329
+ # message = message.call if message.respond_to?(:call)
330
+ # message = normalize_message(attribute, message, options)
331
+ # self[attribute].include? message
332
+ # end
333
+ return true if details[attribute].map { |e| e[:error] }.include? message
334
+ message = message.call if message.respond_to?(:call)
335
+ message = normalize_message(attribute, message, options)
336
+ self[attribute].include? message
337
+ end
338
+
339
+ private
340
+
341
+ def apply_default_array(hash)
342
+ hash.default_proc = proc { |h, key| h[key] = [] }
343
+ hash
344
+ end
345
+
346
+ def normalize_message(attribute, message, options)
347
+ # case message
348
+ # when Symbol
349
+ # generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
350
+ # else
351
+ # message
352
+ # end
353
+ generate_message(
354
+ attribute, message, options.reject { |k, _| CALLBACKS_OPTIONS.include?(k) }
355
+ )
356
+ end
357
+
358
+ def normalize_detail(message, options)
359
+ # { error: message }.merge(options.except(*CALLBACKS_OPTIONS + MESSAGE_OPTIONS))
360
+ ignore = (CALLBACKS_OPTIONS + MESSAGE_OPTIONS)
361
+ { error: message }.merge(options.reject { |k, _| ignore.include?(k) })
362
+ end
363
+
364
+ def without_default_proc(hash)
365
+ hash.dup.tap do |new_h|
366
+ new_h.default_proc = nil
367
+ end
368
+ end
369
+
370
+ def reactive_empty!(state = empty?)
371
+ React::State.set_state(self, 'ERRORS?', state) unless ReactiveRecord::Base.data_loading?
372
+ end
373
+ end
374
+ end
@@ -0,0 +1,187 @@
1
+ module ActiveRecord
2
+ module InstanceMethods
3
+ def inspect
4
+ "<#{model_name}:#{ReactiveRecord::Operations::Base::FORMAT % to_key} "\
5
+ "(#{ReactiveRecord::Operations::Base::FORMAT % object_id}) "\
6
+ "#{backing_record.inspection_details} >"
7
+ end
8
+
9
+ attr_reader :backing_record
10
+
11
+ def attributes
12
+ @backing_record.attributes
13
+ end
14
+
15
+ def changed_attributes
16
+ backing_record.changed_attributes_and_values
17
+ end
18
+
19
+ def changes
20
+ backing_record.changes
21
+ end
22
+
23
+ def initialize(hash = {})
24
+ if hash.is_a? ReactiveRecord::Base
25
+ @backing_record = hash
26
+ else
27
+ # standard active_record new -> creates a new instance, primary key is ignored if present
28
+ # we have to build the backing record first then initialize it so associations work correctly
29
+ @backing_record = ReactiveRecord::Base.new(self.class, {}, self)
30
+ if self.class.inheritance_column && !hash.key?(self.class.inheritance_column)
31
+ hash[self.class.inheritance_column] = self.class.name
32
+ end
33
+ @backing_record.instance_eval do
34
+ h = {}
35
+ hash.each do |a, v|
36
+ a = model._dealias_attribute(a)
37
+ h[a] = convert(a, v).itself
38
+ end
39
+ self.class.load_data do
40
+ h.each do |attribute, value|
41
+ next if attribute == primary_key
42
+ @ar_instance[attribute] = value
43
+ changed_attributes << attribute
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def primary_key
51
+ self.class.primary_key
52
+ end
53
+
54
+ def id
55
+ @backing_record.get_primary_key_value
56
+ end
57
+
58
+ def id=(value)
59
+ @backing_record.id = value
60
+ end
61
+
62
+ def id?
63
+ id.present?
64
+ end
65
+
66
+ def model_name
67
+ # in reality should return ActiveModel::Name object, blah blah
68
+ self.class.model_name
69
+ end
70
+
71
+ def revert
72
+ @backing_record.revert
73
+ end
74
+
75
+ def changed?
76
+ @backing_record.changed?
77
+ end
78
+
79
+ def dup
80
+ self.class.new(self.attributes)
81
+ end
82
+
83
+ def ==(ar_instance)
84
+ @backing_record == ar_instance.instance_eval { @backing_record }
85
+ end
86
+
87
+ def [](attr)
88
+ send(attr)
89
+ end
90
+
91
+ def []=(attr, val)
92
+ send("#{attr}=", val)
93
+ end
94
+
95
+ def itself
96
+ # this is useful when you just want to get a handle on record instance
97
+ # in the ReactiveRecord.load method
98
+ id # force load of id...
99
+ # if self.class.columns_hash.keys.include?(self.class.inheritance_column) &&
100
+ # (klass = self[self.class.inheritance_column]).loaded?
101
+ # Object.const_get(klass).new(attributes)
102
+ # else
103
+ self
104
+ # end
105
+ end
106
+
107
+ def load(*attributes, &block)
108
+ first_time = true
109
+ ReactiveRecord.load do
110
+ results = attributes.collect { |attr| send("#{attr}#{'!' if first_time}") }
111
+ results = yield(*results) if block
112
+ first_time = false
113
+ block.nil? && results.count == 1 ? results.first : results
114
+ end
115
+ end
116
+
117
+ def save(opts = {}, &block)
118
+ @backing_record.save_or_validate(true, opts.has_key?(:validate) ? opts[:validate] : true, opts[:force], &block)
119
+ end
120
+
121
+ def validate(opts = {}, &block)
122
+ @backing_record.save_or_validate(false, true, opts[:force]).then do
123
+ if block
124
+ yield @backing_record.ar_instance
125
+ else
126
+ @backing_record.ar_instance
127
+ end
128
+ end
129
+ end
130
+
131
+ def valid?
132
+ errors.reactive_empty?
133
+ end
134
+
135
+ def saving?
136
+ @backing_record.saving?
137
+ end
138
+
139
+ def destroy(&block)
140
+ @backing_record.destroy(&block)
141
+ end
142
+
143
+ def destroyed?
144
+ @backing_record.destroyed
145
+ end
146
+
147
+ def new?
148
+ @backing_record.new?
149
+ end
150
+
151
+ def errors
152
+ React::State.get_state(@backing_record, @backing_record)
153
+ @backing_record.errors
154
+ end
155
+
156
+ def to_key
157
+ @backing_record.object_id
158
+ end
159
+
160
+ def update_attribute(attr, value, &block)
161
+ send("#{attr}=", value)
162
+ save(validate: false, &block)
163
+ end
164
+
165
+ def update(attrs = {}, &block)
166
+ attrs.each { |attr, value| send("#{attr}=", value) }
167
+ save(&block)
168
+ end
169
+
170
+ def <=>(other)
171
+ id.to_i <=> other.id.to_i
172
+ end
173
+
174
+ def becomes(klass)
175
+ klass._new_without_sti_type_cast(backing_record)
176
+ end
177
+
178
+ def becomes!(klass)
179
+ self[self.class.inheritance_column] = klass.name
180
+ becomes(klass)
181
+ end
182
+
183
+ def cast_to_current_sti_type
184
+ @backing_record.set_ar_instance!
185
+ end
186
+ end
187
+ end