ladder 0.3.1 → 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.
@@ -1,104 +1,155 @@
1
- module Ladder::Resource::Dynamic
2
- extend ActiveSupport::Concern
1
+ module Ladder
2
+ module Resource
3
+ module Dynamic
4
+ extend ActiveSupport::Concern
3
5
 
4
- included do
5
- include Ladder::Resource
6
- include InstanceMethods
6
+ include Ladder::Resource
7
7
 
8
- field :_context, type: Hash
8
+ included do
9
+ include InstanceMethods
9
10
 
10
- after_find :apply_context
11
- end
11
+ field :_context, type: Hash
12
+ field :_types, type: Array
12
13
 
13
- ##
14
- # Dynamic field definition
15
- def property(field_name, *opts)
16
- # Store context information
17
- self._context ||= Hash.new(nil)
14
+ after_find :apply_context
15
+ after_find :apply_types
16
+ end
18
17
 
19
- # Ensure new field name is unique
20
- field_name = opts.first[:predicate].qname.join('_').to_sym if respond_to? field_name or :name == field_name
21
- self._context[field_name] = opts.first[:predicate].to_s
18
+ ##
19
+ # Dynamically define a field on the object instance; in addition to
20
+ # (or overloading) class-level properties
21
+ #
22
+ # @see Ladder::Resource#property
23
+ #
24
+ # @param [String] field_name ActiveModel attribute name for the field
25
+ # @param [Hash] opts options to pass to Mongoid / ActiveTriples
26
+ # @option opts [RDF::Term] :predicate RDF predicate for this property
27
+ # @return [Hash] an updated context for the object
28
+ def property(field_name, opts = {})
29
+ # Store context information
30
+ self._context ||= Hash.new(nil)
31
+
32
+ # Ensure new field name is unique
33
+ field_name = opts[:predicate].qname.join('_').to_sym if resource_class.properties.symbolize_keys.keys.include? field_name
34
+
35
+ self._context[field_name] = opts[:predicate].to_s
36
+ apply_context
37
+ end
22
38
 
23
- apply_context
24
- end
39
+ private
25
40
 
26
- private
41
+ ##
42
+ # Dynamically define field accessors
43
+ #
44
+ # @see http://mongoid.org/en/mongoid/v3/documents.html#dynamic_fields Mongoid Dynamic Fields
45
+ #
46
+ # @param [String] field_name ActiveModel attribute name for the field
47
+ # @return [void]
48
+ def create_accessors(field_name)
49
+ define_singleton_method(field_name) { read_attribute(field_name) }
50
+ define_singleton_method("#{field_name}=") { |value| write_attribute(field_name, value) }
51
+ end
27
52
 
28
- ##
29
- # Dynamic field accessors (Mongoid)
30
- def create_accessors(field_name)
31
- define_singleton_method(field_name) { read_attribute(field_name) }
32
- define_singleton_method("#{field_name}=") { |value| write_attribute(field_name, value) }
33
- end
34
-
35
- ##
36
- # Apply dynamic fields and properties to this instance
37
- def apply_context
38
- return unless self._context
53
+ ##
54
+ # Apply dynamic fields and properties to this instance
55
+ #
56
+ # @return [void]
57
+ def apply_context
58
+ return unless self._context
39
59
 
40
- self._context.each do |field_name, uri|
41
- next if fields.keys.include? field_name
60
+ self._context.each do |field_name, uri|
61
+ next if fields.keys.include? field_name
42
62
 
43
- if term = RDF::Vocabulary.find_term(uri)
44
- create_accessors field_name
63
+ if RDF::Vocabulary.find_term(uri)
64
+ create_accessors field_name
45
65
 
46
- # Update resource properties
47
- resource_class.property(field_name, predicate: term)
66
+ # Update resource properties
67
+ resource_class.property(field_name.to_sym, predicate: RDF::Vocabulary.find_term(uri))
68
+ end
48
69
  end
49
70
  end
50
- end
51
71
 
