architect4r 0.3.2
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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +53 -0
- data/Guardfile +10 -0
- data/License +20 -0
- data/README.md +62 -0
- data/Rakefile +40 -0
- data/ReleaseNotes.md +33 -0
- data/Roadmap.md +31 -0
- data/Specs.md +21 -0
- data/architect4r.gemspec +31 -0
- data/lib/architect4r.rb +66 -0
- data/lib/architect4r/adapters/carrier_wave.rb +64 -0
- data/lib/architect4r/core/configuration.rb +148 -0
- data/lib/architect4r/core/cypher_methods.rb +47 -0
- data/lib/architect4r/core/management_methods.rb +129 -0
- data/lib/architect4r/core/node_methods.rb +73 -0
- data/lib/architect4r/core/relationship_methods.rb +82 -0
- data/lib/architect4r/generic_node.rb +7 -0
- data/lib/architect4r/has_node.rb +80 -0
- data/lib/architect4r/instance_manager.rb +47 -0
- data/lib/architect4r/model/callbacks.rb +19 -0
- data/lib/architect4r/model/connection.rb +29 -0
- data/lib/architect4r/model/links_query_interface.rb +23 -0
- data/lib/architect4r/model/node.rb +117 -0
- data/lib/architect4r/model/persistency.rb +95 -0
- data/lib/architect4r/model/properties.rb +166 -0
- data/lib/architect4r/model/queries.rb +38 -0
- data/lib/architect4r/model/relationship.rb +105 -0
- data/lib/architect4r/model/relationships.rb +16 -0
- data/lib/architect4r/model/validations.rb +11 -0
- data/lib/architect4r/server.rb +96 -0
- data/lib/architect4r/version.rb +3 -0
- data/spec/architect4r_spec.rb +9 -0
- data/spec/core/configuration_spec.rb +54 -0
- data/spec/core/cypher_methods_spec.rb +29 -0
- data/spec/core/node_methods_spec.rb +47 -0
- data/spec/core/relationship_methods_spec.rb +92 -0
- data/spec/fixtures/architect4r.yml +21 -0
- data/spec/fixtures/graph.db.default/active_tx_log +1 -0
- data/spec/fixtures/graph.db.default/index/lucene-store.db +0 -0
- data/spec/fixtures/graph.db.default/index/lucene.log.1 +0 -0
- data/spec/fixtures/graph.db.default/index/lucene.log.active +0 -0
- data/spec/fixtures/graph.db.default/index/lucene.log.v0 +0 -0
- data/spec/fixtures/graph.db.default/index/lucene.log.v1 +0 -0
- data/spec/fixtures/graph.db.default/index/lucene.log.v2 +0 -0
- data/spec/fixtures/graph.db.default/lock +0 -0
- data/spec/fixtures/graph.db.default/messages.log +183 -0
- data/spec/fixtures/graph.db.default/neostore +0 -0
- data/spec/fixtures/graph.db.default/neostore.id +0 -0
- data/spec/fixtures/graph.db.default/neostore.nodestore.db +0 -0
- data/spec/fixtures/graph.db.default/neostore.nodestore.db.id +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db.arrays +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db.arrays.id +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db.id +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db.index +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db.index.id +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db.index.keys +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db.index.keys.id +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db.strings +0 -0
- data/spec/fixtures/graph.db.default/neostore.propertystore.db.strings.id +0 -0
- data/spec/fixtures/graph.db.default/neostore.relationshipstore.db +0 -0
- data/spec/fixtures/graph.db.default/neostore.relationshipstore.db.id +0 -0
- data/spec/fixtures/graph.db.default/neostore.relationshiptypestore.db +0 -0
- data/spec/fixtures/graph.db.default/neostore.relationshiptypestore.db.id +0 -0
- data/spec/fixtures/graph.db.default/neostore.relationshiptypestore.db.names +0 -0
- data/spec/fixtures/graph.db.default/neostore.relationshiptypestore.db.names.id +0 -0
- data/spec/fixtures/graph.db.default/nioneo_logical.log.1 +0 -0
- data/spec/fixtures/graph.db.default/nioneo_logical.log.active +0 -0
- data/spec/fixtures/graph.db.default/tm_tx_log.1 +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/active_tx_log +1 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/messages.log +142 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.nodestore.db +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.nodestore.db.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db.arrays +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db.arrays.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db.index +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db.index.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db.index.keys +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db.index.keys.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db.strings +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.propertystore.db.strings.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.relationshipstore.db +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.relationshipstore.db.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.relationshiptypestore.db +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.relationshiptypestore.db.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.relationshiptypestore.db.names +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/neostore.relationshiptypestore.db.names.id +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/nioneo_logical.log.active +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/nioneo_logical.log.v0 +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/nioneo_logical.log.v1 +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/nioneo_logical.log.v2 +0 -0
- data/spec/fixtures/graph.db.default/upgrade_backup/tm_tx_log.1 +0 -0
- data/spec/has_node_spec.rb +87 -0
- data/spec/model/links_query_interface_spec.rb +22 -0
- data/spec/model/links_spec.rb +26 -0
- data/spec/model/node_spec.rb +48 -0
- data/spec/model/persistency_spec.rb +98 -0
- data/spec/model/properties_spec.rb +165 -0
- data/spec/model/queries_spec.rb +50 -0
- data/spec/model/relationship_spec.rb +63 -0
- data/spec/model/validations_spec.rb +31 -0
- data/spec/server_spec.rb +33 -0
- data/spec/spec_helper.rb +115 -0
- metadata +377 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module Architect4r
|
|
2
|
+
|
|
3
|
+
# has_node :node, AccountNode, :sync => [:firstname, :lastname]
|
|
4
|
+
module HasNode
|
|
5
|
+
|
|
6
|
+
# Easier integration pattern
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
|
|
11
|
+
def has_node(attr_name, model_name, options={})
|
|
12
|
+
|
|
13
|
+
#if self.respond_to?(:after_create)
|
|
14
|
+
after_create :"architect4r_create_#{attr_name}"
|
|
15
|
+
after_update :"architect4r_sync_#{attr_name}"
|
|
16
|
+
after_destroy :"architect4r_destroy_#{attr_name}"
|
|
17
|
+
#end
|
|
18
|
+
|
|
19
|
+
# Create a neo4j node
|
|
20
|
+
define_method("architect4r_create_#{attr_name}") do
|
|
21
|
+
return nil unless self.id
|
|
22
|
+
|
|
23
|
+
new_node = model_name.new
|
|
24
|
+
new_node.write_attribute(:architect4r_sync_id, self.id)
|
|
25
|
+
|
|
26
|
+
options[:sync].to_a.each do |prop|
|
|
27
|
+
new_node.send("#{prop}=", self.send(prop))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if new_node.save
|
|
31
|
+
new_node
|
|
32
|
+
else
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
instance_variable_set("@#{attr_name}", new_node)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Create a getter method
|
|
39
|
+
define_method(attr_name) do
|
|
40
|
+
# Only available if the database record is already created
|
|
41
|
+
return nil unless self.id
|
|
42
|
+
|
|
43
|
+
# Get or create the graph db node
|
|
44
|
+
the_node = instance_variable_get("@#{attr_name}")
|
|
45
|
+
the_node ||= begin
|
|
46
|
+
linked_node = model_name.find_by_cypher("start s=node(#{model_name.model_root.id}) match s<-[:model_type]-d where d.architect4r_sync_id = #{self.id} return d", "d").first
|
|
47
|
+
linked_node ||= self.send("architect4r_create_#{attr_name}")
|
|
48
|
+
instance_variable_set("@#{attr_name}", linked_node)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
define_method("architect4r_destroy_#{attr_name}") do
|
|
53
|
+
self.send("#{attr_name}").destroy
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Keep the graph node in sync, so it reflects the name
|
|
58
|
+
#
|
|
59
|
+
define_method("architect4r_sync_#{attr_name}") do
|
|
60
|
+
changed = false
|
|
61
|
+
|
|
62
|
+
options[:sync].to_a.each do |prop|
|
|
63
|
+
if self.node.send("#{prop}") != self.send(prop)
|
|
64
|
+
self.node.send("#{prop}=", self.send(prop))
|
|
65
|
+
changed = true
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if changed
|
|
70
|
+
self.node.save
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Architect4r
|
|
2
|
+
class InstanceManager
|
|
3
|
+
|
|
4
|
+
def initialize(path)
|
|
5
|
+
@path = path
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def server_path
|
|
9
|
+
@path
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def start
|
|
13
|
+
%x[#{server_path}/bin/neo4j start]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def stop
|
|
17
|
+
%x[#{server_path}/bin/neo4j stop]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def restart
|
|
21
|
+
%x[#{server_path}/bin/neo4j restart]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def reset
|
|
25
|
+
self.stop
|
|
26
|
+
|
|
27
|
+
# Reset the database
|
|
28
|
+
FileUtils.rm_rf("#{server_path}/data/graph.db")
|
|
29
|
+
FileUtils.mkdir("#{server_path}/data/graph.db")
|
|
30
|
+
|
|
31
|
+
# Remove log files
|
|
32
|
+
FileUtils.rm_rf("#{server_path}/data/log")
|
|
33
|
+
FileUtils.mkdir("#{server_path}/data/log")
|
|
34
|
+
|
|
35
|
+
# Start the server
|
|
36
|
+
self.start
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reset_to_sample_data(from)
|
|
40
|
+
self.stop
|
|
41
|
+
FileUtils.rm_rf("#{server_path}/data/graph.db")
|
|
42
|
+
FileUtils.cp_r(from, "#{server_path}/data/graph.db/")
|
|
43
|
+
self.start
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
module Architect4r
|
|
4
|
+
module Model
|
|
5
|
+
|
|
6
|
+
module Callbacks
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
extend ActiveModel::Callbacks
|
|
11
|
+
include ActiveModel::Validations::Callbacks
|
|
12
|
+
|
|
13
|
+
define_model_callbacks :initialize, :only => :after
|
|
14
|
+
define_model_callbacks :create, :destroy, :save, :update
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Architect4r
|
|
2
|
+
module Model
|
|
3
|
+
module Connection
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
module InstanceMethods
|
|
7
|
+
|
|
8
|
+
def connection
|
|
9
|
+
self.class.connection
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
|
|
16
|
+
def use_server(server)
|
|
17
|
+
@connection = server
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def connection
|
|
21
|
+
# TODO: apply configuration
|
|
22
|
+
@connection || Server.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
module Architect4r
|
|
2
|
+
|
|
3
|
+
module Model
|
|
4
|
+
|
|
5
|
+
class Node
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# architect4r extensions
|
|
9
|
+
#
|
|
10
|
+
include Architect4r::Model::Connection
|
|
11
|
+
include Architect4r::Model::Callbacks
|
|
12
|
+
include Architect4r::Model::Persistency
|
|
13
|
+
include Architect4r::Model::Queries
|
|
14
|
+
include Architect4r::Model::Relationships
|
|
15
|
+
|
|
16
|
+
def self.inherited(subklass)
|
|
17
|
+
super
|
|
18
|
+
subklass.send(:include, ActiveModel::Conversion)
|
|
19
|
+
subklass.extend ActiveModel::Naming
|
|
20
|
+
subklass.send(:include, Architect4r::Model::Properties)
|
|
21
|
+
subklass.send(:include, Architect4r::Model::Validations)
|
|
22
|
+
|
|
23
|
+
subklass.class_exec do
|
|
24
|
+
|
|
25
|
+
def self.model_root
|
|
26
|
+
@model_root ||= begin
|
|
27
|
+
# Check if there is already a model root,
|
|
28
|
+
query = "start root = node(0) match (root)-[r:#{ model_root_relation_type}]->(x) where r.architect4r_type and r.architect4r_type = '#{name}' return x"
|
|
29
|
+
the_root = connection.execute_cypher(query).to_a.first
|
|
30
|
+
the_root &&= the_root['x']
|
|
31
|
+
|
|
32
|
+
# otherwise create one
|
|
33
|
+
the_root ||= begin
|
|
34
|
+
m_root = connection.create_node(:name => "#{name} Root")
|
|
35
|
+
connection.create_relationship(0, m_root, model_root_relation_type, { 'architect4r_type' => name })
|
|
36
|
+
m_root
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Return model root node
|
|
40
|
+
GenericNode.send(:build_from_database, the_root)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_accessor :raw_data
|
|
48
|
+
|
|
49
|
+
def initialize(properties={})
|
|
50
|
+
run_callbacks :initialize do
|
|
51
|
+
parse_properties(properties)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Create the document. Validation is enabled by default and will return
|
|
56
|
+
# false if the document is not valid. If all goes well, the document will
|
|
57
|
+
# be returned.
|
|
58
|
+
def create(options = {})
|
|
59
|
+
run_callbacks :create do
|
|
60
|
+
run_callbacks :save do
|
|
61
|
+
# only create valid records
|
|
62
|
+
return false unless perform_validations(options)
|
|
63
|
+
|
|
64
|
+
# perform creation
|
|
65
|
+
if result = connection.create_node(self._to_database_hash)
|
|
66
|
+
self.raw_data = result
|
|
67
|
+
|
|
68
|
+
# Link the node with a model root node
|
|
69
|
+
connection.create_relationship(self.id, self.class.model_root.id, 'model_type')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# if something goes wrong we receive a nil value and return false
|
|
73
|
+
!result.nil?
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Trigger the callbacks (before, after, around)
|
|
79
|
+
# only if the document isn't new
|
|
80
|
+
def update(options = {})
|
|
81
|
+
run_callbacks :update do
|
|
82
|
+
run_callbacks :save do
|
|
83
|
+
# Check if record can be updated
|
|
84
|
+
raise "Cannot save a destroyed document!" if destroyed?
|
|
85
|
+
raise "Calling #{self.class.name}#update on document that has not been created!" if new?
|
|
86
|
+
|
|
87
|
+
# Check if we can continue
|
|
88
|
+
return false unless perform_validations(options)
|
|
89
|
+
|
|
90
|
+
# perform update
|
|
91
|
+
result = connection.update_node(self.id, self._to_database_hash)
|
|
92
|
+
|
|
93
|
+
# if something goes wrong we receive a nil value and return false
|
|
94
|
+
!result.nil?
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def destroy
|
|
100
|
+
run_callbacks :destroy do
|
|
101
|
+
if result = connection.delete_node(self.id)
|
|
102
|
+
@_destroyed = true
|
|
103
|
+
self.freeze
|
|
104
|
+
end
|
|
105
|
+
result
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.model_root_relation_type
|
|
110
|
+
'model_root'
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Architect4r
|
|
2
|
+
module Model
|
|
3
|
+
module Persistency
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module InstanceMethods
|
|
11
|
+
|
|
12
|
+
# Creates the document in the db. Raises an exception
|
|
13
|
+
# if the document is not created properly.
|
|
14
|
+
def create!(options = {})
|
|
15
|
+
self.fail_validate!(self) unless self.create(options)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def save(options = {})
|
|
19
|
+
self.new? ? create(options) : update(options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def save!
|
|
23
|
+
self.class.fail_validate!(self) unless self.save
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def id
|
|
28
|
+
@id ||= if raw_data && raw_data['self'].present?
|
|
29
|
+
raw_data['self'].split('/').last.to_i
|
|
30
|
+
else
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
alias :to_key :id
|
|
35
|
+
alias :to_param :id
|
|
36
|
+
|
|
37
|
+
def new?
|
|
38
|
+
# Persisted objects always have an id.
|
|
39
|
+
id.nil?
|
|
40
|
+
end
|
|
41
|
+
alias :new_record? :new?
|
|
42
|
+
|
|
43
|
+
def destroyed?
|
|
44
|
+
!!@_destroyed
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def persisted?
|
|
48
|
+
!new? && !destroyed?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Update the document's attributes and save. For example:
|
|
52
|
+
def update_attributes(hash)
|
|
53
|
+
update_attributes_without_saving hash
|
|
54
|
+
save
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
protected
|
|
58
|
+
|
|
59
|
+
def perform_validations(options = {})
|
|
60
|
+
(options[:validate].presence || true) ? valid? : true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
module ClassMethods
|
|
66
|
+
|
|
67
|
+
def create(*args, &block)
|
|
68
|
+
instance = new(*args, &block)
|
|
69
|
+
instance.create
|
|
70
|
+
instance
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Defines an instance and save it directly to the database
|
|
74
|
+
def create!(*args, &block)
|
|
75
|
+
instance = new(*args, &block)
|
|
76
|
+
instance.create!
|
|
77
|
+
instance
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
protected
|
|
81
|
+
|
|
82
|
+
# Create an object from the database data
|
|
83
|
+
def build_from_database(data)
|
|
84
|
+
return nil if data.blank?
|
|
85
|
+
|
|
86
|
+
# Create an instance of the object
|
|
87
|
+
obj = self.new(data['data'])
|
|
88
|
+
obj.raw_data = data
|
|
89
|
+
obj
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
module Architect4r
|
|
2
|
+
|
|
3
|
+
module Model
|
|
4
|
+
|
|
5
|
+
module Properties
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
class_attribute(:properties) unless respond_to?(:properties)
|
|
10
|
+
self.properties ||= {}
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module InstanceMethods
|
|
14
|
+
def parse_properties(properties = {})
|
|
15
|
+
@properties_data = {}
|
|
16
|
+
update_attributes_without_saving(properties)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Return a hash of all properties which can be transformed into json
|
|
20
|
+
# Remove all nil values, as those cannot be stored in the graph
|
|
21
|
+
def _to_database_hash
|
|
22
|
+
@properties_data.merge('architect4r_type' => self.class.name).
|
|
23
|
+
reject { |key, value| value.nil? }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Read the casted value of an attribute defined with a property.
|
|
27
|
+
#
|
|
28
|
+
# ==== Returns
|
|
29
|
+
# Object:: the casted attibutes value.
|
|
30
|
+
def read_attribute(property, locale = nil)
|
|
31
|
+
property = "#{property}_#{locale}" if locale
|
|
32
|
+
@properties_data[property.to_s]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Store a casted value in the current instance of an attribute defined
|
|
36
|
+
# with a property and update dirty status
|
|
37
|
+
def write_attribute(property, value, locale = nil)
|
|
38
|
+
# retrieve options for the attribute
|
|
39
|
+
opts = self.class.properties[property.to_s]
|
|
40
|
+
|
|
41
|
+
# Check if we should store a localized version of the data
|
|
42
|
+
property = "#{property}_#{locale}" if locale
|
|
43
|
+
|
|
44
|
+
# TODO: Mark dirty attribute tracking
|
|
45
|
+
|
|
46
|
+
# Cast the value before storing it
|
|
47
|
+
cast_to = opts && opts[:cast_to] || Object
|
|
48
|
+
|
|
49
|
+
@properties_data[property.to_s] = if value.nil?
|
|
50
|
+
nil
|
|
51
|
+
elsif cast_to == String
|
|
52
|
+
value.to_s
|
|
53
|
+
elsif cast_to == Integer
|
|
54
|
+
value.to_i
|
|
55
|
+
elsif cast_to == Float
|
|
56
|
+
value.to_f
|
|
57
|
+
elsif cast_to == TrueClass or cast_to == FalseClass
|
|
58
|
+
if value.kind_of?(Integer)
|
|
59
|
+
value == 1
|
|
60
|
+
else
|
|
61
|
+
%w[ true 1 t ].include?(value.to_s.downcase)
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
value
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def update_attributes_without_saving(hash)
|
|
69
|
+
return if hash.nil?
|
|
70
|
+
hash.each do |key, value|
|
|
71
|
+
if self.respond_to?("#{key}=")
|
|
72
|
+
self.send("#{key}=", value)
|
|
73
|
+
else
|
|
74
|
+
@properties_data[key] = value
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
alias :attributes= :update_attributes_without_saving
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
module ClassMethods
|
|
84
|
+
|
|
85
|
+
# Allow setting properties
|
|
86
|
+
def property(name, options = {})
|
|
87
|
+
unless self.properties.keys.find { |p| p == name.to_s }
|
|
88
|
+
define_property(name, options)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def timestamps!
|
|
93
|
+
property(:updated_at, :cast_to => Time)
|
|
94
|
+
property(:created_at, :cast_to => Time)
|
|
95
|
+
|
|
96
|
+
set_callback :save, :before do |object|
|
|
97
|
+
write_attribute('updated_at', Time.now)
|
|
98
|
+
write_attribute('created_at', Time.now) if object.new?
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
protected
|
|
103
|
+
|
|
104
|
+
# This is not a thread safe operation, if you have to set new properties at runtime
|
|
105
|
+
# make sure a mutex is used.
|
|
106
|
+
def define_property(name, options={})
|
|
107
|
+
# read only flag
|
|
108
|
+
read_only = options.delete(:read_only) || false
|
|
109
|
+
|
|
110
|
+
# Store the property and its options
|
|
111
|
+
self.properties[name.to_s] = options
|
|
112
|
+
|
|
113
|
+
# Create getter and setter methods
|
|
114
|
+
create_property_getter(name)
|
|
115
|
+
create_property_setter(name) unless read_only == true
|
|
116
|
+
|
|
117
|
+
# Return property name just in case ;)
|
|
118
|
+
name
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# defines the getter for the property (and optional aliases)
|
|
122
|
+
def create_property_getter(name)
|
|
123
|
+
define_method(name) do
|
|
124
|
+
# Get property configuration
|
|
125
|
+
localize = self.class.properties[name.to_s][:localize]
|
|
126
|
+
|
|
127
|
+
# Determine current locale
|
|
128
|
+
locale = localize ? I18n.locale : nil
|
|
129
|
+
|
|
130
|
+
# Fetch property value
|
|
131
|
+
result = read_attribute(name, locale)
|
|
132
|
+
|
|
133
|
+
# If there is a fallback locale, fetch its value if appropriate
|
|
134
|
+
if result.nil? && localize && localize != true && localize.to_s != locale.to_s
|
|
135
|
+
result = read_attribute(name, localize)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Finally return some data
|
|
139
|
+
result
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
opts = self.properties[name.to_s]
|
|
143
|
+
|
|
144
|
+
if opts[:cast_to].presence && opts[:cast_to] == TrueClass
|
|
145
|
+
define_method("#{name}?") do
|
|
146
|
+
value = read_attribute(name)
|
|
147
|
+
!(value.nil? || value == false)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# defines the setter for the property (and optional aliases)
|
|
153
|
+
def create_property_setter(name)
|
|
154
|
+
define_method("#{name}=") do |value|
|
|
155
|
+
locale = self.class.properties[name.to_s][:localize] ? I18n.locale : nil
|
|
156
|
+
write_attribute(name, value, locale)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
end
|