module Granite module Form module Model # == Lifecycle methods for Granite::Form::Model # # Provides methods +save+ and +destroy+ and its bang variants. # Also, patches +create+ and +update_attributes+ methods by adding # save at the end. # # You can define save or destroy performers with define_ # methods. Create and update performers might be defined instead of # save performer: # # class Book # include Granite::Form::Model # include Granite::Form::Model::Lifecycle # # attribute :id, Integer # attribute :title, String # # define_save do # executes in the instance scope # REDIS.set(id, attributes.to_json) # end # # define_destroy do # REDIS.del(id) # end # end # # class Author # include Granite::Form::Model # include Granite::Form::Model::Lifecycle # # attribute :id, Integer # attribute :name, String # # define_create do # will be called on create only # REDIS.sadd('author_ids', id) # REDIS.set(id, attributes.to_json) # end # # define_update do # will be called on update only # REDIS.set(id, attributes.to_json) # end # end # # In case of undefined performer Granite::Form::UnsavableObject # or Granite::Form::UndestroyableObject will be raised respectively. # # If performers was not defined in model, they cat be passed as # blocks to `save`, `update` and `destroy` methods: # # authos.save { REDIS.set(id, attributes.to_json) } # authos.update { REDIS.set(id, attributes.to_json) } # authos.destroy { REDIS.del(id) } # # Save and destroy processes acts almost the save way as # ActiveRecord's (with +persisted?+ and +destroyed?+ methods # affecting). # module Lifecycle extend ActiveSupport::Concern included do include Persistence class_attribute(*%i[save create update destroy].map { |action| "_#{action}_performer" }) private(*%i[save create update destroy].map { |action| "_#{action}_performer=" }) end module ClassMethods # define_ methods define performers for lifecycle # actions. Every action block must return boolean result, which # would mean the action success. If action performed unsuccessfully # Granite::Form::ObjectNotSaved or Granite::Form::ObjectNotDestroyed will # be raised respectively in case of bang methods using. # # class Author # define_create { true } # end # # Author.new.save # => true # Author.new.save! # => true # # class Author # define_create { false } # end # # Author.new.save # => false # Author.new.save! # => Granite::Form::ObjectNotSaved # # Also performers blocks are executed in the instance context, but # instance also passed as argument # # define_update do |instance| # instance.attributes.to_json # end # # +define_create+ and +define_update+ performers has higher priority # than +define_save+. # # class Author # define_update { ... } # define_save { ... } # end # # author = Author.create # using define_save performer # author.update_attributes(...) # using define_update performer # %i[save create update destroy].each do |action| define_method "define_#{action}" do |&block| send("_#{action}_performer=", block) end end # Initializes new instance with attributes passed and calls +save+ # on it. Returns instance in any case. # def create(*args) new(*args).tap(&:save) end # Initializes new instance with attributes passed and calls +save!+ # on it. Returns instance in case of success and raises Granite::Form::ValidationError # or Granite::Form::ObjectNotSaved in case of validation or saving fail respectively. # def create!(*args) new(*args).tap(&:save!) end end # define_ on instance level works the same # way as class define_ methods, but defines # performers for instance only # # user.define_save do # REDIS.set(id, attributes.to_json) # end # user.save! # => will use instance-level performer # %i[save create update destroy].each do |action| define_method "define_#{action}" do |&block| send("_#{action}_performer=", block) end end # Assigns passed attributes and calls +save+ # Returns true or false in case of successful or unsuccessful # saving respectively. # # author.update(name: 'Donald') # # If update performer is not defined with `define_update` # or `define_save`, it raises Granite::Form::UnsavableObject. # Also save performer block might be passed instead of in-class # performer definition: # # author.update(name: 'Donald') { REDIS.set(id, attributes.to_json) } # def update(attributes, &block) assign_attributes(attributes) && save(&block) end alias_method :update_attributes, :update # Assigns passed attributes and calls +save!+ # Returns true in case of success and raises Granite::Form::ValidationError # or Granite::Form::ObjectNotSaved in case of validation or # saving fail respectively. # # author.update!(name: 'Donald') # # If update performer is not defined with `define_update` # or `define_save`, it raises Granite::Form::UnsavableObject. # Also save performer block might be passed instead of in-class # performer definition: # # author.update!(name: 'Donald') { REDIS.set(id, attributes.to_json) } # def update!(attributes, &block) assign_attributes(attributes) && save!(&block) end alias_method :update_attributes!, :update! # # Saves object by calling save performer defined with +define_save+, # +define_create+ or +define_update+ methods. # Returns true or false in case of successful # or unsuccessful saving respectively. Changes +persisted?+ to true # # author.save # # If save performer is not defined with `define_update` or # `define_create` or `define_save`, it raises Granite::Form::UnsavableObject. # Also save performer block might be passed instead of in-class # performer definition: # # author.save { REDIS.set(id, attributes.to_json) } # def save(_options = {}, &block) raise Granite::Form::UnsavableObject unless block || savable? valid? && save_object(&block) end # Saves object by calling save performer defined with +define_save+, # +define_create+ or +define_update+ methods. # Returns true in case of success and raises Granite::Form::ValidationError # or Granite::Form::ObjectNotSaved in case of validation or # saving fail respectively. Changes +persisted?+ to true # # author.save! # # If save performer is not defined with `define_update` or # `define_create` or `define_save`, it raises Granite::Form::UnsavableObject. # Also save performer block might be passed instead of in-class # performer definition: # # author.save! { REDIS.set(id, attributes.to_json) } # def save!(_options = {}, &block) raise Granite::Form::UnsavableObject unless block || savable? validate! save_object(&block) or raise Granite::Form::ObjectNotSaved end # Destroys object by calling the destroy performer. # Returns instance in any case. Changes +persisted?+ # to false and +destroyed?+ to true in case of success. # # author.destroy # # If destroy performer is not defined with `define_destroy`, # it raises Granite::Form::UndestroyableObject. # Also destroy performer block might be passed instead of in-class # performer definition: # # author.destroy { REDIS.del(id) } # def destroy(&block) raise Granite::Form::UndestroyableObject unless block || destroyable? destroy_object(&block) self end # Destroys object by calling the destroy performer. # In case of success returns instance and changes +persisted?+ # to false and +destroyed?+ to true. # Raises Granite::Form::ObjectNotDestroyed in case of fail. # # author.destroy! # # If destroy performer is not defined with `define_destroy`, # it raises Granite::Form::UndestroyableObject. # Also destroy performer block might be passed instead of in-class # performer definition: # # author.destroy! { REDIS.del(id) } # def destroy!(&block) raise Granite::Form::UndestroyableObject unless block || destroyable? destroy_object(&block) or raise Granite::Form::ObjectNotDestroyed self end private def savable? !!((persisted? ? _update_performer : _create_performer) || _save_performer) end def save_object(&block) apply_association_changes! if respond_to?(:apply_association_changes!) result = persisted? ? update_object(&block) : create_object(&block) mark_persisted! if result result end def create_object(&block) performer = block || _create_performer || _save_performer !!performer_exec(&performer) end def update_object(&block) performer = block || _update_performer || _save_performer !!performer_exec(&performer) end def destroyable? !!_destroy_performer end def destroy_object(&block) performer = block || _destroy_performer result = !!performer_exec(&performer) mark_destroyed! if result result end def performer_exec(&block) if block.arity == 1 yield(self) else instance_exec(&block) end end end end end end