Created
November 21, 2018 12:40
-
-
Save javierfernandes/1f6bee63a2d224ab7f9cfa54fda4aa90 to your computer and use it in GitHub Desktop.
Metaprogramacion - Transacciones de objetos (Parte 2 & 4)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Transactor | |
| def self.perform(*args, &bloque) | |
| t = Transaction.new(args) | |
| begin | |
| bloque.call(*args) | |
| t.commit() | |
| rescue Exception => e | |
| t.undo() | |
| raise e | |
| end | |
| t | |
| end | |
| end | |
| # State related extensions to all objects | |
| class Object | |
| def takeState | |
| ObjectState.new( | |
| self, | |
| instance_variables.collect do | varName | | |
| StateVariable.new(self, varName, instance_variable_get(varName)) | |
| end | |
| ) | |
| end | |
| def restoreState(state) | |
| state.variables.each { |var| instance_variable_set(var.name, var.value) } | |
| end | |
| end | |
| # | |
| # Transaction model | |
| # | |
| class Transaction | |
| def initialize(objects) | |
| @objects = objects | |
| @copies = copyState(objects) | |
| end | |
| def copyState(objects) | |
| objects.map &:takeState | |
| end | |
| def commit | |
| @changes = @copies.flat_map &:changes | |
| end | |
| def undo | |
| @copies.each_with_index { |state, i| @objects[i].restoreState(state) } | |
| end | |
| def changes | |
| @changes | |
| end | |
| end | |
| class ObjectState | |
| attr_accessor :object | |
| attr_accessor :variables | |
| def initialize(object, variables) | |
| @object = object | |
| @variables = variables | |
| end | |
| def changes | |
| variables.reduce([]) do |acc, variableState| | |
| current = variableState.getFrom(@object) | |
| initial = variableState.value | |
| if (current != initial) | |
| acc.push([@object.object_id, variableState.name, initial, current]) | |
| end | |
| acc | |
| end | |
| end | |
| end | |
| class StateVariable | |
| attr_accessor :object | |
| attr_accessor :name | |
| attr_accessor :value | |
| def initialize(object, name, value) | |
| self.object = object | |
| self.name = name | |
| self.value = value | |
| end | |
| def getFrom(object) | |
| object.instance_variable_get(@name) | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Transactor2 | |
| def self.perform(&bloque) | |
| startTransaction | |
| begin | |
| bloque.call | |
| currentTransaction.commit | |
| currentTransaction | |
| rescue Exception => e | |
| raise e | |
| ensure | |
| clearTransaction | |
| end | |
| end | |
| def self.startTransaction | |
| Thread.current[:transaction] = Transaction2.new | |
| end | |
| def self.currentTransaction | |
| Thread.current[:transaction] || NoopTransaction | |
| end | |
| def self.clearTransaction | |
| Thread.current[:transaction] = nil | |
| end | |
| end | |
| # Usage API (open class) | |
| class Object | |
| def self.transactional | |
| def self.attr_accessor(*names) | |
| names.each do |name| | |
| variableName = "@#{name}" | |
| define_method name do | |
| currentTransaction.read(self, variableName) | |
| end | |
| define_method "#{name}=" do |newValue| | |
| currentTransaction.write(self, variableName, newValue) | |
| end | |
| end | |
| end | |
| define_method :currentTransaction do | |
| Transactor2.currentTransaction | |
| end | |
| end | |
| end | |
| # | |
| # Transaction Object Model | |
| # | |
| # La no-transaccion, delega en al variable de instancia del objeto | |
| # (comportamiento default de ruby) | |
| class NoopTransaction | |
| def self.read(object, name) | |
| object.instance_variable_get(name) | |
| end | |
| def self.write(object, name, value) | |
| object.instance_variable_set(name, value) | |
| end | |
| def changes | |
| [] | |
| end | |
| end | |
| class Transaction2 | |
| def initialize | |
| @states = Hash.new | |
| @commited = false | |
| end | |
| def commit | |
| if (not @commited) | |
| applyValues(:after) | |
| @commited = true | |
| end | |
| end | |
| def undo | |
| if (@commited) | |
| applyValues(:before) | |
| @commited = false | |
| end | |
| end | |
| # Aplica el estado a los objetos | |
| # Puede ser aplicar el valor original (:before) o el valor luego de la transacción (:after) | |
| def applyValues(which) | |
| @states.each_pair do |id, state| | |
| state.applyValues(which) | |
| end | |
| end | |
| # interceptors | |
| def read(object, name) | |
| getStateFor(object).read(name) | |
| end | |
| def write(object, name, value) | |
| state = getStateFor(object) | |
| state.write(name, value) | |
| end | |
| def getStateFor(object) | |
| state = @states[object.object_id] || ObjectTransactionalState.new(object) | |
| @states[object.object_id] = state | |
| end | |
| def changes | |
| @states.keys.inject([]) do |acc, id| | |
| @states[id].collectChanges(acc) | |
| end | |
| end | |
| end | |
| class ObjectTransactionalState | |
| attr_accessor :object, :state | |
| def initialize(object) | |
| self.object = object | |
| self.state = Hash.new | |
| end | |
| def write(name, value) | |
| if (@state.has_key? name) | |
| @state[name].after = value | |
| else | |
| previousValue = _readFromObject(name) | |
| @state[name] = ObjectVariable.new(@object, name, previousValue, value) | |
| end | |
| end | |
| def read(name) | |
| if (@state[name]) | |
| @state[name].after | |
| else | |
| _readFromObject(name) | |
| end | |
| end | |
| def _readFromObject(name) | |
| @object.instance_variable_get(name) | |
| end | |
| def applyValues(which) | |
| @state.each_value do |variable| | |
| variable.applyValue(which) | |
| end | |
| end | |
| def collectChanges(acc) | |
| @state.values.each do |variable| | |
| acc.push(variable.change) | |
| end | |
| acc | |
| end | |
| end | |
| # Representa el estado de una variable de instancia en cierto objeto | |
| # before = valor inicial / original | |
| # after = valor actual en esta transaccion / final (si ya terminó) | |
| class ObjectVariable | |
| attr_accessor :object, :name, :before, :after | |
| def initialize(object, name, before, after) | |
| self.object = object | |
| self.name = name | |
| self.before = before | |
| self.after = after | |
| end | |
| # Commitea uno de los valores en el objeto. Util para commitear / rollbackear | |
| # which = :before | :after | |
| def applyValue(which) | |
| @object.instance_variable_set(@name, self.send(which)) | |
| end | |
| def change | |
| [@object.object_id, @name.to_sym, @before, @after] | |
| end | |
| end | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'rspec' | |
| require_relative '../src/transactor_parte2' | |
| describe 'transactor' do | |
| class Persona | |
| transactional | |
| attr_accessor :nombre, :edad | |
| def initialize(nombre, edad) | |
| self.nombre = nombre | |
| self.edad = edad | |
| end | |
| def cumplirAnios | |
| self.edad = self.edad + 1 | |
| end | |
| end | |
| it 'ejecuta el bloque exitosamente' do | |
| persona = Persona.new("Lanata", 44) | |
| Transactor2.perform do | |
| persona.cumplirAnios() | |
| end | |
| expect(persona.edad).to eq(45) | |
| end | |
| it 'deshace cambios a variables de instancia si el bloque arroja exception' do | |
| p = Persona.new("Lanata", 44) | |
| expect { | |
| Transactor2.perform do | |
| p.cumplirAnios() | |
| raise "Se pudrio todo, aca nadie cumple nada" | |
| end | |
| }.to raise_error("Se pudrio todo, aca nadie cumple nada") | |
| expect(p.edad).to eq(44) | |
| end | |
| it "perform retorna un objeto transaccion que se puede deshacer" do | |
| p = Persona.new("ponyo", 22) | |
| transaction = Transactor2.perform do | |
| p.cumplirAnios() | |
| end | |
| expect(p.edad).to eq 23 # anduvo ! | |
| transaction.undo() | |
| expect(p.edad).to eq 22 # la deshizo ! | |
| end | |
| it "captura los cambios en la transaccion" do | |
| p = Persona.new("ponyo", 22) | |
| transaction = Transactor2.perform { | |
| p.cumplirAnios() | |
| } | |
| expect(transaction.changes()).to eq [[p.object_id, :@edad, 22, 23]] | |
| end | |
| end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| require 'rspec' | |
| require_relative '../src/transactor' | |
| describe 'transactor' do | |
| class Persona | |
| attr_accessor :nombre | |
| attr_accessor :edad | |
| def initialize(nombre, edad) | |
| self.nombre = nombre | |
| self.edad = edad | |
| end | |
| def cumplirAnios | |
| self.edad = self.edad + 1 | |
| end | |
| end | |
| it 'ejecuta el bloque con el parametro dado' do | |
| persona = Persona.new("Lanata", 44) | |
| Transactor.perform(persona) do |p| | |
| p.cumplirAnios() | |
| end | |
| expect(persona.edad).to eq(45) | |
| end | |
| it 'deshace cambios a variables de instancia si el bloque arroja exception' do | |
| persona = Persona.new("Lanata", 44) | |
| expect { | |
| Transactor.perform(persona) do |p| | |
| p.cumplirAnios() | |
| raise "Se pudrio todo, aca nadie cumple nada" | |
| end | |
| }.to raise_error("Se pudrio todo, aca nadie cumple nada") | |
| expect(persona.edad).to eq(44) | |
| end | |
| it "perform retorna un objeto transaccion que se puede deshacer" do | |
| p = Persona.new("ponyo", 22) | |
| transaction = Transactor.perform(p) { |p| | |
| p.cumplirAnios() | |
| } | |
| expect(p.edad).to eq 23 # anduvo ! | |
| transaction.undo() | |
| expect(p.edad).to eq 22 # la deshizo ! | |
| end | |
| it "captura los cambios en la transaccion" do | |
| p = Persona.new("ponyo", 22) | |
| transaction = Transactor.perform(p) { |p| | |
| p.cumplirAnios() | |
| } | |
| expect(transaction.changes()).to eq [[p.object_id, :@edad, 22, 23]] | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment