Class: Solis::Graph

Inherits:
Object
  • Object
show all
Defined in:
lib/solis/graph.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(graph, options = {}) ⇒ Graph

Returns a new instance of Graph.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/solis/graph.rb', line 15

def initialize(graph, options = {})
  raise "Please provide a graph_name, graph_prefix and sparql_endpoint option" if options.nil? || options.empty?
  cloned_options = options.clone

  Solis::Options.instance.set = options

  @global_resource_stack = []
  @graph = graph
  @graph_name = cloned_options.delete(:graph_name) || '/'
  @graph_prefix = cloned_options.delete(:graph_prefix) || 'pf0'
  @sparql_endpoint = cloned_options.delete(:sparql_endpoint) || nil

  if cloned_options&.key?(:hooks) && cloned_options[:hooks].is_a?(Hash)
    hooks = cloned_options[:hooks]

    if hooks.key?(:read)
      if hooks[:read].key?(:before)
        @default_before_read = hooks[:read][:before]
      end

      if hooks[:read].key?(:after)
        @default_after_read = hooks[:read][:after]
      end
    end

    if hooks.key?(:create)
      if hooks[:create].key?(:before)
        @default_before_create = hooks[:create][:before]
      end

      if hooks[:create].key?(:after)
        @default_after_create = hooks[:create][:after]
      end
    end

    if hooks.key?(:update)
      if hooks[:update].key?(:before)
        @default_before_update = hooks[:update][:before]
      end

      if hooks[:update].key?(:after)
        @default_after_update = hooks[:update][:after]
      end
    end

    if hooks.key?(:delete)
      if hooks[:delete].key?(:before)
        @default_before_delete = hooks[:delete][:before]
      end
      if hooks[:delete].key?(:after)
        @default_after_delete = hooks[:delete][:after]
      end
    end
  end

  unless @sparql_endpoint.nil?
    uri = URI.parse(@sparql_endpoint)
    @sparql_endpoint = RDF::Repository.new(uri: RDF::URI(@graph_name), title: uri.host) if uri.scheme.eql?('repository')
  end

  @inflections = cloned_options.delete(:inflections) || nil
  @shapes = Solis::Shape.from_graph(graph)
  @language = cloned_options.delete(:language) || 'en'

  unless @inflections.nil?
    raise "Inflection file not found #{File.absolute_path(@inflections)}" unless File.exist?(@inflections)
    JSON.parse(File.read(@inflections)).each do |s, p|
      raise "No plural found" if s.nil? && p.nil?
      raise "No plural found for #{p}" if s.nil?
      raise "No plural found for #{s}" if p.nil?
      ActiveSupport::Inflector.inflections.irregular(s, p)
    end
  end

  @shape_tree = {}
  shape_keys = @shapes.map do |shape_name, _|
    if shape_name.empty?
      LOGGER.warn("Dangling entity found #{_[:target_class].to_s} removing")
      next
    end
    #@shapes[shape_name][:attributes].select { |_, metadata| metadata.key?(:node_kind) && !metadata[:node_kind].nil? }.values.map { |m| m[:datatype].to_s.split('#').last }
    @shapes[shape_name][:attributes].select { |_, | .key?(:node_kind) && ![:node_kind].nil? }.values.map { |m| m[:datatype].to_s }
  end
  shape_keys += @shapes.keys
  shape_keys = shape_keys.flatten.compact.sort.uniq

  shape_keys.each do |shape_name|
    d = @shape_tree.key?(shape_name) ? @shape_tree[shape_name] : 0
    d += 1
    @shape_tree[shape_name] = d
  end

  @shape_tree = @shape_tree.sort_by(&:last).reverse.to_h

  shape_keys.each do |s|
    shape_as_model(s)
  end

  @shape_tree.each do |shape_name, _|
    shape_as_resource(shape_name)
  end

  Graphiti.configure do |config|
    config.pagination_links = true
    config.context_for_endpoint= ->(path, action) {
      Solis::NoopEndpoint.new(path, action)
    }
  end

  Graphiti.setup!
end

