baza_models 0.0.0 → 0.0.1

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +35 -0
  3. data/Gemfile +9 -3
  4. data/Gemfile.lock +90 -51
  5. data/README.md +98 -3
  6. data/Rakefile +20 -17
  7. data/VERSION +1 -1
  8. data/baza_models.gemspec +149 -0
  9. data/config/best_project_practice_rubocop.yml +2 -0
  10. data/config/best_project_practice_rubocop_todo.yml +35 -0
  11. data/lib/baza_models.rb +15 -0
  12. data/lib/baza_models/autoloader.rb +135 -0
  13. data/lib/baza_models/baza_orm_adapter.rb +39 -0
  14. data/lib/baza_models/can_can_adapter.rb +9 -0
  15. data/lib/baza_models/class_translation.rb +44 -0
  16. data/lib/baza_models/errors.rb +24 -2
  17. data/lib/baza_models/model.rb +236 -115
  18. data/lib/baza_models/model/belongs_to_relations.rb +49 -0
  19. data/lib/baza_models/model/custom_validations.rb +24 -0
  20. data/lib/baza_models/model/delegation.rb +21 -0
  21. data/lib/baza_models/model/has_many_relations.rb +85 -0
  22. data/lib/baza_models/model/has_one_relations.rb +93 -0
  23. data/lib/baza_models/model/manipulation.rb +123 -0
  24. data/lib/baza_models/model/queries.rb +45 -0
  25. data/lib/baza_models/model/scopes.rb +19 -0
  26. data/lib/baza_models/model/translation_functionality.rb +30 -0
  27. data/lib/baza_models/model/validations.rb +95 -0
  28. data/lib/baza_models/query.rb +447 -0
  29. data/lib/baza_models/query/inspector.rb +74 -0
  30. data/lib/baza_models/query/not.rb +34 -0
  31. data/lib/baza_models/ransacker.rb +30 -0
  32. data/lib/baza_models/test_database_cleaner.rb +23 -0
  33. data/lib/baza_models/validators/base_validator.rb +14 -0
  34. data/lib/baza_models/validators/confirmation_validator.rb +12 -0
  35. data/lib/baza_models/validators/format_validator.rb +11 -0
  36. data/lib/baza_models/validators/length_validator.rb +16 -0
  37. data/lib/baza_models/validators/uniqueness_validator.rb +21 -0
  38. data/shippable.yml +3 -1
  39. data/spec/baza_models/autoloader_spec.rb +57 -0
  40. data/spec/baza_models/baza_orm_adapter_spec.rb +52 -0
  41. data/spec/baza_models/class_translation_spec.rb +25 -0
  42. data/spec/baza_models/factory_girl_spec.rb +13 -0
  43. data/spec/baza_models/model/belongs_to_relations_spec.rb +26 -0
  44. data/spec/baza_models/model/custom_validations_spec.rb +18 -0
  45. data/spec/baza_models/model/delgation_spec.rb +16 -0
  46. data/spec/baza_models/model/has_many_relations_spec.rb +68 -0
  47. data/spec/baza_models/model/has_one_relations_spec.rb +35 -0
  48. data/spec/baza_models/model/manipulation_spec.rb +25 -0
  49. data/spec/baza_models/model/queries_spec.rb +59 -0
  50. data/spec/baza_models/model/scopes_spec.rb +23 -0
  51. data/spec/baza_models/model/translate_functionality_spec.rb +13 -0
  52. data/spec/baza_models/model/validations_spec.rb +52 -0
  53. data/spec/baza_models/model_spec.rb +75 -98
  54. data/spec/baza_models/query/not_spec.rb +16 -0
  55. data/spec/baza_models/query_spec.rb +155 -0
  56. data/spec/baza_models/ransacker_spec.rb +15 -0
  57. data/spec/baza_models/validators/confirmation_validator_spec.rb +28 -0
  58. data/spec/baza_models/validators/format_validator_spec.rb +17 -0
  59. data/spec/baza_models/validators/length_validator_spec.rb +19 -0
  60. data/spec/baza_models/validators/uniqueness_validator_spec.rb +24 -0
  61. data/spec/factories/organization.rb +5 -0
  62. data/spec/factories/user.rb +7 -0
  63. data/spec/spec_helper.rb +17 -5
  64. data/spec/support/database_helper.rb +87 -0
  65. data/spec/test_classes/organization.rb +3 -0
  66. data/spec/test_classes/person.rb +3 -0
  67. data/spec/test_classes/role.rb +12 -0
  68. data/spec/test_classes/user.rb +40 -0
  69. data/spec/test_classes/user_passport.rb +3 -0
  70. metadata +146 -7
  71. data/spec/test_classes/user_test.rb +0 -17
