lib/neo4j/active_node/property.rb in neo4j-3.0.0.alpha.8 vs lib/neo4j/active_node/property.rb in neo4j-3.0.0.alpha.9

- old
+ new

@@ -7,38 +7,50 @@ include ActiveAttr::TypecastedAttributes include ActiveAttr::AttributeDefaults include ActiveAttr::QueryAttributes include ActiveModel::Dirty - class UndefinedPropertyError < RuntimeError - end + class UndefinedPropertyError < RuntimeError; end + class MultiparameterAssignmentError < StandardError; end def initialize(attributes={}, options={}) - relationship_props = self.class.extract_relationship_attributes!(attributes) + attributes = process_attributes(attributes) + writer_method_props = extract_writer_methods!(attributes) validate_attributes!(attributes) writer_method_props.each do |key, value| self.send("#{key}=", value) end super(attributes, options) end - def save_properties - @previously_changed = changes - changed_attributes.clear - end - - # Returning nil when we get ActiveAttr::UnknownAttributeError from ActiveAttr def read_attribute(name) super(name) rescue ActiveAttr::UnknownAttributeError nil end alias_method :[], :read_attribute + def default_properties=(properties) + keys = self.class.default_properties.keys + @default_properties = properties.reject{|key| !keys.include?(key)} + end + + def default_property(key) + keys = self.class.default_properties.keys + keys.include?(key.to_sym) ? default_properties[key.to_sym] : nil + end + + def default_properties + @default_properties ||= {} + # keys = self.class.default_properties.keys + # _persisted_node.props.reject{|key| !keys.include?(key)} + end + + private # Changes attributes hash to remove relationship keys # Raises an error if there are any keys left which haven't been defined as properties on the model def validate_attributes!(attributes) @@ -52,37 +64,128 @@ writer_method_props end end + # Gives support for Rails date_select, datetime_select, time_select helpers. + def process_attributes(attributes = nil) + multi_parameter_attributes = {} + new_attributes = {} + attributes.each_pair do |key, value| + if key =~ /\A([^\(]+)\((\d+)([if])\)$/ + found_key, index = $1, $2.to_i + (multi_parameter_attributes[found_key] ||= {})[index] = value.empty? ? nil : value.send("to_#{$3}") + else + new_attributes[key] = value + end + end + + multi_parameter_attributes.empty? ? new_attributes : process_multiparameter_attributes(multi_parameter_attributes, new_attributes) + end + + def process_multiparameter_attributes(multi_parameter_attributes, new_attributes) + multi_parameter_attributes.each_pair do |key, values| + begin + values = (values.keys.min..values.keys.max).map { |i| values[i] } + field = self.class.attributes[key.to_sym] + new_attributes[key] = instantiate_object(field, values) + rescue => e + raise MultiparameterAssignmentError, "error on assignment #{values.inspect} to #{key}" + end + end + new_attributes + end + + def instantiate_object(field, values_with_empty_parameters) + return nil if values_with_empty_parameters.all? { |v| v.nil? } + values = values_with_empty_parameters.collect { |v| v.nil? ? 1 : v } + klass = field[:type] + if klass + klass.new(*values) + else + values + end + end + module ClassMethods + # Defines a property on the class + # + # See active_attr gem for allowed options, e.g which type + # Notice, in Neo4j you don't have to declare properties before using them, see the neo4j-core api. + # + # @example Without type + # class Person + # # declare a property which can have any value + # property :name + # end + # + # @example With type and a default value + # class Person + # # declare a property which can have any value + # property :score, type: Integer, default: 0 + # end + # + # @example With an index + # class Person + # # declare a property which can have any value + # property :name, index: :exact + # end + # + # @example With a constraint + # class Person + # # declare a property which can have any value + # property :name, constraint: :unique + # end def property(name, options={}) magic_properties(name, options) - - # if (name.to_s == 'remember_created_at') - # binding.pry - # end attribute(name, options) + + # either constraint or index, do not set both + if options[:constraint] + raise "unknown constraint type #{options[:constraint]}, only :unique supported" if options[:constraint] != :unique + constraint(name, type: :unique) + elsif options[:index] + raise "unknown index type #{options[:index]}, only :exact supported" if options[:index] != :exact + index(name, options) if options[:index] == :exact + end end - #overrides ActiveAttr's attribute! method + def default_property(name, &block) + default_properties[name] = block + end + + # @return [Hash<Symbol,Proc>] + def default_properties + @default_property ||= {} + end + + def default_property_values(instance) + default_properties.inject({}) do |result,pair| + result.tap{|obj| obj[pair[0]] = pair[1].call(instance)} + end + end + def attribute!(name, options={}) super(name, options) define_method("#{name}=") do |value| typecast_value = typecast_attribute(typecaster_for(self.class._attribute_type(name)), value) send("#{name}_will_change!") unless typecast_value == read_attribute(name) super(value) end end + def cached_class? + !!Neo4j::Config[:cache_class_names] + end + # Extracts keys from attributes hash which are relationships of the model # TODO: Validate separately that relationships are getting the right values? Perhaps also store the values and persist relationships on save? - def extract_relationship_attributes!(attributes) - attributes.keys.inject({}) do |relationship_props, key| - relationship_props[key] = attributes.delete(key) if self.has_relationship?(key) + def extract_association_attributes!(attributes) + attributes.keys.inject({}) do |association_props, key| + association_props[key] = attributes.delete(key) if self.has_association?(key) - relationship_props + association_props end end private