Решение на Пета задача - DataModel от Михаил Здравков

Обратно към всички решения

Към профила на Михаил Здравков

Резултати

  • 5 точки от тестове
  • 0 бонус точки
  • 5 точки общо
  • 20 успешни тест(а)
  • 5 неуспешни тест(а)

Код

class Object
def with_attr(field, value)
self.public_send "#{field}=", value
self
end
end
class ArrayStore
attr_reader :storage
def initialize
@storage = []
end
def create(record)
@storage << record
end
def find(query = {})
@storage.find_all { |record| query < record }
end
def update(id, attributes = {})
@storage[id].merge! attributes
end
def delete(query)
@storage.delete_if { |record| query < record }
end
end
class HashStore
attr_reader :storage
def initialize
@storage = {}
end
def create(record)
@storage[record[:id]] = record
end
def find(query = {})
@storage.values.find_all { |record| query < record }
end
def update(id, attributes = {})
@storage[id].merge! attributes
end
def delete(query)
find(query).each { |match| @storage.delete match[:id] }
end
end
module DataModelClassMethods
def attributes(*attributes)
return @attrs ||= [] if attributes.count == 0
@attrs = attributes
attributes.each do |attribute|
attr_accessor attribute
define_singleton_method "find_by_#{attribute}" do |value|
@store.find(Hash[attribute, value]).map { |record| record_to_model(record) }
end
end
end
def data_store(*store)
return @store if store.empty?
if store.count == 1
@store = store.first
@store.instance_variable_set "@max_id", 0
return @store
end
throw ArgumentError.new("got #{store.count} arguments (expected 0 or 1)")
end
def where(query = {})
diff = query.keys - attributes
throw UnknownAttributeError.new("Unknown attribute #{diff[0]}") unless diff.empty?
@store.find(query).map { |record| record_to_model(record) }
end
private_class_method
def record_to_model(record)
new(record).with_attr "id", record[:id]
end
end
class DataModel
extend DataModelClassMethods
attr_accessor :id
def initialize(values = {})
@id = nil
self.class.attributes.each { |attr| instance_variable_set "@#{attr}", values[attr] }
end
def save
attribute_values = self.class.attributes.map { |attr| [attr, public_send(attr)] }.to_h
if @id
self.class.data_store.update(@id, attribute_values)
else
@id = self.class.data_store.instance_variable_get "@max_id"
@id += 1
self.class.data_store.instance_variable_set "@max_id", @id
self.class.data_store.create(attribute_values.merge(id: @id))
end
self.class.record_to_model(self.class.data_store.find(id: @id).first)
end
def delete
throw DeleteUnsavedRecordError.new if self.class.data_store.find(id: @id).empty?
self.class.data_store.delete(id: @id)
@id = nil
self
end
def ==(other)
return false if other.class != self.class
return @id == other.id if !@id.nil? && !other.id.nil?
self.equal? other
end
class DeleteUnsavedRecordError < RuntimeError; end
class UnknownAttributeError < RuntimeError; end
end

Лог от изпълнението

.......F....F.F.......FF.