@@ -1,5 +1,6 @@
1
1
  class BazaModels::Errors
2
2
  class InvalidRecord < RuntimeError; end
3
+ class RecordNotFound < RuntimeError; end
3
4
 
4
5
  def initialize
5
6
  @errors = {}
@@ -14,10 +15,19 @@ class BazaModels::Errors
14
15
  messages = []
15
16
 
16
17
  @errors.each do |attribute_name, errors|
17
- messages += errors
18
+ errors.each do |error|
19
+ message = ""
20
+
21
+ unless attribute_name == :base
22
+ message << "#{StringCases.snake_to_camel(attribute_name)} "
23
+ end
24
+
25
+ message << error
26
+ messages << message
27
+ end
18
28
  end
19
29
 
20
- return messages
30
+ messages
21
31
  end
22
32
 
23
33
  def empty?
@@ -27,4 +37,16 @@ class BazaModels::Errors
27
37
  def any?
28
38
  full_messages.any?
29
39
  end
40
+
41
+ def to_s
42
+ "#<BazaModels::Errors full_messages=\"#{full_messages}\">"
43
+ end
44
+
45
+ def inspect
46
+ to_s
47
+ end
48
+
49
+ def [](key)
50
+ @errors[key] || []
51
+ end
30
52
  end
@@ -1,21 +1,51 @@
1
1
  require "string-cases"
2
2
 
3
3
  class BazaModels::Model
4
- attr_accessor :db
4
+ path = "#{File.dirname(__FILE__)}/model"
5
+
6
+ autoload :BelongsToRelations, "#{path}/belongs_to_relations"
7
+ autoload :CustomValidations, "#{path}/custom_validations"
8
+ autoload :Delegation, "#{path}/delegation"
9
+ autoload :HasManyRelations, "#{path}/has_many_relations"
10
+ autoload :HasOneRelations, "#{path}/has_one_relations"
11
+ autoload :Manipulation, "#{path}/manipulation"
12
+ autoload :Queries, "#{path}/queries"
13
+ autoload :Scopes, "#{path}/scopes"
14
+ autoload :TranslationFunctionality, "#{path}/translation_functionality"
15
+ autoload :Validations, "#{path}/validations"
16
+
17
+ include BelongsToRelations
18
+ include Delegation
19
+ include CustomValidations
20
+ include HasManyRelations
21
+ include HasOneRelations
22
+ include Manipulation
23
+ include Queries
24
+ include Scopes
25
+ include TranslationFunctionality
26
+ include Validations
27
+
28
+ attr_accessor :data, :db
5
29
  attr_reader :changes, :errors
6
30
 
7
31
  # Define all callback methods.
8
- CALLBACK_TYPES = [:before_create, :after_create, :before_save, :after_save, :before_destroy, :after_destroy,
32
+ CALLBACK_TYPES = [
33
+ :before_create, :after_create, :before_save, :after_save, :before_destroy, :after_destroy,
9
34
  :before_validation, :after_validation, :before_validation_on_create, :after_validation_on_create,
10
- :before_validation_on_update, :after_validation_on_update]
35
+ :before_validation_on_update, :after_validation_on_update
36
+ ]
11
37
 
12
38
  CALLBACK_TYPES.each do |callback_type|
39
+ # rubocop:disable Style/ClassVars
13
40
  @@callbacks ||= {}
14
- @@callbacks[callback_type] = []
41
+ # rubocop:enable Style/ClassVars
42
+
43
+ @@callbacks[callback_type] = {}
15
44
  callbacks = @@callbacks
16
45
 
