module Neo4j
module Wrapper
module Property
module ClassMethods
# @return [Hash] a hash of all properties and its configuration defined by property class method
def _decl_props
@_decl_props ||= {}
end
# make sure the inherited classes inherit the _decl_props hash
def inherited(klass)
klass.instance_variable_set(:@_decl_props, _decl_props.clone)
super
end
# Generates accessor method and sets configuration for Neo4j node properties.
# The generated accessor creates a wrapper around the #[] and #[]= operators and
# support conversion of values before it is read or written to the database, using the :type config parameter.
# If a property is set to nil the property will be removed (just like the #[]=accessor])
# A lucene index can also be specified, using the :index config parameter.
# If you want to add index on a none declared property you can (still) use the index class method, see
# {Neo4j::Core::Index::Indexer}[http://rdoc.info/github/andreasronge/neo4j-core/master/Neo4j/Core/Index/Indexer]
#
# === :type
# A property can be of any primitive type (Boolean, String, Fixnum, Float)
# and does not even have to be the same. Arrays of primitive types is also supported. Array values must
# be of the same type and are mutable, e.g. you have to create a new array if you want to change one value.
# To make sure a property is always stored as the same type you can use the TypeConverter, which is specified using
# either the :type or the :converter parameter. This is also important when indexing, e.g finding a
# property which has been declared as a Fixnum but queried as a String will not work.
#
# === :converter
# You can implement your own converter, see Neo4j::TypeConverters.
# You can write your own converter by writing a class that respond to :index_as, :to_ruby and
# :to_java.
#
# === :index
# If a :index is specified the property will be automatically indexed.
# By default, there are two different indices available: :fulltext and :exact
# If not specified in a query it will use by default an :exact index.
# Notice, that you can't combine a fulltext and exact lucene query in the same query.
# If you are using fulltext index you must specify the type parameter in the find method
# (:exact is default).
# Also, if you want to sort or do range query make sure use the same query parameters as the declared type
# For example if you index with property :age, :index => :exact, :type => Fixnum) then you must query
# with age parameter being a fixnum, see examples below.
#
# @example a property with an index
#
# class Foo
# include Neo4j::NodeMixin
# property :description, :index => :fulltext
# end
#
# @example Lucene Query
# Foo.find("description: bla", :type => :fulltext)
# Foo.find({:description => "bla"}, {:type => :fulltext})
#
# @example Lucene Query with a Fixnum index
# Foo.find(:age => 35)
# Foo.find(:age => (32..37))
# Foo.find("age: 35") # does not work !
#
# === More Examples
#
# @example declare a property
# class Foo
# include Neo4j::NodeMixin
# property :age
# end
#
# @example use a declared property
# foo = Foo.new
# foo.age = "hej" # first set it to string
# foo.age = 42 # change it to a Fixnum
#
# However, you can specify an type for the index, see Neo4j::Index::Indexer#index
#
# @example conversion of none primitive types
#
# class Foo
# include Neo4j::NodeMixin
# property :since, :type => DateTime # will be converted into a fixnum
# end
#
# @example declare several properties in one line
#
# class Foo
# include Neo4j::NodeMixin
# property :name, :description, :type => String, :index => :exact
# end
#
# @param [Symbol, Hash] props one or more properties and optionally last a Hash for options
# @see Neo4j::TypeConverters
# @see http://rdoc.info/github/andreasronge/neo4j-core/master/Neo4j/Core/Index/LuceneQuery Neo4j::Core::Index::LuceneQuery
# @see http://rdoc.info/github/andreasronge/neo4j-core/master/Neo4j/Core/Index/Indexer Neo4j::Core::Index::Indexer
def property(*props)
options = props.last.kind_of?(Hash) ? props.pop : {}
props.uniq.each do |prop|
pname = prop.to_sym
_decl_props[pname] ||= {}
options.each { |key, value| _decl_props[pname][key] = value }
converter = options[:converter] || Neo4j::TypeConverters.converter(_decl_props[pname][:type])
_decl_props[pname][:converter] = converter
if options.include?(:index)
index(pname, :type => options[:index], :field_type => converter.index_as)
end
define_method(pname) do
self.class._converter(pname).to_ruby(self[pname])
end
name = (pname.to_s() +"=").to_sym
define_method(name) do |value|
self[pname] = self.class._converter(pname).to_java(value)
end
end
end
# @param [String, Symbol] prop_name the name of the property
# @return [#to_java, #to_ruby, #field_type] a converter for the given property name
# @note if the property has not been defined it will return the DefaultConverter
# @see Neo4j::TypeConverters::DefaultConverter
def _converter(prop_name)
prop_conf = _decl_props[prop_name.to_sym]
Neo4j::TypeConverters::DefaultConverter unless prop_conf
prop_conf[:converter]
end
# Returns true if the given property name has been defined with the class
# method property or properties.
#
# Notice that the node may have properties that has not been declared.
# It is always possible to set an undeclared property on a node.
#
# @return [true, false]
def property?(prop_name)
return false if _decl_props[prop_name.to_sym].nil?
!_decl_props[prop_name.to_sym].nil?
end
end
end
end
end