Failures:

  1) DataModel equality comparison compares by id if both records are saved
     Failure/Error: modified_ivan = user_model.where(id: ivan.id).first
     NameError:
       uninitialized constant DataModelClassMethods::UnknownAttributeError
     # /tmp/d20161202-15620-oovmxf/solution.rb:81:in `where'
     # /tmp/d20161202-15620-oovmxf/spec.rb:102:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (2 levels) in <top (required)>'

  2) DataModel.where raises an error if the query is by an unknown key
     Failure/Error: expect { user_model.where(middle_name: 'Ivanov') }.to raise_error(
       expected DataModel::UnknownAttributeError with "Unknown attribute middle_name", got #<NameError: uninitialized constant DataModelClassMethods::UnknownAttributeError> with backtrace:
         # /tmp/d20161202-15620-oovmxf/solution.rb:81:in `where'
         # /tmp/d20161202-15620-oovmxf/spec.rb:143:in `block (4 levels) in <top (required)>'
         # /tmp/d20161202-15620-oovmxf/spec.rb:143:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:7:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:7:in `block (2 levels) in <top (required)>'
     # /tmp/d20161202-15620-oovmxf/spec.rb:143:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (2 levels) in <top (required)>'

  3) DataModel#delete raises an error if the record is not saved
     Failure/Error: expect { user_model.new(first_name: 'Ivan').delete }.to raise_error(
       expected DataModel::DeleteUnsavedRecordError, got #<UncaughtThrowError: uncaught throw #<DataModel::DeleteUnsavedRecordError: DataModel::DeleteUnsavedRecordError>> with backtrace:
         # /tmp/d20161202-15620-oovmxf/solution.rb:116:in `throw'
         # /tmp/d20161202-15620-oovmxf/solution.rb:116:in `delete'
         # /tmp/d20161202-15620-oovmxf/spec.rb:163:in `block (4 levels) in <top (required)>'
         # /tmp/d20161202-15620-oovmxf/spec.rb:163:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:7:in `block (3 levels) in <top (required)>'
         # ./lib/language/ruby/run_with_timeout.rb:7:in `block (2 levels) in <top (required)>'
     # /tmp/d20161202-15620-oovmxf/spec.rb:163:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (2 levels) in <top (required)>'

  4) ArrayStore behaves like a data store #update updates the attributes of a record with a given ID
     Failure/Error: store.update(2, {id: 2, name: 'Georgi'})
     NoMethodError:
       undefined method `merge!' for nil:NilClass
     Shared Example Group: "a data store" called from /tmp/d20161202-15620-oovmxf/spec.rb:239
     # /tmp/d20161202-15620-oovmxf/solution.rb:24:in `update'
     # /tmp/d20161202-15620-oovmxf/spec.rb:199:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (2 levels) in <top (required)>'

  5) ArrayStore behaves like a data store #update only updates records with the correct IDs
     Failure/Error: store.update(2, {id: 2, name: 'Sasho'})
     NoMethodError:
       undefined method `merge!' for nil:NilClass
     Shared Example Group: "a data store" called from /tmp/d20161202-15620-oovmxf/spec.rb:239
     # /tmp/d20161202-15620-oovmxf/solution.rb:24:in `update'
     # /tmp/d20161202-15620-oovmxf/spec.rb:210:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (3 levels) in <top (required)>'
     # ./lib/language/ruby/run_with_timeout.rb:7:in `block (2 levels) in <top (required)>'

Finished in 0.03015 seconds
25 examples, 5 failures

Failed examples:

rspec /tmp/d20161202-15620-oovmxf/spec.rb:92 # DataModel equality comparison compares by id if both records are saved
rspec /tmp/d20161202-15620-oovmxf/spec.rb:142 # DataModel.where raises an error if the query is by an unknown key
rspec /tmp/d20161202-15620-oovmxf/spec.rb:162 # DataModel#delete raises an error if the record is not saved
rspec /tmp/d20161202-15620-oovmxf/spec.rb:196 # ArrayStore behaves like a data store #update updates the attributes of a record with a given ID
rspec /tmp/d20161202-15620-oovmxf/spec.rb:204 # ArrayStore behaves like a data store #update only updates records with the correct IDs

История (5 версии и 7 коментара)

Михаил обнови решението на 29.11.2016 20:41 (преди над 7 години)