Instance Attribute Details

#default_after_createObject

Returns the value of attribute default_after_create.



13
14
15
# File 'lib/solis/graph.rb', line 13

def default_after_create
  @default_after_create
end

#default_after_deleteObject

Returns the value of attribute default_after_delete.



13
14
15
# File 'lib/solis/graph.rb', line 13

def default_after_delete
  @default_after_delete
end

#default_after_readObject

Returns the value of attribute default_after_read.



13
14
15
# File 'lib/solis/graph.rb', line 13

def default_after_read
  @default_after_read
end

#default_after_updateObject

Returns the value of attribute default_after_update.



13
14
15
# File 'lib/solis/graph.rb', line 13

def default_after_update
  @default_after_update
end

#default_before_createObject

Returns the value of attribute default_before_create.



13
14
15
# File 'lib/solis/graph.rb', line 13

def default_before_create
  @default_before_create
end

#default_before_deleteObject

Returns the value of attribute default_before_delete.



13
14
15
# File 'lib/solis/graph.rb', line 13

def default_before_delete
  @default_before_delete
end

#default_before_readObject

Returns the value of attribute default_before_read.



13
14
15
# File 'lib/solis/graph.rb', line 13

def default_before_read
  @default_before_read
end

#default_before_updateObject

Returns the value of attribute default_before_update.



13
14
15
# File 'lib/solis/graph.rb', line 13

def default_before_update
  @default_before_update
end

#optionsObject

Returns the value of attribute options.



13
14
15
# File 'lib/solis/graph.rb', line 13

def options
  @options
end

Instance Method Details

#flush_all(graph_name = nil, force = false) ⇒ Object



350
351
352
353
354
355
356
357
# File 'lib/solis/graph.rb', line 350

def flush_all(graph_name=nil, force = false)
  raise Solis::Error::NotFoundError, "Supplied graph_name '#{graph_name}' does not equal graph name defined in config file '#{@graph_name}', set force to true" unless graph_name.eql?(@graph_name) && !force

  @sparql_client = SPARQL::Client.new(@sparql_endpoint)
  result = @sparql_client.query("with <#{graph_name}> delete {?s ?p ?o} where{?s ?p ?o}")
  LOGGER.info(result)
  true
end

#jsonapi_schemaObject



135
136
137
# File 'lib/solis/graph.rb', line 135

def jsonapi_schema
  Graphiti::Schema.generate.to_json
end

#list_shapesObject



127
128
129
# File 'lib/solis/graph.rb', line 127

def list_shapes
  @shapes.keys.sort
end

#shape?(key) ⇒ Boolean

Returns:

  • (Boolean)


131
132
133
# File 'lib/solis/graph.rb', line 131

def shape?(key)
  @shapes.key?(key)
end

#shape_as_model(shape_name) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/solis/graph.rb', line 139

def shape_as_model(shape_name)
  raise Solis::Error::NotFoundError, "'#{shape_name}' not found. Available classes are #{list_shapes.join(', ')}" unless shape?(shape_name)
  return Object.const_get(shape_name) if Object.const_defined?(shape_name)

  LOGGER.info("Creating model #{shape_name}")
  attributes = @shapes[shape_name][:attributes].keys.map { |m| m.to_sym }

  model = nil
  parent_model = nil
  if @shapes[shape_name].key?(:target_node) && @shapes[shape_name][:target_node].value =~ /^#{@graph_name}(.*)Shape$/
    parent_shape_model = $1
    parent_model = shape_as_model(parent_shape_model)

    model = Object.const_set(shape_name, ::Class.new(parent_model) do
      attr_accessor(*attributes)
    end)
  else
    model = Object.const_set(shape_name, ::Class.new(Solis::Model) do
      attr_accessor(*attributes)
    end)
  end

  model.graph_name = @graph_name
  model.graph_prefix = @graph_prefix
  model.shapes = @shapes
  model. = @shapes[shape_name]
  #model.language = Graphiti.context[:object]&.language || Solis::Options.instance.get[:language] || @language || 'en'
  unless parent_model.nil?
    parent_model.[:attributes].each do |k, v|
      unless model.[:attributes].key?(k)
        model.[:attributes][k] = v
      end
    end
  end
  model.sparql_endpoint = @sparql_endpoint
  model.graph = self

  model.model_before_read do |original_class|
    @default_before_read.call(original_class)
  end if @default_before_read

  model.model_after_read do |persisted_class|
    @default_after_read.call(persisted_class)
  end if @default_after_read

  model.model_before_create do |original_class|
    @default_before_create.call(original_class)
  end if @default_before_create

  model.model_after_create do |persisted_class|
    @default_after_create.call(persisted_class)
  end if @default_after_create

  model.model_before_update do |original_class, updated_class|
    @default_before_update.call(original_class, updated_class)
  end if @default_before_update

  model.model_after_update do |updated_class, persisted_class|
    @default_after_update.call(updated_class, persisted_class)
  end if @default_after_update

  model.model_before_delete do |updated_class|
    @default_before_delete.call(updated_class)
  end if @default_before_delete

  model.model_after_delete do |persisted_class|
    @default_after_delete.call(persisted_class)
  end if @default_after_delete

  model
