lib/neo4j/rails/model.rb in neo4j-1.0.0.beta.11 vs lib/neo4j/rails/model.rb in neo4j-1.0.0.beta.12

- old
+ new

@@ -1,358 +1,343 @@ -class Neo4j::Model - include Neo4j::NodeMixin - include ActiveModel::Validations - include ActiveModel::Dirty - include ActiveModel::MassAssignmentSecurity +module Neo4j + module Rails + class Model + include Neo4j::NodeMixin + include ActiveModel::Validations + include ActiveModel::Dirty + include ActiveModel::MassAssignmentSecurity - extend ActiveModel::Naming - extend ActiveModel::Callbacks - extend Neo4j::Validations::ClassMethods - extend Neo4j::TxMethods + extend ActiveModel::Naming + extend ActiveModel::Callbacks + extend Neo4j::Validations::ClassMethods + extend TxMethods +# extend ClassMethods::Relationship - define_model_callbacks :create, :save, :update, :destroy + define_model_callbacks :create, :save, :update, :destroy - UniquenessValidator = Neo4j::Validations::UniquenessValidator + UniquenessValidator = Neo4j::Validations::UniquenessValidator - class RecordInvalidError < RuntimeError - attr_reader :record + class RecordInvalidError < RuntimeError + attr_reader :record - def initialize(record) - @record = record - super(@record.errors.full_messages.join(", ")) - end - end + def initialize(record) + @record = record + super(@record.errors.full_messages.join(", ")) + end + end - # -------------------------------------- - # Initialize - # -------------------------------------- + # -------------------------------------- + # Initialize + # -------------------------------------- - def initialize(*) - end + def initialize(*) + end - def init_on_create(*args) # :nodoc: - super() - self.attributes=args[0] if args[0].respond_to?(:each_pair) - @_created_record = true - end + def init_on_create(*args) # :nodoc: + super() + self.attributes=args[0] if args[0].respond_to?(:each_pair) + @_created_record = true + end - # -------------------------------------- - # Identity - # -------------------------------------- + # -------------------------------------- + # Identity + # -------------------------------------- - def id - neo_id.nil? ? nil : neo_id.to_s - end + def id + neo_id.nil? ? nil : neo_id.to_s + end - def to_param - persisted? ? neo_id.to_s : nil - end + def to_param + persisted? ? neo_id.to_s : nil + end - # Returns an Enumerable of all (primary) key attributes - # or nil if model.persisted? is false - def to_key - persisted? ? [:id] : nil - end + # Returns an Enumerable of all (primary) key attributes + # or nil if model.persisted? is false + def to_key + persisted? ? [:id] : nil + end - # enables ActiveModel::Dirty and Validation - def method_missing(method_id, *args, &block) - if !self.class.attribute_methods_generated? - self.class.define_attribute_methods(self.class._decl_props.keys) - # try again - send(method_id, *args, &block) - end - end + # enables ActiveModel::Dirty and Validation + def method_missing(method_id, *args, &block) + if !self.class.attribute_methods_generated? + self.class.define_attribute_methods(self.class._decl_props.keys) + # try again + send(method_id, *args, &block) + end + end - # redefine this methods so that ActiveModel::Dirty will work - def []=(key, new_value) - key = key.to_s - unless key[0] == ?_ - old_value = self.send(:[], key) - attribute_will_change!(key) unless old_value == new_value - end - Neo4j::Rails::Transaction.running? ? super : Neo4j::Rails::Transaction.run { super } - end + # redefine this methods so that ActiveModel::Dirty will work + def []=(key, new_value) + key = key.to_s + unless key[0] == ?_ + old_value = self.send(:[], key) + attribute_will_change!(key) unless old_value == new_value + end + Neo4j::Rails::Transaction.running? ? super : Neo4j::Rails::Transaction.run { super } + end - def attribute_will_change!(attr) - begin - value = __send__(:[], attr) - value = value.duplicable? ? value.clone : value - rescue TypeError, NoMethodError - end - changed_attributes[attr] = value - end + def attribute_will_change!(attr) + begin + value = __send__(:[], attr) + value = value.duplicable? ? value.clone : value + rescue TypeError, NoMethodError + end + changed_attributes[attr] = value + end - def read_attribute_for_validation(key) - respond_to?(key) ? send(key) : self[key] - end + def read_attribute_for_validation(key) + respond_to?(key) ? send(key) : self[key] + end - def attributes=(values) - sanitize_for_mass_assignment(values).each do |k, v| - if respond_to?("#{k}=") - send("#{k}=", v) - else - self[k] = v + def attributes=(values) + sanitize_for_mass_assignment(values).each do |k, v| + if respond_to?("#{k}=") + send("#{k}=", v) + else + self[k] = v + end + end end - end - end - # Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved. - # If saving fails because the resource is invalid then false will be returned. - def update_attributes(attributes) - self.attributes=attributes - save - end + # Updates this resource with all the attributes from the passed-in Hash and requests that the record be saved. + # If saving fails because the resource is invalid then false will be returned. + def update_attributes(attributes) + self.attributes=attributes + save + end - def update_attributes!(attributes) - self.attributes=attributes - save! - end + def update_attributes!(attributes) + self.attributes=attributes + save! + end - def update_nested_attributes(rel_type, clazz, has_one, attr, options) - allow_destroy,reject_if = [options[:allow_destroy], options[:reject_if]] if options + def update_nested_attributes(rel_type, clazz, has_one, attr, options) + allow_destroy, reject_if = [options[:allow_destroy], options[:reject_if]] if options - if new? - # We are updating a node that was created with the 'new' method. - # The relationship will only be kept in the Value object. - outgoing(rel_type)<<clazz.new(attr) unless reject_if?(reject_if,attr) - else - # We have a node that was created with the #create method - has real Neo4j relationships - # does it exist ? - found = if has_one - # id == nil that means we have a has_one relationship - outgoing(rel_type).first - else - # do we have an ID ? - id = attr[:id] - # this is a has_n relationship, find which one we want to update - id && outgoing(rel_type).find { |n| n.id == id } - end + if new? + # We are updating a node that was created with the 'new' method. + # The relationship will only be kept in the Value object. + outgoing(rel_type)<<clazz.new(attr) unless reject_if?(reject_if, attr) + else + # We have a node that was created with the #create method - has real Neo4j relationships + # does it exist ? + found = if has_one + # id == nil that means we have a has_one relationship + outgoing(rel_type).first + else + # do we have an ID ? + id = attr[:id] + # this is a has_n relationship, find which one we want to update + id && outgoing(rel_type).find { |n| n.id == id } + end - # Check if we want to destroy not found nodes (e.g. {..., :_destroy => '1' } ? - destroy = attr[:_destroy] && attr[:_destroy] != '0' + # Check if we want to destroy not found nodes (e.g. {..., :_destroy => '1' } ? + destroy = attr[:_destroy] && attr[:_destroy] != '0' - if found - if destroy - found.destroy if allow_destroy + puts "FOUND #{found}" + if found + if destroy + found.destroy if allow_destroy + else + found.update_attributes_in_tx(attr) # it already exist, so update that one + end + elsif !destroy && !reject_if?(reject_if, attr) + new_node = clazz.new(attr) + saved = new_node.save + outgoing(rel_type) << new_node if saved + end + end + end + + def reject_if?(proc_or_symbol, attr) + return false if proc_or_symbol.nil? + if proc_or_symbol.is_a?(Symbol) + meth = method(proc_or_symbol) + meth.arity == 0 ? meth.call : meth.call(attr) else - found.update_attributes_in_tx(attr) # it already exist, so update that one + proc_or_symbol.call(attr) end - elsif !destroy && !reject_if?(reject_if,attr) - new_node = clazz.new(attr) - saved = new_node.save - outgoing(rel_type) << new_node if saved end - end - end - def reject_if?(proc_or_symbol, attr) - return false if proc_or_symbol.nil? - if proc_or_symbol.is_a?(Symbol) - meth = method(proc_or_symbol) - meth.arity == 0 ? meth.call : meth.call(attr) - else - proc_or_symbol.call(attr) - end - end + def delete + super + @_deleted = true + @_persisted = false + end - def delete - super - @_deleted = true - @_persisted = false - end + def save + valid = valid? + if valid + # if we are trying to save a value then we should create a real node + valid = _run_save_callbacks { create_or_update_node } + @_created_record = false + true + else + # if not valid we should rollback the transaction so that the changes does not take place. + # no point failing the transaction if we have created a model with 'new' + Neo4j::Rails::Transaction.fail if Neo4j::Rails::Transaction.running? && !_java_node.kind_of?(Neo4j::Rails::Value) + false + end + valid + end - def save - valid = valid? - if valid - # if we are trying to save a value then we should create a real node - valid = _run_save_callbacks { create_or_update_node } - @_created_record = false - true - else - # if not valid we should rollback the transaction so that the changes does not take place. - # no point failing the transaction if we have created a model with 'new' - Neo4j::Rails::Transaction.fail if Neo4j::Rails::Transaction.running? && !_java_node.kind_of?(Neo4j::Value) - false - end - valid - end + def create_or_update_node + valid = true + if _java_node.kind_of?(Neo4j::Rails::Value) + node = Neo4j::Node.new(props) + valid = _java_node.save_nested(node) + init_on_load(node) + init_on_create + end - def create_or_update_node - valid = true - if _java_node.kind_of?(Neo4j::Value) - node = Neo4j::Node.new(props) - valid = _java_node.save_nested(node) - init_on_load(node) - init_on_create - end + if new_record? + _run_create_callbacks { clear_changes } + else + _run_update_callbacks { clear_changes } + end + valid + end - if new_record? - _run_create_callbacks { clear_changes } - else - _run_update_callbacks { clear_changes } - end - valid - end + def clear_changes + @previously_changed = changes + @changed_attributes.clear + end - def clear_changes - @previously_changed = changes - @changed_attributes.clear - end + def save! + raise RecordInvalidError.new(self) unless save + end - def save! - raise RecordInvalidError.new(self) unless save - end + # Returns if the record is persisted, i.e. it’s not a new record and it was not destroyed + def persisted? + !new_record? && !destroyed? + end - # Returns if the record is persisted, i.e. it’s not a new record and it was not destroyed - def persisted? - !new_record? && !destroyed? - end + def to_model + self + end - def to_model - self - end + # Returns true if this object hasn’t been saved yet — that is, a record for the object doesn’t exist yet; otherwise, returns false. + def new_record?() + # it is new if the model has been created with either the new or create method + new? || @_created_record == true + end - # Returns true if this object hasn’t been saved yet — that is, a record for the object doesn’t exist yet; otherwise, returns false. - def new_record?() - # it is new if the model has been created with either the new or create method - new? || @_created_record == true - end + def new? + _java_node.kind_of?(Neo4j::Rails::Value) + end - def new? - _java_node.kind_of?(Neo4j::Value) - end + def del + @_deleted = true + super + end - def del - @_deleted = true - super - end + def destroy + _run_update_callbacks { del } + end - def destroy - _run_update_callbacks { del } - end + def destroyed?() + @_deleted + end - def destroyed?() - @_deleted - end + tx_methods :destroy, :create_or_update_node, :update_attributes, :update_attributes! - tx_methods :destroy, :create_or_update_node, :update_attributes, :update_attributes! + # -------------------------------------- + # Class Methods + # -------------------------------------- - # -------------------------------------- - # Class Methods - # -------------------------------------- + class << self + extend TxMethods - class << self - extend Neo4j::TxMethods + # returns a value object instead of creating a new node + def new(*args) + wrapped = self.orig_new + value = Neo4j::Rails::Value.new(wrapped) + wrapped.init_on_load(value) + wrapped.attributes=args[0] if args[0].respond_to?(:each_pair) + wrapped + end - # returns a value object instead of creating a new node - def new(*args) - value = Neo4j::Value.new - wrapped = self.orig_new - wrapped.init_on_load(value) - wrapped.attributes=args[0] if args[0].respond_to?(:each_pair) - - wrapped.class._decl_rels.each_pair do |field, dsl| - - meta = class << wrapped; - self; + # Handle Model.find(params[:id]) + def find(*args) + if args.length == 1 && String === args[0] && args[0].to_i != 0 + load(*args) + else + hits = super + # We need to save this so that the Rack Neo4j::Rails:LuceneConnection::Closer can close it + Thread.current[:neo4j_lucene_connection] ||= [] + Thread.current[:neo4j_lucene_connection] << hits + hits + end end - wrapped.class._decl_rels.each_pair do |field, dsl| - meta.send(:define_method, field) do - if new? - value.outgoing(dsl.namespace_type) - else - self.outgoing(dsl.namespace_type) - end - end if dsl.direction == :outgoing - - meta.send(:define_method, field) do - raise "NOT IMPLEMENTED #{field} (incoming relationship) FOR #new method, please create a new model with the create method instead" - end if dsl.direction == :incoming + def load(*ids) + result = ids.map { |id| Neo4j::Node.load(id) } + if ids.length == 1 + result.first + else + result + end end - end - wrapped - end - # Handle Model.find(params[:id]) - def find(*args) - if args.length == 1 && String === args[0] && args[0].to_i != 0 - load(*args) - else - hits = super - # We need to save this so that the Rack Neo4j::Rails:LuceneConnection::Closer can close it - Thread.current[:neo4j_lucene_connection] ||= [] - Thread.current[:neo4j_lucene_connection] << hits - hits - end - end + alias_method :_orig_create, :create - def load(*ids) - result = ids.map { |id| Neo4j::Node.load(id) } - if ids.length == 1 - result.first - else - result - end - end + def create(*) + model = super + model.save + model + end + def create!(*args) + model = _orig_create(*args) + model.save! + model + end - alias_method :_orig_create, :create + tx_methods :create, :create! - def create(*) - model = super - model.save - model - end - def create!(*args) - model = _orig_create(*args) - model.save! - model - end + def transaction(&block) + Neo4j::Rails::Transaction.run &block + end - tx_methods :create, :create! + def accepts_nested_attributes_for(*attr_names) + options = attr_names.pop if attr_names[-1].is_a?(Hash) + attr_names.each do |association_name| + rel = self._decl_rels[association_name.to_sym] + raise "No relationship declared with has_n or has_one with type #{association_name}" unless rel + to_class = rel.to_class + raise "Can't use accepts_nested_attributes_for(#{association_name}) since it has not defined which class it has a relationship to, use has_n(#{association_name}).to(MyOtherClass)" unless to_class + type = rel.namespace_type + has_one = rel.has_one? - def transaction(&block) - Neo4j::Rails::Transaction.run &block - end - - def accepts_nested_attributes_for(*attr_names) - options = attr_names.pop if attr_names[-1].is_a?(Hash) - - attr_names.each do |association_name| - rel = self._decl_rels[association_name.to_sym] - raise "No relationship declared with has_n or has_one with type #{association_name}" unless rel - to_class = rel.to_class - raise "Can't use accepts_nested_attributes_for(#{association_name}) since it has not defined which class it has a relationship to, use has_n(#{association_name}).to(MyOtherClass)" unless to_class - type = rel.namespace_type - has_one = rel.has_one? - - send(:define_method, "#{association_name}_attributes=") do |attributes| - if has_one - update_nested_attributes(type, to_class, true, attributes, options) - else - if attributes.is_a?(Array) - attributes.each do |attr| - update_nested_attributes(type, to_class, false, attr, options) + send(:define_method, "#{association_name}_attributes=") do |attributes| + if has_one + update_nested_attributes(type, to_class, true, attributes, options) + else + if attributes.is_a?(Array) + attributes.each do |attr| + update_nested_attributes(type, to_class, false, attr, options) + end + else + attributes.each_value do |attr| + update_nested_attributes(type, to_class, false, attr, options) + end + end end - else - attributes.each_value do |attr| - update_nested_attributes(type, to_class, false, attr, options) - end end + tx_methods("#{association_name}_attributes=") end end - tx_methods("#{association_name}_attributes=") + end + end end - end