17
- (class << self; self; end).send(:define_method, callback_type) do |method_name, *args, &blk|
18
- callbacks[callback_type] << {
46
+ (class << self; self; end).__send__(:define_method, callback_type) do |method_name = nil, *args, &blk|
47
+ callbacks[callback_type][name] ||= []
48
+ callbacks[callback_type][name] << {
19
49
  block: blk,
20
50
  method_name: method_name,
21
51
  args: args
@@ -23,13 +53,29 @@ class BazaModels::Model
23
53
  end
24
54
  end
25
55
 
26
- def initialize(data = {})
56
+
57
+ QUERY_METHODS = [
58
+ :all, :any?, :empty?, :none?, :count, :first, :find_first, :last, :length, :select, :includes,
59
+ :joins, :group, :where, :order, :limit, :to_a, :accessible_by
60
+ ]
61
+ QUERY_METHODS.each do |query_method|
62
+ (class << self; self; end).__send__(:define_method, query_method) do |*args, &blk|
63
+ BazaModels::Query.new(model: self).__send__(query_method, *args, &blk)
64
+ end
65
+ end
66
+
67
+ def initialize(data = {}, args = {})
27
68
  self.class.init_model unless self.class.model_initialized?
28
69
 
29
- @data = data
70
+ reset_errors
30
71
  @changes = {}
31
72
 
32
- reset_errors
73
+ if args[:init]
74
+ @data = self.class.__blank_attributes.merge(real_attributes(data))
75
+ else
76
+ @data = self.class.__blank_attributes.clone
77
+ @changes.merge!(real_attributes(data))
78
+ end
33
79
 
34
80
  if @data[:id]
35
81
  @new_record = false
@@ -38,18 +84,45 @@ class BazaModels::Model
38
84
  end
39
85
  end
40
86
 
87
+ # rubocop:disable Style/TrivialAccessors
41
88
  def new_record?
42
- return @new_record
89
+ # rubocop:enable Style/TrivialAccessors
90
+
91
+ @new_record
92
+ end
93
+
94
+ def persisted?
95
+ !new_record?
43
96
  end
44
97
 
45
98
  def db
46
99
  return @db if @db
47
- return self.class.db
100
+ @db ||= self.class.db
48
101
  end
49
102
 
103
+ attr_writer :db
104
+
50
105
  def self.db
106
+ @db = nil if @db && @db.closed?
51
107
  return @db if @db
52
- return BazaModels.primary_db
108
+ @db ||= BazaModels.primary_db
109
+ raise "No Baza database has been configured" unless @db
110
+ @db
111
+ end
112
+
113
+ def self.to_adapter
114
+ BazaModels::BazaOrmAdapter.new(class: self)
115
+ end
116
+
117
+ class << self
118
+ attr_writer :db, :table_name
119
+ end
120
+
121
+ attr_writer :table_name
122
+
123
+ def autoloads
124
+ @autoloads ||= {}
125
+ @autoloads
53
126
  end
54
127
 
55
128
  def table_name
@@ -57,172 +130,220 @@ class BazaModels::Model
57
130
  end
58
131
 
59
132
  def self.table_name
60
- @table_name ||= "#{StringCases.camel_to_snake(name)}s"
133
+ @table_name ||= "#{StringCases.camel_to_snake(name.gsub("::", ""))}s"
61
134
  end
62
135
 
63
- def self.init_model
64
- @table = db.tables[table_name]
65
- @table.columns.each do |column_name, column|
66
- init_attribute_from_column(column)
67
- end
136
+ def self.relationships
137
+ @relationships ||= {}
138
+ end
68
139
 
69
- @model_initialized = true
140
+ def to_model
141
+ self
70
142
  end
71
143
 
72
- def self.init_attribute_from_column(column)
73
- column_name = column.name.to_sym
144
+ def self.init_model
145
+ @table = db.tables[table_name]
74
146
 
75
- define_method(column_name) do
76
- return @changes.fetch(column_name) if @changes.key?(column_name)
77
- return @data.fetch(column_name)
78
- end
147
+ @__blank_attributes ||= {}
79
148
 
80
- define_method("#{column_name}_was") do
81
- return @data.fetch(column_name)
149
+ @table.columns do |column|
150
+ init_attribute_from_column(column)
151
+ @__blank_attributes[column.name.to_sym] = nil
82
152
  end
83
153
 
84
- define_method("#{column_name}=") do |new_value|
85
- @changes[column_name] = new_value
86
- end
154
+ @model_initialized = true
87
155
  end
88
156
 
89
- def self.model_initialized?
90
- return @model_initialized
157
+ def id
158
+ @data.fetch(:id)
91
159
  end
92
160
 
93
- def self.validates(attribute_name, args)
94
- args.each do |validator_name, validator_args|
95
- validator_camel_name = StringCases.snake_to_camel(validator_name)
96
- class_name = "#{validator_camel_name}Validator"
161
+ def to_param
162
+ id.to_s if id
163
+ end
97
164
 
98
- @@validators ||= {}
99
- @@validators[attribute_name] ||= []
100
- @@validators[attribute_name] << BazaModels::Validators.const_get(class_name).new(attribute_name, args)
165
+ def to_key
166
+ if new_record?
167
+ nil
168
+ else
169
+ [id]
101
170
  end
102
171
  end
103
172
 
104
- def id
105
- return @data[:id]
173
+ def reload
174
+ @data = db.single(table_name, {id: id}, limit: 1)
175
+ raise BazaModels::Errors::RecordNotFound unless @data
176
+ @changes = {}
177
+ self
106
178
  end
107
179
 
108
- def save
109
- if valid?
110
- new_record = new_record?
111
- fire_callbacks(:before_save)
180
+ def to_s
181
+ if new_record?
182
+ "#<#{self.class.name} new!>"
183
+ else
184
+ "#<#{self.class.name} id=#{id}>"
185
+ end
186
+ end
112
187
 
113
- if new_record
114
- fire_callbacks(:before_create)
115
- @data[:id] = db.insert(table_name, @data.merge(@changes), return_id: true)
188
+ def inspect
189
+ data_str = ""
190
+ @data.each do |key, value|
191
+ if @changes.key?(key)
192
+ value_to_use = @changes.fetch(key)
116
193
  else
117
- db.update(table_name, @changes, id: id)
194
+ value_to_use = value
118
195
  end
119
196
 
120
- @changes = {}
121
- @new_record = false
122
- reload
197
+ data_str << " " unless data_str.empty?
198
+ data_str << "#{key}=\"#{value_to_use}\""
199
+ end
200
+
201
+ "#<#{self.class.name} #{data_str}>"
202
+ end
123
203
 
124
- fire_callbacks(:after_save)
125
- fire_callbacks(:after_create) if new_record
204
+ def ==(other)
205
+ return false unless self.class == other.class
126
206
 
127
- return true
207
+ if new_record? && other.new_record?
208
+ return merged_data == other.__send__(:merged_data)
128
209
  else
129
- return false
210
+ return id == other.id
130
211
  end
131
212
  end
132
213
 
133
- def save!
134
- if save
135
- return true
136
- else
137
- raise BazaModels::Errors::InvalidRecord
138
- end
214
+ # rubocop:disable Style/PredicateName
215
+ def has_attribute?(name)
216
+ # rubocop:enable Style/PredicateName
217
+ self.class.column_names.include?(name.to_s)
139
218
  end
140
219
 
141
- def reload
142
- @data = db.select(table_name, {id: id}, limit: 1).fetch
143
- @changes = {}
144
- return self
220
+ def self.column_names
221
+ @column_names ||= __blank_attributes.keys.map(&:to_s)
145
222
  end
146
223
 
147
- def destroy
148
- if new_record?
149
- errors.add(:base, "cannot destroy new record")
150
- return false
151
- else
152
- fire_callbacks(:before_destroy)
153
- db.delete(table_name, id: id)
154
- fire_callbacks(:after_destroy)
155
- return true
156
- end
224
+ def self.ransack(params)
225
+ BazaModels::Ransacker.new(class: self, params: params)
157
226
  end
158
227
 
159
- def destroy!
160
- raise BazaModels::Errors::InvalidRecord, @errors.full_messages.join(". ") unless destroy
228
+ def [](key)
229
+ read_attribute(key)
161
230
  end
162
231
 
163
- def assign_attributes(attributes)
164
- @changes.merge!(attributes)
232
+ def []=(key, value)
233
+ write_attribute(key, value)
165
234
  end
166
235
 
167
- def update_attributes(attributes)
168
- assign_attributes(attributes)
169
- return save
236
+ def read_attribute(attribute_name)
237
+ return @changes.fetch(attribute_name) if @changes.key?(attribute_name)
238
+ @data.fetch(attribute_name)
170
239
  end
171
240
 
172
- def update_attributes!(attributes)
173
- unless update_attributes(attributes)
174
- raise BazaModels::Errors::InvalidRecord, @errors.full_messages.join(". ")
241
+ def write_attribute(attribute_name, value)
242
+ @changes[attribute_name] = value
243
+ end
244
+
245
+ def changed?
246
+ changed = false
247
+ @changes.each do |key, value|
248
+ next if @data.fetch(key) == value
249
+ changed = true
250
+ break
175
251
  end
252
+
253
+ changed
176
254
  end
177
255
 
178
- def valid?
179
- fire_callbacks(:before_validation)
256
+ protected
180
257
 
181
- if new_record?
182
- fire_callbacks(:before_validation_on_create)
183
- else
184
- fire_callbacks(:before_validation_on_update)
185
- end
258
+ class << self
259
+ attr_reader :__blank_attributes
260
+ end
186
261
 
187
- reset_errors
262
+ # rubocop:disable Style/TrivialAccessors
263
+ def self.model_initialized?
264
+ # rubocop:enable Style/TrivialAccessors
265
+ @model_initialized
266
+ end
188
267
 
189
- merged_data = @data.merge(@changes)
190
- merged_data.each do |attribute_name, attribute_value|
191
- next unless @@validators.key?(attribute_name)
268
+ def self.init_attribute_from_column(column)
269
+ column_name = column.name.to_sym
192
270
 
193
- @@validators[attribute_name].each do |validator|
194
- validator.validate(self, attribute_value)
195
- end
271
+ define_method(column_name) do
272
+ read_attribute(column_name)
273
+ end
274
+
275
+ define_method("#{column_name}_was") do
276
+ return @data.fetch(column_name)
196
277
  end
197
278
 
198
- fire_callbacks(:after_validation)
279
+ define_method("#{column_name}=") do |new_value|
280
+ write_attribute(column_name, new_value)
281
+ end
199
282
 
200
- if new_record?
201
- fire_callbacks(:after_validation_on_create)
202
- else
203
- fire_callbacks(:after_validation_on_update)
283
+ define_method("#{column_name}?") do
284
+ !@data.fetch(column_name).to_s.strip.empty?
204
285
  end
205
286
 
206
- return @errors.empty?
287
+ define_method("#{column_name}_changed?") do
288
+ if @changes.key?(column_name) && @changes.fetch(column_name) != @data.fetch(column_name)
289
+ true
290
+ else
291
+ false
292
+ end
293
+ end
207
294
  end
208
295
 
209
- protected
210
-
211
296
  def reset_errors
212
297
  @errors = BazaModels::Errors.new
213
298
  end
214
299
 
215
300
  def fire_callbacks(name)
216
- if @@callbacks[name]
217
- @@callbacks[name].each do |callback_data|
218
- if callback_data[:block]
219
- callback_data[:block].call(*callback_data[:args])
220
- elsif callback_data[:method_name]
221
- __send__(callback_data[:method_name], *callback_data[:args])
301
+ return if !@@callbacks[name] || !@@callbacks[name][self.class.name]
302
+
303
+ @@callbacks[name][self.class.name].each do |callback_data|
304
+ if callback_data[:block]
305
+ instance_eval(&callback_data.fetch(:block))
306
+ elsif callback_data[:method_name]
307
+ __send__(callback_data[:method_name], *callback_data.fetch(:args))
308
+ else
309
+ raise "Didn't know how to perform callbacks for #{name}"
310
+ end
311
+ end
312
+ end
313
+
314
+ def merged_data
315
+ @data.merge(@changes)
316
+ end
317
+
318
+ # Converts attributes like "user" to "user_id" and so on.
319
+ def real_attributes(attributes)
320
+ new_attributes = {}
321
+ attributes.each do |attribute_name, attribute_value|
322
+ belongs_to_relations = self.class.instance_variable_get(:@belongs_to_relations)
323
+
324
+ if belongs_to_relations
325
+ belongs_to_relations.each do |relation|
326
+ if attribute_name.to_s == relation[:relation_name].to_s
327
+ attribute_name = :"#{attribute_name}_id"
328
+ attribute_value = attribute_value.id
329
+ end
330
+ end
331
+ end
332
+
333
+ unless has_attribute?(attribute_name)
334
+ set_method_name = "#{attribute_name}="
335
+
336
+ if respond_to?(set_method_name)
337
+ __send__(set_method_name, attribute_value)
338
+ next
222
339
  else
223
- raise "Didn't know how to perform callbacks for #{name}"
340
+ raise "Unknown attribute: #{attribute_name}"
224
341
  end
225
342
  end
343
+
344
+ new_attributes[attribute_name.to_sym] = attribute_value
226
345
  end
346
+
347
+ new_attributes
227
348
  end
228
349
  end