end

#shape_as_resource(shape_name, stack_level = []) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/solis/graph.rb', line 211

def shape_as_resource(shape_name, stack_level = [])
  model = shape_as_model(shape_name)
  resource_name = "#{shape_name}Resource"

  raise Solis::Error::NotFoundError, "#{shape_name} not found. Available classes are #{list_shapes.join(', ')}" unless shape?(shape_name)
  return Object.const_get(resource_name) if Object.const_defined?(resource_name)

  LOGGER.info("Creating resource #{resource_name}")

  attributes = @shapes[shape_name][:attributes].select { |_, | .key?(:node_kind) && [:node_kind].nil? }

  relations = @shapes[shape_name][:attributes].select { |_, | .key?(:node_kind) && ![:node_kind].nil? }

  @global_resource_stack << resource_name
  relations.each_key do |k|
    next if relations[k][:node_kind].is_a?(RDF::URI) && relations[k][:class].value.gsub(@graph_name, '').gsub('Shape', '').eql?(shape_name)
    relation_shape = relations[k][:class].value.gsub(@graph_name, '').gsub('Shape', '')
    if relation_shape =~ /\//
      relation_shape = relations[k][:class].value.split('/').last.gsub('Shape','')
    end

    shape_as_resource(relation_shape, stack_level << relation_shape) unless stack_level.include?(relation_shape)
  end

  description = @shapes[shape_name][:comment]
  parent_resource = Resource
  descendants = ObjectSpace.each_object(Class).select { |klass| klass < model }.map { |m| "#{m.to_s}Resource" }

  graph = self

  #Resource
  if Object.const_defined?(resource_name)
    resource = Object.const_get(resource_name)
  else
    ###################
    # Define new resource
    resource = Object.const_set(resource_name, ::Class.new(Resource) do
      if descendants.length > 0
        self.polymorphic = descendants
        self.polymorphic << resource_name
        self.polymorphic.uniq!
      end

      self.model = model
      self.type = model.name.demodulize.underscore.pluralize.to_sym
      self.description = description

      attributes.each do |key, |
        next if key.nil? || key.empty?
        if key.eql?('id')
          attribute key.to_sym, :uuid, description: [:comment]
        else
          if ([:maxcount] && [:maxcount] > 1 || [:maxcount].nil?) && ![:boolean, :hash, :array].include?([:datatype])
            datatype = "array_of_#{[:datatype]}s".to_sym
          else
            datatype = [:datatype]
          end
          LOGGER.info "\t#{resource_name}.#{key}(#{datatype})"
          attribute key.to_sym, datatype, description: [:comment]
        end
      end
    end)

    relations.each do |key, value|
      #          next if value[:datatype].to_s.classify.eql?(shape_name) #why skip self relations...
      if (value[:mincount] && value[:mincount] > 1 || value[:mincount].nil?) || (value[:maxcount] && value[:maxcount] > 1 || value[:maxcount].nil?)
        belongs_to_resource_name = value[:datatype].nil? ? value[:class].value.gsub(self.model.graph_name, '') : value[:datatype].to_s.tableize.classify
        LOGGER.info "\t\t\t#{resource_name}(#{resource_name.gsub('Resource','').tableize.singularize}) belongs_to #{belongs_to_resource_name}(#{key})"
        resource.belongs_to(key.to_sym, foreign_key: :id, resource: graph.shape_as_resource("#{belongs_to_resource_name}", stack_level << belongs_to_resource_name)) do
          #resource.attribute key.to_sym, :string, only: [:filterable]

          link do |resource|
            remote_resources = resource.instance_variable_get("@#{key}")
            if remote_resources
              remote_resources = [remote_resources] unless remote_resources.is_a?(Array)
              resource_ids = remote_resources.map do |remote_resource|
                remote_resource.id =~ /^http/ ? remote_resource.id.split('/').last : remote_resource.id
              end

            end

            "#{resource.class.graph_name.gsub(/\/$/,'')}/#{belongs_to_resource_name.tableize}?filter[id]=#{resource_ids.join(',')}" unless remote_resources.nil? || resource_ids.empty?
          end
        end
      else
        has_many_resource_name = value[:datatype].nil? ? value[:class].gsub(self.model.graph_name, '') : value[:datatype].to_s.classify
        LOGGER.info "\t\t\t#{resource_name}(#{resource_name.gsub('Resource','').tableize.singularize}) has_many #{has_many_resource_name}(#{key})"
        resource.has_many(key.to_sym, foreign_key: :id, primary_key: :id, resource: graph.shape_as_resource("#{has_many_resource_name}", stack_level << has_many_resource_name)) do

          belongs_to_resource = graph.shape_as_resource("#{has_many_resource_name}")

          belongs_to_resource.belongs_to(resource.model.name.tableize.singularize, foreign_key: :id, primary_key: :id, resource: graph.shape_as_resource(resource.model.name)) do
            link do |resource|
              ids=[]
              remote_resources = resource.instance_variable_get("@#{shape_name.tableize.singularize}")
              if remote_resources
                remote_resources = [remote_resources] unless remote_resources.is_a?(Array)
                resource_ids = remote_resources.map do |remote_resource|
                  remote_resource.id =~ /^http/ ? remote_resource.id.split('/').last : remote_resource.id
                end
              end
              #"#{resource.class.graph_name.gsub(/\/$/,'')}/#{belongs_to_resource.name.tableize}?filter[id]=#{resource_ids.join(',')}" unless remote_resources.nil? || resource_ids.empty?
              "#{resource.class.graph_name.gsub(/\/$/,'')}/#{remote_resources.first.name.tableize}?filter[id]=#{resource_ids.join(',')}" unless remote_resources.nil? || resource_ids.empty?
            end
          end
          #
          link do |resource|
            remote_resources = resource.instance_variable_get("@#{key}")
            if remote_resources
              remote_resources = [remote_resources] unless remote_resources.is_a?(Array)
              resource_ids = remote_resources.map do |remote_resource|
                remote_resource.id =~ /^http/ ? remote_resource.id.split('/').last : remote_resource.id
              end

            end
            "#{resource.class.graph_name.gsub(/\/$/,'')}/#{remote_resources.first.name.tableize}?filter[id]=#{resource_ids.join(',')}" unless remote_resources.nil? || resource_ids.empty?
          end
        end
      end

      resource.filter :"#{key}_id", :string, single: true, only: [:eq, :not_eq] do
        eq do |scope, filter_value|
          scope[:filters][key.to_sym] = filter_value
          scope
        end

        not_eq do |scope, filter_value|
          scope[:filters][key.to_sym] = {value: [filter_value], operator: '=', is_not: true}
          scope
        end
      end

    end
  end
  resource.sparql_endpoint = @sparql_endpoint
  resource.endpoint_namespace = "#{resource.model.graph_name.gsub(/\/$/,'')}#{Solis::Options.instance.get[:base_path]}"
  resource
end