lib/neo4j/rails/model.rb in neo4j-1.0.0.beta.19 vs lib/neo4j/rails/model.rb in neo4j-1.0.0.beta.20

- old
+ new

@@ -1,25 +1,31 @@ module Neo4j module Rails class Model include Neo4j::NodeMixin + include ActiveModel::Serializers::Xml include ActiveModel::Validations include ActiveModel::Dirty include ActiveModel::MassAssignmentSecurity extend ActiveModel::Naming extend ActiveModel::Callbacks - extend Neo4j::Validations::ClassMethods + + include Neo4j::Rails::Validations + extend Neo4j::Rails::Validations::ClassMethods + + include Finders # ActiveRecord style find + include Mapping::Property # allows some additional options on the #property class method + extend TxMethods + class_inheritable_hash :attribute_defaults + self.attribute_defaults = {} + define_model_callbacks :create, :save, :update, :destroy - rule :all - - UniquenessValidator = Neo4j::Validations::UniquenessValidator - class RecordInvalidError < RuntimeError attr_reader :record def initialize(record) @record = record @@ -61,35 +67,19 @@ 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) + elsif property?(method_id) + send(:[], method_id) + else + super 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 - - 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] + send(key) end def attributes=(values) sanitize_for_mass_assignment(values).each do |k, v| if respond_to?("#{k}=") @@ -157,28 +147,30 @@ else proc_or_symbol.call(attr) end end - def save + def save(*args) _run_save_callbacks do - if create_or_update_node + if create_or_update_node(*args) 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 end end - def create_or_update_node - if valid?(:save) + def create_or_update_node(options = {}) + options.reverse_merge!({ :validate => true }) + + if options[:validate] == false || valid?(:save) if new_record? _run_create_callbacks do - if valid?(:create) + if options[:validate] == false || valid?(:create) node = Neo4j::Node.new(props) return false unless _java_node.save_nested(node) init_on_load(node) init_on_create self.created_at = DateTime.now if Neo4j::Config[:timestamps] && respond_to?(:created_at) @@ -186,11 +178,11 @@ true end end else _run_update_callbacks do - if valid?(:update) + if options[:validate] == false || valid?(:update) clear_changes self.updated_at = DateTime.now if Neo4j::Config[:timestamps] && respond_to?(:updated_at) true end end @@ -206,12 +198,12 @@ def reload(options = nil) clear_changes reload_from_database or set_deleted_properties and return self end - def save! - raise RecordInvalidError.new(self) unless save + def save!(*args) + raise RecordInvalidError.new(self) unless save(*args) 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? @@ -243,11 +235,12 @@ def destroyed?() @_deleted end - tx_methods :destroy, :create_or_update_node, :update_attributes, :update_attributes! + # TODO: []= shouldn't need to be in a transaction because it shouldn't update the DB. Need to refactor the @_java_node handling stuff if we want that to be the case though + tx_methods :destroy, :create_or_update_node, :[]=, :update_attributes, :update_attributes!, :update_nested_attributes # -------------------------------------- # Class Methods # -------------------------------------- @@ -257,92 +250,61 @@ # 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.attributes = initial_attributes(*args) wrapped end - # Behave like ActiveModel - def all_with_args(*args) - if args.empty? - all_without_args - else - hits = find_without_checking_for_id(*args) - # 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_chain :all, :args - - # Handle Model.find(params[:id]) - def find_with_checking_for_id(*args) - if args.length == 1 && String === args[0] && args[0].to_i != 0 - load(*args) - else - all_with_args(*args).first - end - end - - alias_method_chain :find, :checking_for_id - - def load(*ids) - result = ids.map { |id| Neo4j::Node.load(id) } - if ids.length == 1 - result.first - else - result - end - end - alias_method :_orig_create, :create def create(*args) new(*args).tap { |o| o.save } end def create!(*args) new(*args).tap { |o| o.save! } end - tx_methods :create, :create! - def transaction(&block) - Neo4j::Rails::Transaction.run &block + Neo4j::Rails::Transaction.run do |tx| + block.call(tx) + end 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 + target_class = rel.target_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 target_class + type = rel.rel_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) + update_nested_attributes(type, target_class, true, attributes, options) else if attributes.is_a?(Array) attributes.each do |attr| - update_nested_attributes(type, to_class, false, attr, options) + update_nested_attributes(type, target_class, false, attr, options) end else attributes.each_value do |attr| - update_nested_attributes(type, to_class, false, attr, options) + update_nested_attributes(type, target_class, false, attr, options) end end end end - tx_methods("#{association_name}_attributes=") end + end + + protected + def initial_attributes(*args) + args.first.is_a?(Hash) ? args.first.reverse_merge(attribute_defaults) : attribute_defaults end end private def reload_from_database