52
- module InstanceMethods
53
-
54
- ##
55
- # Overload Ladder #update_resource
56
- #
57
- # @see Ladder::Resource
58
- def update_resource(opts = {})
59
- # NB: super has to go first or AT clobbers properties
60
- super(opts)
61
-
62
- if self._context
63
- self._context.each do |field_name, uri|
64
- value = self.send(field_name)
65
- cast_uri = RDF::URI.new(value)
66
- resource.set_value(RDF::Vocabulary.find_term(uri), cast_uri.valid? ? cast_uri : value)
72
+ ##
73
+ # Apply dynamic types to this instance
74
+ #
75
+ # @return [void]
76
+ def apply_types
77
+ return unless _types
78
+
79
+ _types.each do |rdf_type|
80
+ unless resource.type.include? RDF::Vocabulary.find_term(rdf_type)
81
+ resource << RDF::Statement.new(rdf_subject, RDF.type, RDF::Vocabulary.find_term(rdf_type))
82
+ end
67
83
  end
68
84
  end
69
85
 
70
- resource
71
- end
86
+ module InstanceMethods
87
+ ##
88
+ # Update the delegated ActiveTriples::Resource from
89
+ # ActiveModel properties & relations
90
+ #
91
+ # @see Ladder::Resource#update_resource
92
+ #
93
+ # @param [Hash] opts options to pass to Mongoid / ActiveTriples
94
+ # @return [ActiveTriples::Resource] resource for the object
95
+ def update_resource(opts = {})
96
+ # NB: super has to go first or AT clobbers properties
97
+ super(opts)
98
+
99
+ if self._context
100
+ self._context.each do |field_name, uri|
101
+ value = send(field_name)
102
+ cast_uri = RDF::URI.new(value)
103
+ resource.set_value(RDF::Vocabulary.find_term(uri), cast_uri.valid? ? cast_uri : value)
104
+ end
105
+ end
106
+
107
+ resource
108
+ end
72
109
 
73
- ##
74
- # Overload Ladder #<<
75
- #
76
- # @see Ladder::Resource
77
- def <<(data)
78
- # ActiveTriples::Resource expects: RDF::Statement, Hash, or Array
79
- data = RDF::Statement.from(data) unless data.is_a? RDF::Statement
80
-
81
- unless resource_class.properties.values.map(&:predicate).include? data.predicate
82
- # Generate a dynamic field name
83
- qname = data.predicate.qname
84
- field_name = (respond_to? qname.last or :name == qname.last) ? qname.join('_').to_sym : qname.last
85
-
86
- # Define property on class
87
- property field_name, predicate: data.predicate
88
- end
89
-
90
- super(data)
91
- end
110
+ ##
111
+ # Push an RDF::Statement into the object
112
+ #
113
+ # @see Ladder::Resource#<<
114
+ #
115
+ # @param [RDF::Statement, Hash, Array] statement @see RDF::Statement#from
116
+ # @return [void]
117
+ def <<(statement)
118
+ # ActiveTriples::Resource expects: RDF::Statement, Hash, or Array
119
+ statement = RDF::Statement.from(statement) unless statement.is_a? RDF::Statement
120
+
121
+ # Don't store statically-defined types
122
+ return if resource_class.type == statement.object
123
+
124
+ if RDF.type == statement.predicate
125
+ # Store type information
126
+ self._types ||= []
127
+ self._types << statement.object.to_s
128
+
129
+ apply_types
130
+ return
131
+ end
132
+
133
+ # If we have an undefined predicate, then dynamically define it
134
+ return unless statement.predicate.qname
135
+ property statement.predicate.qname.last, predicate: statement.predicate unless field_from_predicate statement.predicate
136
+
137
+ super
138
+ end
92
139
 
93
- private
94
- ##
95
- # Overload ActiveTriples #resource_class
96
- #
97
- # @see ActiveTriples::Identifiable
98
- def resource_class
99
- @modified_resource_class ||= self.class.resource_class.clone
140
+ private
141
+
142
+ ##
143
+ # Return a cloned, mutatable copy of the
144
+ # ActiveTriples::Resource class for this instance
145
+ #
146
+ # @see ActiveTriples::Identifiable#resource_class
147
+ #
148
+ # @return [Class] a GeneratedResourceSchema for this class
149
+ def resource_class
150
+ @modified_resource_class ||= self.class.resource_class.clone
151
+ end
100
152
  end
101
-
153
+ end
102
154
  end
103
-
104
- end
155
+ end
@@ -1,54 +1,67 @@
1
1
  require 'json/ld'
2
2
 