+class Object
+ def with_attr(field, value)
+ self.public_send "#{field}=", value
+ self
+ end
+end
+
+class ArrayStore
+ attr_reader :storage
+
+ def initialize
+ @storage = []
+ end
+
+ def create(record)
+ @storage << record
+ end
+
+ def find(query = {})
+ @storage.find_all { |record| query < record }
+ end
+
+ def delete(query)
+ @storage.delete_if { |record| query < record }
+ end
+end
+
+class HashStore
+ attr_reader :storage
+
+ def initialize
+ @storage = {}
+ end
+
+ def create(record)
+ @storage[record[:id]] = record
+ end
+
+ def find(query = {})
+ @storage.values.find_all { |record| query < record }
+ end
+
+ def update(id, attributes = {})
+ @storage[id].merge! attributes
+ end
+
+ def delete(query)
+ find(query).each { |match| @storage.delete match[:id] }
+ end
+end
+
+class DataModel
+ attr_accessor :id
+
+ def initialize(values = {})
+ @id = nil
+ self.class.attributes.each { |attr| instance_variable_set "@#{attr}", values[attr] }
+ end
+
+ def save
+ attribute_values = self.class.attributes.map { |attr| [attr, public_send(attr)] }.to_h
+ attribute_values[:id] = self.class.data_store.storage.count + 1
+ self.class.data_store.update(attribute_values) if @id
+ @id = self.class.data_store.create(attribute_values)[:id] unless @id
+ self
+ end
+
+ def delete
+ throw DeleteUnsavedRecordError.new if self.class.data_store.find(id: @id).empty?
+ self.class.data_store.delete(id: @id)
+ @id = nil
+ self
+ end
+
+ def ==(other)
+ return false if other.class != self.class
+ return @id == other.id if !@id.nil? && !other.id.nil?
+ self.equal? other
+ end
+
+ class DeleteUnsavedRecordError < RuntimeError; end
+ class UnknownAttributeError < RuntimeError; end
+
+ def self.attributes(*attributes)
+ return @attrs ||= [] if attributes.count == 0
+ @attrs = attributes
+
+ attributes.each do |attribute|
+ attr_accessor attribute
+ define_singleton_method "find_by_#{attribute}" do |value|
+ @store.find(Hash[attribute, value]).map { |record| record_to_model(record) }
+ end
+ end
+ end
+
+ def self.data_store(*store)
+ return @store if store.empty?
+ return @store = store.first if store.count == 1
+ throw ArgumentError.new("got #{store.count} arguments (expected 0 or 1)")
+ end
+
+ def self.where(query = {})
+ diff = attributes - query.keys
+ throw UnknownAttributeError.new("Unknown attribute #{diff[0]}") unless diff.empty?
+ @store.find(query).map { |record| record_to_model(record) }
+ end
+
+ private_class_method
+
+ def self.record_to_model(record)
+ new(record).with_attr "id", record[:id]
+ end
+end

Михаил обнови решението на 29.11.2016 20:42 (преди над 7 години)

class Object
def with_attr(field, value)
self.public_send "#{field}=", value
self
end
end
class ArrayStore
attr_reader :storage
def initialize
@storage = []
end
def create(record)
@storage << record
end
def find(query = {})
@storage.find_all { |record| query < record }
end
+ def update(id, attributes = {})
+ @storage[id].merge! attributes
+ end
+
def delete(query)
@storage.delete_if { |record| query < record }
end
end
class HashStore
attr_reader :storage
def initialize
@storage = {}
end
def create(record)
@storage[record[:id]] = record
end
def find(query = {})
@storage.values.find_all { |record| query < record }
end
def update(id, attributes = {})
@storage[id].merge! attributes
end
def delete(query)
find(query).each { |match| @storage.delete match[:id] }
end
end
class DataModel
attr_accessor :id
def initialize(values = {})
@id = nil
self.class.attributes.each { |attr| instance_variable_set "@#{attr}", values[attr] }
end
def save
attribute_values = self.class.attributes.map { |attr| [attr, public_send(attr)] }.to_h
attribute_values[:id] = self.class.data_store.storage.count + 1
self.class.data_store.update(attribute_values) if @id
@id = self.class.data_store.create(attribute_values)[:id] unless @id
self
end
def delete
throw DeleteUnsavedRecordError.new if self.class.data_store.find(id: @id).empty?
self.class.data_store.delete(id: @id)
@id = nil
self
end
def ==(other)
return false if other.class != self.class
return @id == other.id if !@id.nil? && !other.id.nil?
self.equal? other
end
class DeleteUnsavedRecordError < RuntimeError; end
class UnknownAttributeError < RuntimeError; end
def self.attributes(*attributes)
return @attrs ||= [] if attributes.count == 0
@attrs = attributes
attributes.each do |attribute|
attr_accessor attribute
define_singleton_method "find_by_#{attribute}" do |value|
@store.find(Hash[attribute, value]).map { |record| record_to_model(record) }
end
end
end
def self.data_store(*store)
return @store if store.empty?
return @store = store.first if store.count == 1
throw ArgumentError.new("got #{store.count} arguments (expected 0 or 1)")
end
def self.where(query = {})
diff = attributes - query.keys
throw UnknownAttributeError.new("Unknown attribute #{diff[0]}") unless diff.empty?
@store.find(query).map { |record| record_to_model(record) }
end
private_class_method
def self.record_to_model(record)
new(record).with_attr "id", record[:id]
end
end

