neo4j 1.0.0.beta.21-java
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +141 -0
- data/CONTRIBUTORS +17 -0
- data/Gemfile +16 -0
- data/README.rdoc +135 -0
- data/lib/generators/neo4j.rb +65 -0
- data/lib/generators/neo4j/model/model_generator.rb +39 -0
- data/lib/generators/neo4j/model/templates/model.erb +7 -0
- data/lib/neo4j.rb +77 -0
- data/lib/neo4j/config.rb +153 -0
- data/lib/neo4j/database.rb +56 -0
- data/lib/neo4j/equal.rb +21 -0
- data/lib/neo4j/event_handler.rb +116 -0
- data/lib/neo4j/index/class_methods.rb +62 -0
- data/lib/neo4j/index/index.rb +33 -0
- data/lib/neo4j/index/indexer.rb +312 -0
- data/lib/neo4j/index/indexer_registry.rb +68 -0
- data/lib/neo4j/index/lucene_query.rb +191 -0
- data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
- data/lib/neo4j/jars/lucene-core-3.0.2.jar +0 -0
- data/lib/neo4j/jars/neo4j-index-1.2-1.2.M03.jar +0 -0
- data/lib/neo4j/jars/neo4j-kernel-1.2-1.2.M03.jar +0 -0
- data/lib/neo4j/jars/neo4j-lucene-index-0.2-1.2.M03.jar +0 -0
- data/lib/neo4j/load.rb +21 -0
- data/lib/neo4j/mapping/class_methods/init_node.rb +50 -0
- data/lib/neo4j/mapping/class_methods/init_rel.rb +35 -0
- data/lib/neo4j/mapping/class_methods/list.rb +13 -0
- data/lib/neo4j/mapping/class_methods/property.rb +82 -0
- data/lib/neo4j/mapping/class_methods/relationship.rb +91 -0
- data/lib/neo4j/mapping/class_methods/rule.rb +295 -0
- data/lib/neo4j/mapping/decl_relationship_dsl.rb +214 -0
- data/lib/neo4j/mapping/has_list.rb +134 -0
- data/lib/neo4j/mapping/has_n.rb +83 -0
- data/lib/neo4j/mapping/node_mixin.rb +112 -0
- data/lib/neo4j/mapping/relationship_mixin.rb +120 -0
- data/lib/neo4j/model.rb +4 -0
- data/lib/neo4j/neo4j.rb +95 -0
- data/lib/neo4j/node.rb +131 -0
- data/lib/neo4j/node_mixin.rb +4 -0
- data/lib/neo4j/node_relationship.rb +149 -0
- data/lib/neo4j/node_traverser.rb +157 -0
- data/lib/neo4j/property.rb +111 -0
- data/lib/neo4j/rails/attributes.rb +155 -0
- data/lib/neo4j/rails/callbacks.rb +34 -0
- data/lib/neo4j/rails/finders.rb +134 -0
- data/lib/neo4j/rails/lucene_connection_closer.rb +19 -0
- data/lib/neo4j/rails/mapping/property.rb +60 -0
- data/lib/neo4j/rails/model.rb +105 -0
- data/lib/neo4j/rails/persistence.rb +260 -0
- data/lib/neo4j/rails/railtie.rb +21 -0
- data/lib/neo4j/rails/relationships/mapper.rb +96 -0
- data/lib/neo4j/rails/relationships/relationship.rb +30 -0
- data/lib/neo4j/rails/relationships/relationships.rb +60 -0
- data/lib/neo4j/rails/serialization.rb +25 -0
- data/lib/neo4j/rails/timestamps.rb +65 -0
- data/lib/neo4j/rails/transaction.rb +67 -0
- data/lib/neo4j/rails/tx_methods.rb +15 -0
- data/lib/neo4j/rails/validations.rb +38 -0
- data/lib/neo4j/rails/validations/non_nil.rb +11 -0
- data/lib/neo4j/rails/validations/uniqueness.rb +37 -0
- data/lib/neo4j/relationship.rb +169 -0
- data/lib/neo4j/relationship_mixin.rb +4 -0
- data/lib/neo4j/relationship_traverser.rb +92 -0
- data/lib/neo4j/to_java.rb +31 -0
- data/lib/neo4j/transaction.rb +68 -0
- data/lib/neo4j/type_converters.rb +117 -0
- data/lib/neo4j/version.rb +3 -0
- data/lib/orm_adapter/adapters/neo4j.rb +55 -0
- data/lib/tmp/neo4j/active_tx_log +1 -0
- data/lib/tmp/neo4j/index/lucene-store.db +0 -0
- data/lib/tmp/neo4j/index/lucene.log.active +0 -0
- data/lib/tmp/neo4j/lucene-fulltext/lucene-store.db +0 -0
- data/lib/tmp/neo4j/lucene-fulltext/lucene.log.active +0 -0
- data/lib/tmp/neo4j/lucene/lucene-store.db +0 -0
- data/lib/tmp/neo4j/lucene/lucene.log.active +0 -0
- data/lib/tmp/neo4j/messages.log +85 -0
- data/lib/tmp/neo4j/neostore +0 -0
- data/lib/tmp/neo4j/neostore.id +0 -0
- data/lib/tmp/neo4j/neostore.nodestore.db +0 -0
- data/lib/tmp/neo4j/neostore.nodestore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.arrays +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.arrays.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index.keys +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.index.keys.id +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.strings +0 -0
- data/lib/tmp/neo4j/neostore.propertystore.db.strings.id +0 -0
- data/lib/tmp/neo4j/neostore.relationshipstore.db +0 -0
- data/lib/tmp/neo4j/neostore.relationshipstore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db.id +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names +0 -0
- data/lib/tmp/neo4j/neostore.relationshiptypestore.db.names.id +0 -0
- data/lib/tmp/neo4j/nioneo_logical.log.active +0 -0
- data/lib/tmp/neo4j/tm_tx_log.1 +0 -0
- data/neo4j.gemspec +31 -0
- metadata +216 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
module Neo4j
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
config.neo4j = ActiveSupport::OrderedOptions.new
|
4
|
+
|
5
|
+
initializer "neo4j.tx" do |app|
|
6
|
+
app.config.middleware.use Neo4j::Rails::LuceneConnectionCloser
|
7
|
+
end
|
8
|
+
|
9
|
+
# Add ActiveModel translations to the I18n load_path
|
10
|
+
initializer "i18n" do |app|
|
11
|
+
config.i18n.load_path += Dir[File.join(File.dirname(__FILE__), '..', '..', '..', 'config', 'locales', '*.{rb,yml}')]
|
12
|
+
end
|
13
|
+
|
14
|
+
# Starting Neo after :load_config_initializers allows apps to
|
15
|
+
# register migrations in config/initializers
|
16
|
+
initializer "neo4j.start", :after => :load_config_initializers do |app|
|
17
|
+
Neo4j::Config.setup.merge!(app.config.neo4j.to_hash)
|
18
|
+
Neo4j.start
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Rails
|
3
|
+
module Relationships
|
4
|
+
|
5
|
+
class Mapper
|
6
|
+
def initialize(rel_type, dsl=nil)
|
7
|
+
@rel_type = rel_type
|
8
|
+
@relationships = []
|
9
|
+
@dsl = dsl
|
10
|
+
end
|
11
|
+
|
12
|
+
def rel_type
|
13
|
+
(@dsl && @dsl.rel_type) || @rel_type
|
14
|
+
end
|
15
|
+
|
16
|
+
def direction
|
17
|
+
(@dsl && @dsl.direction) || :outgoing
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
"#{self.class} #{object_id} dir: #{direction} rel_type: #{@rel_type} wrapped #{@dsl}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def single_relationship(*)
|
25
|
+
@relationships.first
|
26
|
+
end
|
27
|
+
|
28
|
+
def each(&block)
|
29
|
+
# TODO direction
|
30
|
+
# TODO DRY
|
31
|
+
@relationships.each do |rel|
|
32
|
+
block.call rel.end_node
|
33
|
+
end
|
34
|
+
@dsl.each(&block) if @dsl
|
35
|
+
end
|
36
|
+
|
37
|
+
def each_node(node, direction, &block) #:nodoc:
|
38
|
+
# TODO direction
|
39
|
+
@relationships.each do |rel|
|
40
|
+
block.call rel.end_node
|
41
|
+
end
|
42
|
+
@dsl.each_node(node, direction, &block) if @dsl && node.persisted?
|
43
|
+
end
|
44
|
+
|
45
|
+
def del_rel(rel)
|
46
|
+
# TODO, we need to delete this when it is saved
|
47
|
+
@relationships.delete(rel)
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_relationship_to(from, to)
|
51
|
+
#rel_type = (@dsl && @dsl.rel_type) || @rel_type
|
52
|
+
#from, to = (@dsl && @dsl.incoming?) ? [to, from] : [from, to]
|
53
|
+
@relationships << Relationship.new(@rel_type, from, to, self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def single_node(from)
|
57
|
+
if !@relationships.empty?
|
58
|
+
@relationships.first.end_node
|
59
|
+
else
|
60
|
+
@dsl.single_node(from) if @dsl && from.persisted?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def valid?
|
65
|
+
all_valid = true
|
66
|
+
!@relationships.each do |rel|
|
67
|
+
start_node = rel.start_node
|
68
|
+
end_node = rel.end_node
|
69
|
+
|
70
|
+
if !end_node.valid?
|
71
|
+
all_valid = false
|
72
|
+
start_node.errors[@rel_type.to_sym] ||= []
|
73
|
+
start_node.errors[@rel_type.to_sym] << end_node.errors
|
74
|
+
end
|
75
|
+
end
|
76
|
+
all_valid
|
77
|
+
end
|
78
|
+
|
79
|
+
def persist
|
80
|
+
@relationships.each do |rel|
|
81
|
+
if @dsl
|
82
|
+
start_node = rel.start_node
|
83
|
+
end_node = rel.end_node
|
84
|
+
|
85
|
+
end_node.save!
|
86
|
+
@dsl.create_relationship_to(start_node, end_node)
|
87
|
+
else
|
88
|
+
rel.end_node.save!
|
89
|
+
rel.start_node.outgoing(@rel_type) << rel.end_node
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Rails
|
3
|
+
module Relationships
|
4
|
+
|
5
|
+
class Relationship
|
6
|
+
attr_reader :rel_type, :start_node, :end_node
|
7
|
+
|
8
|
+
|
9
|
+
def initialize(rel_type, start_node, end_node, decl)
|
10
|
+
@rel_type = rel_type
|
11
|
+
@start_node = start_node
|
12
|
+
@end_node = end_node
|
13
|
+
@decl = decl
|
14
|
+
end
|
15
|
+
|
16
|
+
def del
|
17
|
+
@decl.del_rel(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def persist
|
21
|
+
@start_node._java
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"Rel [#{rel_type} start #{start_node} end #{end_node}]"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Rails
|
3
|
+
module Relationships
|
4
|
+
|
5
|
+
# TODO, reuse for incoming relationships ?
|
6
|
+
class OutgoingRelationship
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(from_node, mapper)
|
10
|
+
@from_node = from_node
|
11
|
+
@mapper = mapper
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(other)
|
15
|
+
@mapper.create_relationship_to(@from_node, other)
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(&block)
|
19
|
+
# TODO Direction
|
20
|
+
@mapper.each &block
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def write_changed_relationships
|
26
|
+
@relationships.each_value do |mapper|
|
27
|
+
mapper.persist
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid_relationships?
|
32
|
+
!@relationships.values.find {|mapper| !mapper.valid?}
|
33
|
+
end
|
34
|
+
|
35
|
+
def _decl_rels_for(type)
|
36
|
+
dsl = super
|
37
|
+
if false && persisted?
|
38
|
+
dsl
|
39
|
+
else
|
40
|
+
@relationships[type] ||= Mapper.new(type, dsl)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def clear_relationships
|
46
|
+
@relationships = {}
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def outgoing(rel_type)
|
51
|
+
if persisted?
|
52
|
+
super
|
53
|
+
else
|
54
|
+
@relationships[rel_type] ||= Mapper.new(rel_type)
|
55
|
+
OutgoingRelationship.new(self, @relationships[rel_type])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Rails
|
3
|
+
module Serialization
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveModel::Serializers::Xml
|
8
|
+
|
9
|
+
# Patch for ActiveModel's XML serializer. There is a bug in the original where
|
10
|
+
# raw_value is used in the initializer and so demands always that the object being
|
11
|
+
# serialized is sent the attribute's name as a method call. This causes a problem
|
12
|
+
# for Neo4j properties that aren't declared and so don't have methods to call. Besides
|
13
|
+
# which it's not necessary to re-call to get the value again if it has already
|
14
|
+
# been passed.
|
15
|
+
class ActiveModel::Serializers::Xml::Serializer::Attribute
|
16
|
+
def initialize(name, serializable, raw_value=nil)
|
17
|
+
@name, @serializable = name, serializable
|
18
|
+
@value = raw_value || @serializable.send(name)
|
19
|
+
@type = compute_type
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# Handle all the created_at, updated_at, created_on, updated_on type stuff.
|
2
|
+
module Neo4j
|
3
|
+
module Rails
|
4
|
+
module Timestamps
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
TIMESTAMP_PROPERTIES = [ :created_at, :created_on, :updated_at, :updated_on ]
|
8
|
+
|
9
|
+
def write_changed_attributes
|
10
|
+
update_timestamp
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def init_on_create(*args)
|
15
|
+
create_timestamp
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Set the timestamps for this model if timestamps is set to true in the config
|
20
|
+
# and the model is set up with the correct property name, e.g.:
|
21
|
+
#
|
22
|
+
# class Trackable < Neo4j::Rails::Model
|
23
|
+
# property :updated_at, :type => DateTime
|
24
|
+
# end
|
25
|
+
def update_timestamp
|
26
|
+
write_date_or_timestamp(:updated_at) if Neo4j::Config[:timestamps] && respond_to?(:updated_at)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set the timestamps for this model if timestamps is set to true in the config
|
30
|
+
# and the model is set up with the correct property name, e.g.:
|
31
|
+
#
|
32
|
+
# class Trackable < Neo4j::Rails::Model
|
33
|
+
# property :created_at, :type => DateTime
|
34
|
+
# end
|
35
|
+
def create_timestamp
|
36
|
+
write_date_or_timestamp(:created_at) if Neo4j::Config[:timestamps] && respond_to?(:created_at)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Write the timestamp as a Date, DateTime or Time depending on the property type
|
40
|
+
def write_date_or_timestamp(attribute)
|
41
|
+
value = case self.class._decl_props[attribute][:type].to_s
|
42
|
+
when "DateTime"
|
43
|
+
DateTime.now
|
44
|
+
when "Date"
|
45
|
+
Date.today
|
46
|
+
when "Time"
|
47
|
+
Time.now
|
48
|
+
end
|
49
|
+
|
50
|
+
send("#{attribute}=", value)
|
51
|
+
end
|
52
|
+
|
53
|
+
module ClassMethods
|
54
|
+
def property_setup(property, options)
|
55
|
+
super
|
56
|
+
|
57
|
+
# ensure there's always a type on the timestamp properties
|
58
|
+
if Neo4j::Config[:timestamps] && TIMESTAMP_PROPERTIES.include?(property)
|
59
|
+
_decl_props[property][:type] = Time
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Rails
|
3
|
+
|
4
|
+
# This method is typically used in an Rails ActionController as a filter method.
|
5
|
+
# It will guarantee that an transaction is create before your action is called,
|
6
|
+
# and that the transaction is finished after your action is called.
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
# class MyController < ActionController::Base
|
10
|
+
# around_filter Neo4j::Rails::Transaction, :only => [:edit, :delete]
|
11
|
+
# ...
|
12
|
+
# end
|
13
|
+
class Transaction
|
14
|
+
class << self
|
15
|
+
def new
|
16
|
+
finish if Thread.current[:neo4j_transaction]
|
17
|
+
Thread.current[:neo4j_transaction] = Neo4j::Transaction.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def current
|
21
|
+
Thread.current[:neo4j_transaction]
|
22
|
+
end
|
23
|
+
|
24
|
+
def running?
|
25
|
+
Thread.current[:neo4j_transaction] != nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def fail?
|
29
|
+
Thread.current[:neo4j_transaction_fail] != nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def fail
|
33
|
+
Thread.current[:neo4j_transaction_fail] = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def success
|
37
|
+
Thread.current[:neo4j_transaction_fail] = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def finish
|
41
|
+
tx = Thread.current[:neo4j_transaction]
|
42
|
+
tx.success unless fail?
|
43
|
+
tx.finish
|
44
|
+
Thread.current[:neo4j_transaction] = nil
|
45
|
+
Thread.current[:neo4j_transaction_fail] = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def filter(*, &block)
|
49
|
+
run &block
|
50
|
+
end
|
51
|
+
|
52
|
+
def run
|
53
|
+
begin
|
54
|
+
new
|
55
|
+
ret = yield self
|
56
|
+
rescue
|
57
|
+
fail
|
58
|
+
raise
|
59
|
+
ensure
|
60
|
+
finish
|
61
|
+
end
|
62
|
+
ret
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Rails
|
3
|
+
module TxMethods
|
4
|
+
def tx_methods(*methods)
|
5
|
+
methods.each do |method|
|
6
|
+
tx_method = "#{method}_in_tx"
|
7
|
+
send(:alias_method, tx_method, method)
|
8
|
+
send(:define_method, method) do |*args|
|
9
|
+
Neo4j::Rails::Transaction.running? ? send(tx_method, *args) : Neo4j::Rails::Transaction.run { send(tx_method, *args) }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Rails
|
3
|
+
module Validations
|
4
|
+
include ActiveModel::Validations
|
5
|
+
|
6
|
+
def read_attribute_for_validation(key)
|
7
|
+
respond_to?(key) ? send(key) : self[key]
|
8
|
+
end
|
9
|
+
|
10
|
+
# The validation process on save can be skipped by passing false. The regular Model#save method is
|
11
|
+
# replaced with this when the validations module is mixed in, which it is by default.
|
12
|
+
def save(options={})
|
13
|
+
perform_validations(options) ? super : false
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid?(context = nil)
|
17
|
+
context ||= (new_record? ? :create : :update)
|
18
|
+
output = super(context)
|
19
|
+
output_rels = valid_relationships?
|
20
|
+
errors.empty? && output && output_rels
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def perform_validations(options={})
|
25
|
+
perform_validation = case options
|
26
|
+
when Hash
|
27
|
+
options[:validate] != false
|
28
|
+
end
|
29
|
+
|
30
|
+
if perform_validation
|
31
|
+
valid?(options.is_a?(Hash) ? options[:context] : nil)
|
32
|
+
else
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Rails
|
3
|
+
module Validations
|
4
|
+
class NonNilValidator < ActiveModel::EachValidator
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
record.errors.add(attribute, :nil, options.merge(:value => value)) if value.nil?
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Neo4j
|
2
|
+
module Rails
|
3
|
+
module Validations
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
7
|
+
def initialize(options)
|
8
|
+
super(options.reverse_merge(:case_sensitive => true))
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup(klass)
|
12
|
+
@attributes.each do |attribute|
|
13
|
+
if klass.index_type_for(attribute) != :exact
|
14
|
+
raise "Can't validate property #{attribute.inspect} on class #{klass} since there is no :exact lucene index on that property or the index declaration #{attribute} comes after the validation declaration in #{klass} (try to move it before the validation rules)"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate_each(record, attribute, value)
|
20
|
+
return if options[:allow_blank] && value.blank?
|
21
|
+
record.class.all("#{attribute}: \"#{value}\"").each do |rec|
|
22
|
+
if rec.id != record.id # it doesn't count if we find ourself!
|
23
|
+
record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
|
24
|
+
break
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module ClassMethods
|
31
|
+
def validates_uniqueness_of(*attr_names)
|
32
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|