3
- module Ladder::Resource::Serializable
4
- ##
5
- # Return JSON-LD representation
6
- #
7
- # @see ActiveTriples::Resource#dump
8
- def as_jsonld(opts = {})
9
- JSON.parse update_resource(opts.slice :related).dump(:jsonld, {standard_prefixes: true}.merge(opts))
10
- end
11
-
12
- ##
13
- # Generate a qname-based JSON representation
14
- #
15
- def as_qname(opts = {})
16
- qname_hash = type.empty? ? {} : {rdf: {type: type.first.pname }}
3
+ module Ladder
4
+ module Resource
5
+ module Serializable
6
+ ##
7
+ # Return a JSON-LD representation for the resource
8
+ #
9
+ # @see ActiveTriples::Resource#dump
10
+ #
11
+ # @param [Hash] opts options to pass to ActiveTriples
12
+ # @option opts [Boolean] :related whether to include related resources
13
+ # @return [Hash] a serialized JSON-LD version of the resource
14
+ def as_jsonld(opts = {})
15
+ JSON.parse update_resource(opts.slice :related).dump(:jsonld, { standard_prefixes: true }.merge(opts))
16
+ end
17
17
 
18
- resource_class.properties.each do |field_name, property|
19
- ns, name = property.predicate.qname
20
- qname_hash[ns] ||= Hash.new
18
+ ##
19
+ # Return a framed, compacted JSON-LD representation
20
+ # by embedding related objects from the graph
21
+ #
22
+ # NB: Will NOT embed related objects with same @type.
23
+ # Spec under discussion, see https://github.com/json-ld/json-ld.org/issues/110
24
+ #
25
+ # @return [Hash] a serialized JSON-LD version of the resource
26
+ def as_framed_jsonld
27
+ json_hash = as_jsonld related: true
21
28
 
22
- object = self.send(field_name)
29
+ context = json_hash['@context']
30
+ frame = { '@context' => context }
31
+ frame['@type'] = type.first.pname unless type.empty?
23
32
 
24
- if relations.keys.include? field_name
25
- if opts[:related]
26
- qname_hash[ns][name] = object.to_a.map { |obj| obj.as_qname }
27
- else
28
- qname_hash[ns][name] = object.to_a.map { |obj| "#{obj.class.name.underscore.pluralize}:#{obj.id}" }
29
- end
30
- elsif fields.keys.include? field_name
31
- qname_hash[ns][name] = read_attribute(field_name)
33
+ JSON::LD::API.compact(JSON::LD::API.frame(json_hash, frame), context)
32
34
  end
33
- end
34
35
 
35
- qname_hash
36
- end
36
+ ##
37
+ # Return a qname-based JSON representation
38
+ #
39
+ # @param [Hash] opts options for serializaiton
40
+ # @option opts [Boolean] :related whether to include related resources
41
+ # @return [Hash] a serialized 'qname' version of the resource
42
+ def as_qname(opts = {})
43
+ qname_hash = type.empty? ? {} : { rdf: { type: type.first.pname } }
44
+
45
+ resource_class.properties.each do |field_name, property|
46
+ ns, name = property.predicate.qname
47
+ qname_hash[ns] ||= {}
37
48
 
38
- ##
39
- # Return a framed, compacted JSON-LD representation
40
- # by embedding related objects from the graph
41
- #
42
- # NB: Will NOT embed related objects with same @type.
43
- # Spec under discussion, see https://github.com/json-ld/json-ld.org/issues/110
44
- def as_framed_jsonld
45
- json_hash = as_jsonld related: true
49
+ if relations.keys.include? field_name
50
+ if opts[:related]
51
+ qname_hash[ns][name] = send(field_name).to_a.map(&:as_qname)
52
+ else
53
+ qname_hash[ns][name] = send(field_name).to_a.map { |obj| "#{obj.class.name.underscore.pluralize}:#{obj.id}" }
54
+ end
55
+ elsif fields.keys.include? field_name
56
+ qname_hash[ns][name] = read_attribute(field_name)
57
+ end
46
58
 
47
- context = json_hash['@context']
48
- frame = {'@context' => context}
49
- frame['@type'] = type.first.pname unless type.empty?
59
+ # Remove empty/null values
60
+ qname_hash[ns].delete_if { |_k, v| v.blank? }
61
+ end
50
62
 
51
- JSON::LD::API.compact(JSON::LD::API.frame(json_hash, frame), context)
63
+ qname_hash
64
+ end
65
+ end
52
66
  end
53
-
54
- end
67
+ end
@@ -2,18 +2,20 @@ require 'active_support/concern'
2
2
  require 'elasticsearch/model'