Михаил обнови решението на 29.11.2016 21:07 (преди над 7 години)

class Object
def with_attr(field, value)
self.public_send "#{field}=", value
self
end
end
class ArrayStore
attr_reader :storage
def initialize
@storage = []
end
def create(record)
@storage << record
end
def find(query = {})
@storage.find_all { |record| query < record }
end
def update(id, attributes = {})
@storage[id].merge! attributes
end
def delete(query)
@storage.delete_if { |record| query < record }
end
end
class HashStore
attr_reader :storage
def initialize
@storage = {}
end
def create(record)
@storage[record[:id]] = record
end
def find(query = {})
@storage.values.find_all { |record| query < record }
end
def update(id, attributes = {})
@storage[id].merge! attributes
end
def delete(query)
find(query).each { |match| @storage.delete match[:id] }
end
end
class DataModel
attr_accessor :id
def initialize(values = {})
@id = nil
self.class.attributes.each { |attr| instance_variable_set "@#{attr}", values[attr] }
end
def save
attribute_values = self.class.attributes.map { |attr| [attr, public_send(attr)] }.to_h
- attribute_values[:id] = self.class.data_store.storage.count + 1
- self.class.data_store.update(attribute_values) if @id
- @id = self.class.data_store.create(attribute_values)[:id] unless @id
- self
+ self.class.data_store.update(@id, attribute_values) if @id
+ unless @id

Мислех си за това, но ми се стори странно. Защото реално този нов модул би имал смисъл единствено за дадения клас, без него не може да се ползва. Това ме кара да си мисля, че трябва да си остане като част от класа. Или е нормална практика да разделиш по този начин различни смислови части от един компонент?

Да, обвързан е с дадения клас, но пък печелиш от това, че са на едно определено място всички функции за работа върху колекцията, отделени от тези, които работят с конкретен запис.

+ attribute_values[:id] = @id = self.class.data_store.storage.count + 1

Да, прав си. Просто тоя код го пренаписвах и променях N пъти за да пробвам различни решения и накрая съм се оплел. Достатъчно е просто да сетна @id и да го ползвам него по-надолу.

ПП. Ааа, видях и функционалния проблем. Ако съм трил може да сложа id, което вече го има...

