baza_models 0.0.0 → 0.0.1

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