3
3
  require 'elasticsearch/model/callbacks'
4
4
 
5
- module Ladder::Searchable
6
- extend ActiveSupport::Concern
5
+ module Ladder
6
+ module Searchable
7
+ extend ActiveSupport::Concern
7
8
 
8
- autoload :Background, 'ladder/searchable/background'
9
- autoload :File, 'ladder/searchable/file'
10
- autoload :Resource, 'ladder/searchable/resource'
9
+ autoload :Background, 'ladder/searchable/background'
10
+ autoload :File, 'ladder/searchable/file'
11
+ autoload :Resource, 'ladder/searchable/resource'
11
12
 
12
- included do
13
- include Elasticsearch::Model
14
- include Elasticsearch::Model::Callbacks unless self.ancestors.include? Ladder::Searchable::Background
13
+ included do
14
+ include Elasticsearch::Model
15
+ include Elasticsearch::Model::Callbacks unless ancestors.include? Ladder::Searchable::Background
15
16
 
16
- include Ladder::Searchable::Resource if self.ancestors.include? Ladder::Resource
17
- include Ladder::Searchable::File if self.ancestors.include? Ladder::File
18
- end
19
- end
17
+ include Ladder::Searchable::Resource if ancestors.include? Ladder::Resource
18
+ include Ladder::Searchable::File if ancestors.include? Ladder::File
19
+ end
20
+ end
21
+ end
@@ -1,43 +1,52 @@
1
1
  require 'active_job'
2
2
 
3
- module Ladder::Searchable::Background
4
- extend ActiveSupport::Concern
3
+ module Ladder
4
+ module Searchable
5
+ module Background
6
+ extend ActiveSupport::Concern
5
7
 
6
- included do
7
- include Ladder::Searchable
8
- include GlobalID::Identification
8
+ included do
9
+ include Ladder::Searchable
10
+ include GlobalID::Identification
9
11
 
10
- GlobalID.app = 'Ladder'
12
+ GlobalID.app = 'Ladder'
11
13
 
12
- after_create { enqueue :index }
13
- after_update { enqueue :update }
14
- before_destroy { enqueue :delete }
15
- end
14
+ after_create { enqueue :index }
15
+ after_update { enqueue :update }
16
+ before_destroy { enqueue :delete }
17
+ end
16
18
 
17
- private
19
+ private
18
20
 
19
- def enqueue(operation)
20
- # Force autosave of related documents before queueing for indexing or updating
21
- methods.select{|i| i[/autosave_documents/] }.each{|m| send m} unless :delete == operation
21
+ ##
22
+ # Queue an index operation for asynchronous execution
23
+ #
24
+ # @param [Symbol] operation the kind of operation to perform: index, delete, update
25
+ # @return [void]
26
+ def enqueue(operation)
27
+ # Force autosave of related documents before queueing for indexing or updating
28
+ methods.select { |i| i[/autosave_documents/] }.each { |m| send m } unless :delete == operation
22
29
 
23
- Indexer.set(queue: self.class.name.underscore.pluralize).perform_later(operation.to_s, self)
24
- end
30
+ Indexer.set(queue: self.class.name.underscore.pluralize).perform_later(operation.to_s, self)
31
+ end
25
32
 
26
- class Indexer < ActiveJob::Base
27
- queue_as :elasticsearch
28
-
29
- def perform(operation, model)
30
- case operation
31
- when 'index'
32
- model.__elasticsearch__.index_document
33
- when 'update'
34
- model.__elasticsearch__.update_document
35
- when 'delete'
36
- model.__elasticsearch__.delete_document
37
- # else raise ArgumentError, "Unknown operation '#{operation}'"
33
+ class Indexer < ActiveJob::Base
34
+ queue_as :elasticsearch
35
+
36
+ ##
37
+ # Perform a queued index operation
38
+ #
39
+ # @param [String] operation the kind of operation to perform: index, delete, update
40
+ # @param [Ladder::Resource, Ladder::File] model the object instance to modify in the index
41
+ # @return [void]
42
+ def perform(operation, model)
43
+ case operation
44
+ when 'index' then model.__elasticsearch__.index_document
45
+ when 'update' then model.__elasticsearch__.update_document
46
+ when 'delete' then model.__elasticsearch__.delete_document
47
+ end
48
+ end
38
49
  end
39
50
  end
40
-
41
51
  end
42
-
43
- end
52
+ end