+ self.class.data_store.create(attribute_values)
+ end
+ self.class.record_to_model(self.class.data_store.find(id: @id).first)
end
def delete
throw DeleteUnsavedRecordError.new if self.class.data_store.find(id: @id).empty?
self.class.data_store.delete(id: @id)
@id = nil
self
end
def ==(other)
return false if other.class != self.class
return @id == other.id if !@id.nil? && !other.id.nil?
self.equal? other
end
class DeleteUnsavedRecordError < RuntimeError; end
class UnknownAttributeError < RuntimeError; end
def self.attributes(*attributes)
return @attrs ||= [] if attributes.count == 0
@attrs = attributes
attributes.each do |attribute|
attr_accessor attribute
define_singleton_method "find_by_#{attribute}" do |value|
@store.find(Hash[attribute, value]).map { |record| record_to_model(record) }
end
end
end
def self.data_store(*store)
return @store if store.empty?
return @store = store.first if store.count == 1
throw ArgumentError.new("got #{store.count} arguments (expected 0 or 1)")
end
def self.where(query = {})
diff = attributes - query.keys
throw UnknownAttributeError.new("Unknown attribute #{diff[0]}") unless diff.empty?
@store.find(query).map { |record| record_to_model(record) }
end
private_class_method
def self.record_to_model(record)
new(record).with_attr "id", record[:id]
end
end

Михаил обнови решението на 01.12.2016 08:38 (преди над 7 години)

class Object
def with_attr(field, value)
self.public_send "#{field}=", value
self
end
end
class ArrayStore
attr_reader :storage
def initialize
@storage = []
end
def create(record)
@storage << record
end
def find(query = {})
@storage.find_all { |record| query < record }
end
def update(id, attributes = {})
@storage[id].merge! attributes
end
def delete(query)
@storage.delete_if { |record| query < record }
end
end
class HashStore
attr_reader :storage
def initialize
@storage = {}
end
def create(record)
@storage[record[:id]] = record
end
def find(query = {})
@storage.values.find_all { |record| query < record }
end
def update(id, attributes = {})
@storage[id].merge! attributes
end
def delete(query)
find(query).each { |match| @storage.delete match[:id] }
end
end
+module DataModelClassMethods
+ def attributes(*attributes)
+ return @attrs ||= [] if attributes.count == 0
+ @attrs = attributes
+
+ attributes.each do |attribute|
+ attr_accessor attribute
+ define_singleton_method "find_by_#{attribute}" do |value|
+ @store.find(Hash[attribute, value]).map { |record| record_to_model(record) }
+ end
+ end
+ end
+
+ def data_store(*store)
+ return @store if store.empty?
+ return @store = store.first if store.count == 1
+ throw ArgumentError.new("got #{store.count} arguments (expected 0 or 1)")
+ end
+
+ def where(query = {})
+ diff = query.keys - attributes
+ throw UnknownAttributeError.new("Unknown attribute #{diff[0]}") unless diff.empty?
+ @store.find(query).map { |record| record_to_model(record) }
+ end
+
+ private_class_method
+
+ def record_to_model(record)
+ new(record).with_attr "id", record[:id]
+ end
+end
+
class DataModel
+ extend DataModelClassMethods
+
attr_accessor :id
def initialize(values = {})
@id = nil
self.class.attributes.each { |attr| instance_variable_set "@#{attr}", values[attr] }
end
def save
attribute_values = self.class.attributes.map { |attr| [attr, public_send(attr)] }.to_h
- self.class.data_store.update(@id, attribute_values) if @id
- unless @id
- attribute_values[:id] = @id = self.class.data_store.storage.count + 1
- self.class.data_store.create(attribute_values)
+ if @id
+ self.class.data_store.update(@id, attribute_values)
+ else
+ ids = self.class.where.map(&:id)
+ free_ids = Range.new(1, (ids.max || 0) + 1).to_a - ids
+ @id = free_ids.min
+ self.class.data_store.create(attribute_values.merge(id: @id))
end
self.class.record_to_model(self.class.data_store.find(id: @id).first)
end
def delete
throw DeleteUnsavedRecordError.new if self.class.data_store.find(id: @id).empty?
self.class.data_store.delete(id: @id)
@id = nil
self
end
def ==(other)
return false if other.class != self.class
return @id == other.id if !@id.nil? && !other.id.nil?
self.equal? other
end
class DeleteUnsavedRecordError < RuntimeError; end
class UnknownAttributeError < RuntimeError; end
-
- def self.attributes(*attributes)
- return @attrs ||= [] if attributes.count == 0
- @attrs = attributes
-
- attributes.each do |attribute|
- attr_accessor attribute
- define_singleton_method "find_by_#{attribute}" do |value|
- @store.find(Hash[attribute, value]).map { |record| record_to_model(record) }
- end
- end
- end
-
- def self.data_store(*store)
- return @store if store.empty?
- return @store = store.first if store.count == 1
- throw ArgumentError.new("got #{store.count} arguments (expected 0 or 1)")
- end
-
- def self.where(query = {})
- diff = attributes - query.keys
- throw UnknownAttributeError.new("Unknown attribute #{diff[0]}") unless diff.empty?
- @store.find(query).map { |record| record_to_model(record) }
- end
-
- private_class_method
-
- def self.record_to_model(record)
- new(record).with_attr "id", record[:id]
- end
end

