riddler 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +105 -6
- data/lib/riddler.rb +52 -3
- data/lib/riddler/configuration.rb +11 -0
- data/lib/riddler/context.rb +40 -2
- data/lib/riddler/context_builder.rb +35 -0
- data/lib/riddler/context_builders/faraday_builder.rb +23 -0
- data/lib/riddler/context_builders/user_agent.rb +34 -0
- data/lib/riddler/context_director.rb +67 -0
- data/lib/riddler/drops/hash_drop.rb +29 -0
- data/lib/riddler/element.rb +15 -14
- data/lib/riddler/elements/heading.rb +0 -1
- data/lib/riddler/elements/image.rb +21 -0
- data/lib/riddler/elements/link.rb +30 -0
- data/lib/riddler/elements/text.rb +17 -0
- data/lib/riddler/elements/variant.rb +21 -0
- data/lib/riddler/includeable.rb +19 -0
- data/lib/riddler/protobuf/content_definition.proto +51 -0
- data/lib/riddler/protobuf/content_definition_pb.rb +33 -0
- data/lib/riddler/protobuf/content_management.proto +35 -0
- data/lib/riddler/protobuf/content_management_pb.rb +43 -0
- data/lib/riddler/protobuf/content_management_services_pb.rb +27 -0
- data/lib/riddler/protobuf/slug.proto +61 -0
- data/lib/riddler/protobuf/slug_pb.rb +52 -0
- data/lib/riddler/step.rb +16 -16
- data/lib/riddler/steps/content.rb +5 -5
- data/lib/riddler/steps/variant.rb +23 -0
- data/lib/riddler/test_generator.rb +73 -0
- data/lib/riddler/use_cases.rb +7 -0
- data/lib/riddler/use_cases/admin_preview_step.rb +32 -0
- data/lib/riddler/use_cases/complete_interaction.rb +20 -0
- data/lib/riddler/use_cases/dismiss_interaction.rb +20 -0
- data/lib/riddler/use_cases/preview_context.rb +28 -0
- data/lib/riddler/use_cases/preview_step.rb +16 -5
- data/lib/riddler/use_cases/show_content_definition.rb +42 -0
- data/lib/riddler/use_cases/show_slug.rb +138 -0
- data/lib/riddler/version.rb +1 -1
- data/riddler.gemspec +12 -8
- metadata +100 -10
- data/.gitignore +0 -8
- data/.travis.yml +0 -7
- data/Gemfile +0 -6
- data/Gemfile.lock +0 -58
- data/Rakefile +0 -10
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,52 @@
|
|
1
|
+
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
2
|
+
# source: riddler/protobuf/slug.proto
|
3
|
+
|
4
|
+
require 'google/protobuf'
|
5
|
+
|
6
|
+
require 'google/protobuf/timestamp_pb'
|
7
|
+
Google::Protobuf::DescriptorPool.generated_pool.build do
|
8
|
+
add_message "riddler.protobuf.Slug" do
|
9
|
+
optional :id, :string, 1
|
10
|
+
optional :created_at, :message, 2, "google.protobuf.Timestamp"
|
11
|
+
optional :updated_at, :message, 3, "google.protobuf.Timestamp"
|
12
|
+
optional :name, :string, 4
|
13
|
+
optional :status, :enum, 5, "riddler.protobuf.SlugStatus"
|
14
|
+
optional :content_definition_id, :string, 6
|
15
|
+
optional :interaction_identity, :string, 7
|
16
|
+
optional :target_predicate, :string, 8
|
17
|
+
end
|
18
|
+
add_message "riddler.protobuf.EventCount" do
|
19
|
+
optional :event_name, :string, 1
|
20
|
+
optional :count, :int32, 2
|
21
|
+
end
|
22
|
+
add_message "riddler.protobuf.SlugStats" do
|
23
|
+
optional :interval, :enum, 1, "riddler.protobuf.Interval"
|
24
|
+
repeated :event_counts, :message, 2, "riddler.protobuf.EventCount"
|
25
|
+
end
|
26
|
+
add_enum "riddler.protobuf.SlugStatus" do
|
27
|
+
value :UNKNOWN_SLUG_STATUS, 0
|
28
|
+
value :LIVE, 1
|
29
|
+
value :PAUSED, 2
|
30
|
+
end
|
31
|
+
add_enum "riddler.protobuf.Interval" do
|
32
|
+
value :UNKNOWN_INTERVAL, 0
|
33
|
+
value :SECOND, 1
|
34
|
+
value :MINUTE, 2
|
35
|
+
value :HOUR, 3
|
36
|
+
value :DAY, 4
|
37
|
+
value :WEEK, 5
|
38
|
+
value :MONTH, 6
|
39
|
+
value :QUARTER, 7
|
40
|
+
value :YEAR, 8
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Riddler
|
45
|
+
module Protobuf
|
46
|
+
Slug = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.Slug").msgclass
|
47
|
+
EventCount = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.EventCount").msgclass
|
48
|
+
SlugStats = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.SlugStats").msgclass
|
49
|
+
SlugStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.SlugStatus").enummodule
|
50
|
+
Interval = Google::Protobuf::DescriptorPool.generated_pool.lookup("riddler.protobuf.Interval").enummodule
|
51
|
+
end
|
52
|
+
end
|
data/lib/riddler/step.rb
CHANGED
@@ -1,14 +1,21 @@
|
|
1
1
|
module Riddler
|
2
|
-
|
3
2
|
class Step
|
4
|
-
|
3
|
+
include ::Riddler::Includeable
|
4
|
+
|
5
|
+
attr_reader :definition, :context, :preview_enabled
|
6
|
+
|
7
|
+
def self.subclasses
|
8
|
+
@@subclasses ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.inherited subclass
|
12
|
+
self.subclasses << subclass
|
13
|
+
end
|
5
14
|
|
6
15
|
def self.for definition, context
|
7
|
-
|
8
|
-
step_type = definition["object"]
|
16
|
+
step_type = definition["type"]
|
9
17
|
|
10
|
-
|
11
|
-
klass = subclasses.detect { |klass| klass.type == step_type }
|
18
|
+
klass = subclasses.detect { |k| k.type == step_type }
|
12
19
|
|
13
20
|
klass.new definition, context
|
14
21
|
end
|
@@ -18,20 +25,13 @@ module Riddler
|
|
18
25
|
@context = context
|
19
26
|
end
|
20
27
|
|
21
|
-
#def id
|
22
|
-
# definition["id"]
|
23
|
-
#end
|
24
|
-
|
25
|
-
#def type
|
26
|
-
# self.class.type
|
27
|
-
#end
|
28
|
-
|
29
28
|
def to_hash
|
30
29
|
{
|
30
|
+
content_type: "step",
|
31
31
|
type: self.class.type,
|
32
|
-
id: definition["id"]
|
32
|
+
id: definition["id"],
|
33
|
+
name: definition["name"]
|
33
34
|
}
|
34
35
|
end
|
35
36
|
end
|
36
|
-
|
37
37
|
end
|
@@ -1,15 +1,16 @@
|
|
1
1
|
module Riddler
|
2
2
|
module Steps
|
3
|
-
|
4
3
|
class Content < ::Riddler::Step
|
5
4
|
def self.type
|
6
5
|
"content"
|
7
6
|
end
|
8
7
|
|
9
8
|
def elements
|
10
|
-
@elements ||= definition["elements"]
|
11
|
-
|
12
|
-
|
9
|
+
@elements ||= definition["elements"]
|
10
|
+
.map do |element_definition|
|
11
|
+
::Riddler::Element.for element_definition, context
|
12
|
+
end
|
13
|
+
.select(&:include?)
|
13
14
|
end
|
14
15
|
|
15
16
|
def to_hash
|
@@ -20,6 +21,5 @@ module Riddler
|
|
20
21
|
hash
|
21
22
|
end
|
22
23
|
end
|
23
|
-
|
24
24
|
end
|
25
25
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Riddler
|
2
|
+
module Steps
|
3
|
+
class Variant < ::Riddler::Step
|
4
|
+
def self.type
|
5
|
+
"variant"
|
6
|
+
end
|
7
|
+
|
8
|
+
def steps
|
9
|
+
@steps ||= definition["steps"].map do |hash|
|
10
|
+
::Riddler::Step.for hash, context
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def included_step
|
15
|
+
steps.detect &:include?
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_hash
|
19
|
+
included_step&.to_hash
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module ::Riddler
|
4
|
+
class TestGenerator
|
5
|
+
include ERB::Util
|
6
|
+
attr_reader :project_root, :input_filename, :test_case
|
7
|
+
|
8
|
+
def initialize project_root, input_filename
|
9
|
+
@project_root = project_root
|
10
|
+
@input_filename = input_filename
|
11
|
+
@test_case = YAML.safe_load File.read input_filename
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_case_name
|
15
|
+
test_case["name"]
|
16
|
+
end
|
17
|
+
|
18
|
+
def class_name
|
19
|
+
classify test_case_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def definition
|
23
|
+
test_case["definition"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def tests
|
27
|
+
test_case["tests"]
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate
|
31
|
+
folder = input_filename.dirname.basename.to_s
|
32
|
+
output_filename = project_root.join *%W[ test cases #{folder} #{test_case_name}_test.rb ]
|
33
|
+
puts "Generating #{output_filename.relative_path_from(project_root)}"
|
34
|
+
output_filename.dirname.mkdir unless output_filename.dirname.exist?
|
35
|
+
File.write output_filename, render
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def render
|
41
|
+
ERB.new(test_class_template).result(binding)
|
42
|
+
end
|
43
|
+
|
44
|
+
def classify string
|
45
|
+
string.split('_').collect!{ |w| w.capitalize }.join
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_class_template
|
49
|
+
<<~TEMPLATE
|
50
|
+
require "test_helper"
|
51
|
+
|
52
|
+
class <%= class_name %>Test < ::Minitest::Test
|
53
|
+
attr_reader :definition
|
54
|
+
|
55
|
+
def setup
|
56
|
+
@definition = <%= definition %>
|
57
|
+
end
|
58
|
+
<% tests.each do |test| %>
|
59
|
+
def test_<%= test["name"] %>
|
60
|
+
expected_result = <% if test["result"].nil? %>nil<% else %><%= test["result"] %><% end %>
|
61
|
+
context = <% if test["context"].nil? %>nil<% else %><%= test["context"] %><% end %>
|
62
|
+
|
63
|
+
result = ::Riddler.render definition, context
|
64
|
+
result&.transform_keys! { |k| k.to_s }
|
65
|
+
|
66
|
+
assert_equal_hash expected_result, result
|
67
|
+
end
|
68
|
+
<% end %>
|
69
|
+
end
|
70
|
+
TEMPLATE
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require "riddler/use_cases/admin_preview_step"
|
2
|
+
require "riddler/use_cases/complete_interaction"
|
3
|
+
require "riddler/use_cases/dismiss_interaction"
|
4
|
+
require "riddler/use_cases/preview_context"
|
5
|
+
require "riddler/use_cases/preview_step"
|
6
|
+
require "riddler/use_cases/show_content_definition"
|
7
|
+
require "riddler/use_cases/show_slug"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class AdminPreviewStep
|
4
|
+
attr_reader :definition, :preview_context_data, :context, :step
|
5
|
+
|
6
|
+
def initialize definition, preview_context_data: {}
|
7
|
+
@definition = definition
|
8
|
+
@preview_context_data = preview_context_data
|
9
|
+
@context = ::Riddler::Context.new preview_context_data
|
10
|
+
@step = ::Riddler::Step.for definition, context
|
11
|
+
end
|
12
|
+
|
13
|
+
def process
|
14
|
+
if step.include?
|
15
|
+
hash = step.to_hash
|
16
|
+
return hash unless hash.nil?
|
17
|
+
|
18
|
+
{
|
19
|
+
response_code: 204,
|
20
|
+
message: "There was no step to include"
|
21
|
+
}
|
22
|
+
else
|
23
|
+
{
|
24
|
+
response_code: 204,
|
25
|
+
include_predicate: step.include_predicate,
|
26
|
+
message: "Excluded - the include_predicate returned false"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class CompleteInteraction
|
4
|
+
attr_reader :interaction_repo, :interaction_id
|
5
|
+
|
6
|
+
def initialize interaction_repo:, interaction_id:
|
7
|
+
@interaction_repo = interaction_repo
|
8
|
+
@interaction_id = interaction_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def process
|
12
|
+
interaction = interaction_repo.find_by id: interaction_id
|
13
|
+
return if interaction.nil?
|
14
|
+
|
15
|
+
interaction.complete
|
16
|
+
interaction_repo.update interaction
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class DismissInteraction
|
4
|
+
attr_reader :interaction_repo, :interaction_id, :interaction
|
5
|
+
|
6
|
+
def initialize interaction_repo:, interaction_id:
|
7
|
+
@interaction_repo = interaction_repo
|
8
|
+
@interaction_id = interaction_id
|
9
|
+
end
|
10
|
+
|
11
|
+
def process
|
12
|
+
@interaction = interaction_repo.find_by id: interaction_id
|
13
|
+
return if interaction.nil?
|
14
|
+
|
15
|
+
interaction.dismiss
|
16
|
+
interaction_repo.update interaction
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class PreviewContext
|
4
|
+
attr_reader :params, :headers
|
5
|
+
|
6
|
+
def initialize params: {}, headers: {}
|
7
|
+
@params = params
|
8
|
+
@headers = headers
|
9
|
+
end
|
10
|
+
|
11
|
+
def context
|
12
|
+
@context ||= generate_context
|
13
|
+
end
|
14
|
+
|
15
|
+
def process
|
16
|
+
context.to_hash
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def generate_context
|
22
|
+
director = ::Riddler::ContextDirector.new params: params,
|
23
|
+
headers: headers
|
24
|
+
director.context
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -1,19 +1,30 @@
|
|
1
1
|
module Riddler
|
2
2
|
module UseCases
|
3
|
-
|
4
3
|
class PreviewStep
|
5
|
-
attr_reader :definition, :
|
4
|
+
attr_reader :definition, :params, :headers, :step
|
6
5
|
|
7
|
-
def initialize definition,
|
6
|
+
def initialize definition, params: {}, headers: {}
|
8
7
|
@definition = definition
|
9
|
-
@
|
8
|
+
@params = params
|
9
|
+
@headers = headers
|
10
10
|
@step = ::Riddler::Step.for definition, context
|
11
11
|
end
|
12
12
|
|
13
|
+
def context
|
14
|
+
@context ||= generate_context
|
15
|
+
end
|
16
|
+
|
13
17
|
def process
|
14
18
|
step.to_hash
|
15
19
|
end
|
16
|
-
end
|
17
20
|
|
21
|
+
private
|
22
|
+
|
23
|
+
def generate_context
|
24
|
+
director = ::Riddler::ContextDirector.new params: params,
|
25
|
+
headers: headers
|
26
|
+
director.context
|
27
|
+
end
|
28
|
+
end
|
18
29
|
end
|
19
30
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class ShowContentDefinition
|
4
|
+
attr_reader :content_definition_repo,
|
5
|
+
:content_definition_id, :context_director,
|
6
|
+
:content_definition, :step
|
7
|
+
|
8
|
+
def initialize content_definition_repo:, content_definition_id:, context_director:
|
9
|
+
@content_definition_repo = content_definition_repo
|
10
|
+
@content_definition_id = content_definition_id
|
11
|
+
@context_director = context_director
|
12
|
+
|
13
|
+
@content_definition = lookup_content_definition
|
14
|
+
@step = ::Riddler::Step.for content_definition.definition, context
|
15
|
+
end
|
16
|
+
|
17
|
+
def context
|
18
|
+
context_director.context
|
19
|
+
end
|
20
|
+
|
21
|
+
def process
|
22
|
+
step.to_hash
|
23
|
+
end
|
24
|
+
|
25
|
+
def excluded?
|
26
|
+
!step.include?
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def lookup_content_definition
|
32
|
+
content_definition_repo.find_by id: content_definition_id
|
33
|
+
end
|
34
|
+
|
35
|
+
def generate_context
|
36
|
+
director = ::Riddler::ContextDirector.new params: params,
|
37
|
+
headers: headers
|
38
|
+
director.context
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Riddler
|
2
|
+
module UseCases
|
3
|
+
class ShowSlug
|
4
|
+
attr_reader :content_definition_repo, :slug_repo, :interaction_repo,
|
5
|
+
:interaction_class, :slug_name, :context_director, :slug, :interaction
|
6
|
+
|
7
|
+
def initialize content_definition_repo:, slug_repo:, interaction_repo:,
|
8
|
+
interaction_class:, slug_name:, params: {}, headers: {}
|
9
|
+
|
10
|
+
@content_definition_repo = content_definition_repo
|
11
|
+
@slug_repo = slug_repo
|
12
|
+
@interaction_repo = interaction_repo
|
13
|
+
@interaction_class = interaction_class
|
14
|
+
@context_director = ::Riddler::ContextDirector.new params: params, headers: headers
|
15
|
+
@slug_name = slug_name
|
16
|
+
@slug = lookup_slug
|
17
|
+
end
|
18
|
+
|
19
|
+
def exists?
|
20
|
+
!slug.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def paused?
|
24
|
+
return true if slug.nil?
|
25
|
+
slug.status == "PAUSED"
|
26
|
+
end
|
27
|
+
|
28
|
+
def dismissed?
|
29
|
+
return false unless slug_defines_identity?
|
30
|
+
find_interaction
|
31
|
+
return false if @interaction.nil?
|
32
|
+
@interaction.status == "DISMISSED"
|
33
|
+
end
|
34
|
+
|
35
|
+
def completed?
|
36
|
+
return false unless slug_defines_identity?
|
37
|
+
find_interaction
|
38
|
+
return false if @interaction.nil?
|
39
|
+
@interaction.status == "COMPLETED"
|
40
|
+
end
|
41
|
+
|
42
|
+
def targeted?
|
43
|
+
slug_targeted?
|
44
|
+
end
|
45
|
+
|
46
|
+
def excluded?
|
47
|
+
definition_use_case.excluded?
|
48
|
+
end
|
49
|
+
|
50
|
+
def process
|
51
|
+
find_interaction || create_interaction
|
52
|
+
context.assign "interaction", interaction.to_hash
|
53
|
+
|
54
|
+
content_hash = definition_use_case.process.merge \
|
55
|
+
interaction_id: interaction.id,
|
56
|
+
dismiss_url: "/interactions/#{interaction.id}/dismiss"
|
57
|
+
|
58
|
+
interaction.content_type = content_hash[:content_type]
|
59
|
+
interaction.content_id = content_hash[:id]
|
60
|
+
|
61
|
+
interaction_repo.update interaction
|
62
|
+
|
63
|
+
content_hash
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def find_interaction
|
69
|
+
return nil unless request_is_unique?
|
70
|
+
|
71
|
+
@interaction ||= interaction_repo.last_by slug_id: slug.id,
|
72
|
+
identity: identity
|
73
|
+
end
|
74
|
+
|
75
|
+
def create_interaction
|
76
|
+
@interaction = interaction_class.new \
|
77
|
+
slug_name: slug.name,
|
78
|
+
slug_id: slug.id,
|
79
|
+
status: "ACTIVE",
|
80
|
+
content_definition_id: slug.content_definition_id,
|
81
|
+
identifiers: context.ids
|
82
|
+
|
83
|
+
@interaction.identity = identity if request_is_unique?
|
84
|
+
|
85
|
+
interaction_repo.create interaction
|
86
|
+
end
|
87
|
+
|
88
|
+
def definition_use_case
|
89
|
+
@definition_use_case ||= ShowContentDefinition.new \
|
90
|
+
content_definition_repo: content_definition_repo,
|
91
|
+
content_definition_id: slug.content_definition_id,
|
92
|
+
context_director: context_director
|
93
|
+
end
|
94
|
+
|
95
|
+
def context
|
96
|
+
context_director.context
|
97
|
+
end
|
98
|
+
|
99
|
+
# Only has IDs extracted - context builders have not been processed
|
100
|
+
def simple_context
|
101
|
+
context_director.simple_context
|
102
|
+
end
|
103
|
+
|
104
|
+
def identity
|
105
|
+
@identity ||= simple_context.render slug.interaction_identity
|
106
|
+
end
|
107
|
+
|
108
|
+
def blank_interaction_identity
|
109
|
+
@blank_interaction_identity ||= ::Riddler::Context.new.render slug.interaction_identity
|
110
|
+
end
|
111
|
+
|
112
|
+
def request_is_unique?
|
113
|
+
slug_defines_identity? && !interaction_identity_is_blank?
|
114
|
+
end
|
115
|
+
|
116
|
+
def interaction_identity_is_blank?
|
117
|
+
identity == blank_interaction_identity
|
118
|
+
end
|
119
|
+
|
120
|
+
def slug_defines_identity?
|
121
|
+
!(slug.interaction_identity.nil? || slug.interaction_identity == "")
|
122
|
+
end
|
123
|
+
|
124
|
+
def lookup_slug
|
125
|
+
slug_repo.find_by name: slug_name
|
126
|
+
end
|
127
|
+
|
128
|
+
def slug_targeted?
|
129
|
+
return true unless slug_has_target_predicate?
|
130
|
+
Predicator.evaluate slug.target_predicate, context.to_liquid
|
131
|
+
end
|
132
|
+
|
133
|
+
def slug_has_target_predicate?
|
134
|
+
slug.target_predicate.to_s.strip != ""
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|