fixture_factory 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/fixture_factory.rb +47 -0
- data/lib/fixture_factory/definition.rb +84 -0
- data/lib/fixture_factory/errors.rb +43 -0
- data/lib/fixture_factory/methods.rb +89 -0
- data/lib/fixture_factory/registry.rb +82 -0
- data/lib/fixture_factory/sequence.rb +19 -0
- data/lib/fixture_factory/version.rb +5 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9970ff1a9b9427593019d53a7ae239fe9752651b9a8a8d15b684ecac2d041c93
|
4
|
+
data.tar.gz: 1842a6092e09ff6d64f25630d0c0a725c49315d2664629b1af0651935ac0970d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a5aa2e004aeabda9c7472c1e159f823b8d97ae8e9d094e82f96fbf4838270ee1f04a69172471f8dd9cc37712d4efe6ce4359e3994e9dd35b534a1982978ec248
|
7
|
+
data.tar.gz: c9533a232a9c28dd4d6ecb8356af3f49868b265766780c9d5fb4c29dc7a089907c97b45b7ba06e03d1bb498f0848d6e18245983861bd0d4efa266417939e0e88
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fixture_factory/version'
|
4
|
+
require 'fixture_factory/definition'
|
5
|
+
require 'fixture_factory/methods'
|
6
|
+
require 'fixture_factory/registry'
|
7
|
+
require 'fixture_factory/sequence'
|
8
|
+
require 'fixture_factory/errors'
|
9
|
+
|
10
|
+
module FixtureFactory
|
11
|
+
class << self
|
12
|
+
def attributes_for(name, options) # :nodoc:
|
13
|
+
_, attributes = retrieve(name, options)
|
14
|
+
attributes
|
15
|
+
end
|
16
|
+
|
17
|
+
def build(name, options) # :nodoc:
|
18
|
+
klass, attributes = retrieve(name, options)
|
19
|
+
klass.new(attributes)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(name, options) # :nodoc:
|
23
|
+
build(name, options).tap(&:save!)
|
24
|
+
end
|
25
|
+
|
26
|
+
def evaluate(block, args: [], context:) # :nodoc:
|
27
|
+
attributes = context.instance_exec(*args, &block)
|
28
|
+
extract_attributes(attributes)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def extract_attributes(object)
|
34
|
+
raise ArgumentError, "FixtureFactory blocks must return a hash-like object" unless object.respond_to?(:to_h)
|
35
|
+
object.to_h.symbolize_keys
|
36
|
+
end
|
37
|
+
|
38
|
+
def retrieve(name, scope:, context: nil, overrides: {})
|
39
|
+
definition = scope.all_factory_definitions.fetch(name) do
|
40
|
+
raise NotFoundError, name
|
41
|
+
end
|
42
|
+
attributes = definition.run(context: context)
|
43
|
+
attributes.merge!(overrides).delete(:id)
|
44
|
+
[definition.klass, attributes]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FixtureFactory
|
4
|
+
class Definition # :nodoc:
|
5
|
+
EMPTY_BLOCK = proc {}
|
6
|
+
|
7
|
+
attr_reader :klass
|
8
|
+
attr_writer :block
|
9
|
+
attr_accessor :fixture_method, :fixture_name, :parent, :sequence
|
10
|
+
|
11
|
+
def initialize(name, options = {})
|
12
|
+
self.parent = options.fetch(:parent) { default_parent_for(name) }
|
13
|
+
self.klass = options.fetch(:class) { parent.klass }
|
14
|
+
self.fixture_method = options.fetch(:via) { parent.fixture_method }
|
15
|
+
self.fixture_name = options.fetch(:like) { parent.fixture_name }
|
16
|
+
self.block = options.fetch(:block) { EMPTY_BLOCK }
|
17
|
+
self.sequence = Sequence.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def block
|
21
|
+
all_blocks = [parent&.block, @block].compact
|
22
|
+
-> (*args) do
|
23
|
+
all_blocks.reduce({}) do |attributes, block|
|
24
|
+
block_attributes = FixtureFactory.evaluate(
|
25
|
+
block, args: args, context: self
|
26
|
+
)
|
27
|
+
attributes.merge(block_attributes)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def klass=(new_class)
|
33
|
+
@klass = case new_class
|
34
|
+
when String
|
35
|
+
new_class.to_s.constantize # rubocop:disable Sorbet/ConstantsFromStrings
|
36
|
+
else
|
37
|
+
new_class
|
38
|
+
end
|
39
|
+
rescue NameError
|
40
|
+
raise WrongClassError, new_class
|
41
|
+
end
|
42
|
+
|
43
|
+
def fixture_args
|
44
|
+
[fixture_method, fixture_name]
|
45
|
+
end
|
46
|
+
|
47
|
+
def from_fixture?
|
48
|
+
fixture_name.present? && fixture_method.present?
|
49
|
+
end
|
50
|
+
|
51
|
+
def run(context:)
|
52
|
+
FixtureFactory.evaluate(runner, context: context)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def runner
|
58
|
+
definition = self
|
59
|
+
args = sequence.take(1)
|
60
|
+
-> do
|
61
|
+
attributes = FixtureFactory.evaluate(definition.block, args: args, context: self)
|
62
|
+
if definition.from_fixture?
|
63
|
+
begin
|
64
|
+
fixture = send(*definition.fixture_args)
|
65
|
+
attributes.reverse_merge!(fixture.attributes)
|
66
|
+
rescue NoMethodError
|
67
|
+
raise WrongFixtureMethodError, definition.fixture_args.first
|
68
|
+
end
|
69
|
+
end
|
70
|
+
attributes
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_parent_for(name)
|
75
|
+
self.class.new(
|
76
|
+
name,
|
77
|
+
parent: nil,
|
78
|
+
like: nil,
|
79
|
+
class: name.to_s.classify.safe_constantize,
|
80
|
+
via: name.to_s.pluralize,
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FixtureFactory
|
4
|
+
class Error < StandardError # :nodoc:
|
5
|
+
end
|
6
|
+
|
7
|
+
# Raised when a factory is referenced, but not defined.
|
8
|
+
class NotFoundError < Error
|
9
|
+
def initialize(fixture_name)
|
10
|
+
super(
|
11
|
+
<<~MSG.squish
|
12
|
+
No factory named "#{fixture_name}".
|
13
|
+
Did you forget to define it?
|
14
|
+
https://github.com/Shopify/fixture_factory/blob/master/README.md#definition
|
15
|
+
MSG
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class WrongFixtureMethodError < Error
|
21
|
+
def initialize(method_name)
|
22
|
+
super(
|
23
|
+
<<~MSG.squish
|
24
|
+
No fixture method named "#{method_name}".
|
25
|
+
Try using the `via` option in your definition to specify a valid method.
|
26
|
+
https://github.com/Shopify/fixture_factory/blob/master/README.md#naming
|
27
|
+
MSG
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class WrongClassError < Error
|
33
|
+
def initialize(class_name)
|
34
|
+
super(
|
35
|
+
<<~MSG.squish
|
36
|
+
No class named "#{class_name}".
|
37
|
+
Try using the `class` option in your definition to specify a valid class name.
|
38
|
+
https://github.com/Shopify/fixture_factory/blob/master/README.md#naming
|
39
|
+
MSG
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FixtureFactory
|
4
|
+
module Methods
|
5
|
+
# Generates a hash of attributes given a factory name and an optional
|
6
|
+
# hash of override attributes.
|
7
|
+
#
|
8
|
+
# === Example
|
9
|
+
#
|
10
|
+
# attributes_for(:user)
|
11
|
+
# attributes_for(:blog, title: 'Riding Rails')
|
12
|
+
# attributes_for(:comment, content: 'Hello', approved: false)
|
13
|
+
# attributes_for(:post, comments_attributes: [attributes_for(:comment)])
|
14
|
+
def attributes_for(name, overrides = {})
|
15
|
+
FixtureFactory.attributes_for(
|
16
|
+
name, overrides: overrides, context: self, scope: self.class
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Generates an array of hash attributes given a factory name, a count,
|
21
|
+
# and an optional hash of override attributes.
|
22
|
+
#
|
23
|
+
# === Example
|
24
|
+
#
|
25
|
+
# attributes_for_list(:user, 5)
|
26
|
+
# attributes_for_list(:blog, 3, title: 'Riding Rails')
|
27
|
+
# attributes_for_list(:comment, 50, content: 'Hello', approved: false)
|
28
|
+
# attributes_for_list(:post, 3, comments_attributes: attributes_for_list(:comment, 1))
|
29
|
+
def attributes_for_list(name, count, overrides = {})
|
30
|
+
count.times.map { attributes_for(name, overrides) }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Generates an instance of a model given a factory name and an optional
|
34
|
+
# hash of override attributes.
|
35
|
+
#
|
36
|
+
# === Example
|
37
|
+
#
|
38
|
+
# build(:user)
|
39
|
+
# build(:blog, title: 'Riding Rails')
|
40
|
+
# build(:comment, content: 'Hello', approved: false)
|
41
|
+
# build(:post, comments: [build(:comment)])
|
42
|
+
def build(name, overrides = {})
|
43
|
+
FixtureFactory.build(
|
44
|
+
name, overrides: overrides, context: self, scope: self.class
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Generates an array of model instances given a factory name and an optional
|
49
|
+
# hash of override attributes.
|
50
|
+
#
|
51
|
+
# === Example
|
52
|
+
#
|
53
|
+
# build_list(:user, 5)
|
54
|
+
# build_list(:blog, 3, title: 'Riding Rails')
|
55
|
+
# build_list(:comment, 50, content: 'Hello', approved: false)
|
56
|
+
# build_list(:post, 3, comments: build_list(:comment, 1))
|
57
|
+
def build_list(name, count, overrides = {})
|
58
|
+
count.times.map { build(name, overrides) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Generates a persisted model instance given a factory name and an optional
|
62
|
+
# hash of override attributes.
|
63
|
+
#
|
64
|
+
# === Example
|
65
|
+
#
|
66
|
+
# create(:user)
|
67
|
+
# create(:blog, title: 'Riding Rails')
|
68
|
+
# create(:comment, content: 'Hello', approved: false)
|
69
|
+
# create(:post, comments: [create(:comment)])
|
70
|
+
def create(name, overrides = {})
|
71
|
+
FixtureFactory.create(
|
72
|
+
name, overrides: overrides, context: self, scope: self.class
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Generates an array of persisted model instances given a factory name and
|
77
|
+
# an optional hash of override attributes.
|
78
|
+
#
|
79
|
+
# === Example
|
80
|
+
#
|
81
|
+
# create_list(:user, 5)
|
82
|
+
# create_list(:blog, 3, title: 'Riding Rails')
|
83
|
+
# create_list(:comment, 50, content: 'Hello', approved: false)
|
84
|
+
# create_list(:post, 3, comments: create_list(:comment, 1))
|
85
|
+
def create_list(name, count, overrides = {})
|
86
|
+
count.times.map { create(name, overrides) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/indifferent_access"
|
4
|
+
|
5
|
+
module FixtureFactory
|
6
|
+
module Registry
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute(:fixture_factory_definitions) # :nodoc:
|
11
|
+
end
|
12
|
+
|
13
|
+
class_methods do
|
14
|
+
# Defines a fixture given a name, options, and a block.
|
15
|
+
#
|
16
|
+
# === Options
|
17
|
+
#
|
18
|
+
# [:parent]
|
19
|
+
# Specify a parent fixture to inherit options from. If no parent is specified,
|
20
|
+
# a default parent will be assumed that guesses class a via options.
|
21
|
+
# [:class]
|
22
|
+
# Specify a class (typically as a string to prevent autoloading) to instantiate
|
23
|
+
# when building or creating models from fixturies. This option is guessed
|
24
|
+
# using the classified fixture name.
|
25
|
+
# [:via]
|
26
|
+
# Specify a fixture method to call when generating data. Only necessary when
|
27
|
+
# the `like` option is used to specify a fixture name. This option is guessed
|
28
|
+
# by default using the pluralized fixture name.
|
29
|
+
# [:like]
|
30
|
+
# Specify a fixture to use when generating data. Used in combination with
|
31
|
+
# `via` to extract attributes from a fixture.
|
32
|
+
# [:block]
|
33
|
+
# Specify a block to call when running a fixture. This option will default to
|
34
|
+
# a passed block or an empty block if neither are provided.
|
35
|
+
#
|
36
|
+
# === Example
|
37
|
+
#
|
38
|
+
# define_factories do
|
39
|
+
# fixture(:user, like: :bob)
|
40
|
+
# fixture(:active_user, parent: :user) do
|
41
|
+
# { active: true }
|
42
|
+
# end
|
43
|
+
# fixture(:cool_user, class: 'User') do
|
44
|
+
# { status: :cool }
|
45
|
+
# end
|
46
|
+
# fixture(:admin, via: :users, like: :bob_admin, class: 'User')
|
47
|
+
# end
|
48
|
+
def fixture(name, options = {}, &block)
|
49
|
+
parent = all_factory_definitions[options[:parent]]
|
50
|
+
options[:parent] = parent if options.key?(:parent)
|
51
|
+
options[:block] = block if block
|
52
|
+
fixture_factory_definitions[name] = Definition.new(name, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Sets up factories definitions for the current class scope. Accepts an
|
56
|
+
# optional block to define factories in.
|
57
|
+
#
|
58
|
+
# === Example
|
59
|
+
#
|
60
|
+
# class SomeTest < ActiveSupport::TestCase
|
61
|
+
# define_factories do
|
62
|
+
# fixture(:blog)
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# SomeTest.define_factories do
|
67
|
+
# fixture(:post)
|
68
|
+
# end
|
69
|
+
def define_factories(&block)
|
70
|
+
self.fixture_factory_definitions = {}.with_indifferent_access
|
71
|
+
instance_exec(&block) if block.present?
|
72
|
+
end
|
73
|
+
|
74
|
+
def all_factory_definitions # :nodoc:
|
75
|
+
fixtury_ancestors = ancestors.select do |ancestor|
|
76
|
+
ancestor.respond_to?(:fixture_factory_definitions)
|
77
|
+
end
|
78
|
+
[self, *fixtury_ancestors].map(&:fixture_factory_definitions).reduce(:reverse_merge)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FixtureFactory
|
4
|
+
class Sequence # :nodoc:
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@count = 0
|
9
|
+
end
|
10
|
+
|
11
|
+
def next
|
12
|
+
@count += 1
|
13
|
+
end
|
14
|
+
|
15
|
+
def each
|
16
|
+
loop { yield(self.next) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fixture_factory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Shopify Inc.
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-03-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: sqlite3
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description:
|
126
|
+
email:
|
127
|
+
- gems@shopify.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- lib/fixture_factory.rb
|
133
|
+
- lib/fixture_factory/definition.rb
|
134
|
+
- lib/fixture_factory/errors.rb
|
135
|
+
- lib/fixture_factory/methods.rb
|
136
|
+
- lib/fixture_factory/registry.rb
|
137
|
+
- lib/fixture_factory/sequence.rb
|
138
|
+
- lib/fixture_factory/version.rb
|
139
|
+
homepage:
|
140
|
+
licenses: []
|
141
|
+
metadata: {}
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubygems_version: 3.0.3
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: Factories via fixtures
|
161
|
+
test_files: []
|