Михаил обнови решението на 01.12.2016 09:05 (преди над 7 години)

class Object
def with_attr(field, value)
self.public_send "#{field}=", value
self
end
end
class ArrayStore
attr_reader :storage
def initialize
@storage = []
end
def create(record)
@storage << record
end
def find(query = {})
@storage.find_all { |record| query < record }
end
def update(id, attributes = {})
@storage[id].merge! attributes
end
def delete(query)
@storage.delete_if { |record| query < record }
end
end
class HashStore
attr_reader :storage
def initialize
@storage = {}
end
def create(record)
@storage[record[:id]] = record
end
def find(query = {})
@storage.values.find_all { |record| query < record }
end
def update(id, attributes = {})
@storage[id].merge! attributes
end
def delete(query)
find(query).each { |match| @storage.delete match[:id] }
end
end
module DataModelClassMethods
def attributes(*attributes)
return @attrs ||= [] if attributes.count == 0
@attrs = attributes
attributes.each do |attribute|
attr_accessor attribute
define_singleton_method "find_by_#{attribute}" do |value|
@store.find(Hash[attribute, value]).map { |record| record_to_model(record) }
end
end
end
def data_store(*store)
return @store if store.empty?
- return @store = store.first if store.count == 1
+ if store.count == 1
+ @store = store.first
+ @store.instance_variable_set "@max_id", 0
+ return @store
+ end
throw ArgumentError.new("got #{store.count} arguments (expected 0 or 1)")
end
def where(query = {})
diff = query.keys - attributes
throw UnknownAttributeError.new("Unknown attribute #{diff[0]}") unless diff.empty?
@store.find(query).map { |record| record_to_model(record) }
end
private_class_method
def record_to_model(record)
new(record).with_attr "id", record[:id]
end
end
class DataModel
extend DataModelClassMethods
attr_accessor :id
def initialize(values = {})
@id = nil
self.class.attributes.each { |attr| instance_variable_set "@#{attr}", values[attr] }
end
def save
attribute_values = self.class.attributes.map { |attr| [attr, public_send(attr)] }.to_h
if @id
self.class.data_store.update(@id, attribute_values)
else
- ids = self.class.where.map(&:id)
- free_ids = Range.new(1, (ids.max || 0) + 1).to_a - ids
- @id = free_ids.min
+ @id = self.class.data_store.instance_variable_get "@max_id"
+ @id += 1
+ self.class.data_store.instance_variable_set "@max_id", @id
self.class.data_store.create(attribute_values.merge(id: @id))
end
self.class.record_to_model(self.class.data_store.find(id: @id).first)
end
def delete
throw DeleteUnsavedRecordError.new if self.class.data_store.find(id: @id).empty?
self.class.data_store.delete(id: @id)
@id = nil
self
end
def ==(other)
return false if other.class != self.class
return @id == other.id if !@id.nil? && !other.id.nil?
self.equal? other
end
class DeleteUnsavedRecordError < RuntimeError; end
class UnknownAttributeError < RuntimeError; end
end