graphql-rb 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +26 -0
  3. data/lib/graphql/configuration/configurable.rb +46 -0
  4. data/lib/graphql/configuration/configuration.rb +92 -0
  5. data/lib/graphql/configuration/slot.rb +124 -0
  6. data/lib/graphql/configuration.rb +3 -0
  7. data/lib/graphql/errors/error.rb +3 -0
  8. data/lib/graphql/errors.rb +1 -0
  9. data/lib/graphql/executor.rb +83 -0
  10. data/lib/graphql/introspection/meta_fields.rb +54 -0
  11. data/lib/graphql/introspection/query.rb +81 -0
  12. data/lib/graphql/introspection/schema.rb +158 -0
  13. data/lib/graphql/introspection.rb +3 -0
  14. data/lib/graphql/language/argument.rb +11 -0
  15. data/lib/graphql/language/directive.rb +5 -0
  16. data/lib/graphql/language/document.rb +23 -0
  17. data/lib/graphql/language/field.rb +55 -0
  18. data/lib/graphql/language/fragment_definition.rb +23 -0
  19. data/lib/graphql/language/fragment_spread.rb +5 -0
  20. data/lib/graphql/language/inline_fragment.rb +23 -0
  21. data/lib/graphql/language/list_type.rb +15 -0
  22. data/lib/graphql/language/name.rb +5 -0
  23. data/lib/graphql/language/named_type.rb +15 -0
  24. data/lib/graphql/language/non_null_type.rb +15 -0
  25. data/lib/graphql/language/operation_definition.rb +33 -0
  26. data/lib/graphql/language/parser.rb +331 -0
  27. data/lib/graphql/language/selection_set.rb +107 -0
  28. data/lib/graphql/language/transform.rb +101 -0
  29. data/lib/graphql/language/value.rb +24 -0
  30. data/lib/graphql/language/variable.rb +11 -0
  31. data/lib/graphql/language/variable_definition.rb +34 -0
  32. data/lib/graphql/language.rb +40 -0
  33. data/lib/graphql/type/argument.rb +16 -0
  34. data/lib/graphql/type/directive.rb +37 -0
  35. data/lib/graphql/type/directives.rb +25 -0
  36. data/lib/graphql/type/enum_type.rb +100 -0
  37. data/lib/graphql/type/field.rb +50 -0
  38. data/lib/graphql/type/input_object_type.rb +47 -0
  39. data/lib/graphql/type/interface_type.rb +64 -0
  40. data/lib/graphql/type/list.rb +23 -0
  41. data/lib/graphql/type/non_null.rb +25 -0
  42. data/lib/graphql/type/object_type.rb +57 -0
  43. data/lib/graphql/type/scalar_type.rb +137 -0
  44. data/lib/graphql/type/schema.rb +49 -0
  45. data/lib/graphql/type/union_type.rb +39 -0
  46. data/lib/graphql/type.rb +82 -0
  47. data/lib/graphql/validator.rb +43 -0
  48. data/lib/graphql/version.rb +3 -0
  49. data/lib/graphql.rb +21 -0
  50. data/spec/configuration/configuration_spec.rb +4 -0
  51. data/spec/data.rb +89 -0
  52. data/spec/introspection/full_spec.rb +12 -0
  53. data/spec/introspection/simple_spec.rb +153 -0
  54. data/spec/language/parser_spec.rb +73 -0
  55. data/spec/schema.rb +145 -0
  56. data/spec/spec_helper.rb +99 -0
  57. data/spec/type/enum_spec.rb +27 -0
  58. data/spec/type/input_object_spec.rb +21 -0
  59. data/spec/type/list_spec.rb +16 -0
  60. data/spec/type/non_null_spec.rb +22 -0
  61. data/spec/type/scalar_type_spec.rb +117 -0
  62. data/spec/type/schema_spec.rb +13 -0
  63. metadata +202 -0
@@ -0,0 +1,101 @@
1
+ require 'parslet'
2
+
3
+ module GraphQL
4
+ module Language
5
+
6
+ class Transform < Parslet::Transform
7
+
8
+
9
+ rule(document: sequence(:a)) { Document.new(a) }
10
+
11
+
12
+ rule(operation_definition: {
13
+ type: simple(:a),
14
+ name: simple(:b),
15
+ variable_definitions: subtree(:c),
16
+ directives: subtree(:d),
17
+ selection_set: simple(:e)
18
+ }) { OperationDefinition.new(a, b, c || [], d || [], e || []) }
19
+
20
+ rule(operation_definition: {
21
+ selection_set: simple(:a)
22
+ }) { OperationDefinition.new('query', '', [], [], a) }
23
+
24
+
25
+ rule(fragment_definition: {
26
+ name: simple(:a),
27
+ type_condition: subtree(:b),
28
+ directives: subtree(:c),
29
+ selection_set: simple(:d)
30
+ }) { FragmentDefinition.new(a, b, c || [], d || []) }
31
+
32
+
33
+ rule(fragment_spread: {
34
+ name: simple(:a),
35
+ directives: subtree(:b)
36
+ }) { FragmentSpread.new(a, b || []) }
37
+
38
+
39
+ rule(inline_fragment: {
40
+ type_condition: subtree(:a),
41
+ directives: subtree(:b),
42
+ selection_set: simple(:c)
43
+ }) { InlineFragment.new(a, b || [], c || []) }
44
+
45
+
46
+ rule(field: {
47
+ alias: simple(:a),
48
+ name: simple(:b),
49
+ arguments: subtree(:c),
50
+ directives: subtree(:d),
51
+ selection_set: simple(:e)
52
+ }) { Field.new(a, b, c || [], d || [], e || []) }
53
+
54
+
55
+ rule(directive: {
56
+ name: simple(:a),
57
+ arguments: subtree(:b)
58
+ }) { Directive.new(a, b || []) }
59
+
60
+
61
+ rule(argument: {
62
+ name: simple(:a),
63
+ value: simple(:b)
64
+ }) { Argument.new(a, b) }
65
+
66
+
67
+ rule(selection_set: {
68
+ selections: subtree(:a)
69
+ }) { SelectionSet.new(a || []) }
70
+
71
+
72
+ rule(variable_definition: {
73
+ variable: simple(:a),
74
+ type: simple(:b),
75
+ default_value: simple(:c)
76
+ }) { VariableDefinition.new(a, b, c) }
77
+
78
+
79
+ rule(variable: simple(:a)) { Variable.new(a) }
80
+
81
+ rule(named_type: simple(:a)) { NamedType.new(a) }
82
+ rule(list_type: simple(:a)) { ListType.new(a) }
83
+ rule(non_null_type: simple(:a)) { NonNullType.new(a) }
84
+
85
+ rule(name: simple(:a)) { a.to_s }
86
+
87
+ rule(value: simple(:a)) { a }
88
+ rule(value: subtree(:a)) { Value.new(a[:kind], a[:value]) }
89
+
90
+ rule(string_value: simple(:a)) { { value: a.to_s, kind: :string } }
91
+ rule(int_value: simple(:a)) { { value: a.to_i, kind: :int } }
92
+ rule(float_value: simple(:a)) { { value: a.to_f, kind: :float } }
93
+ rule(boolean_value: simple(:a)) { { value: a == 'true', kind: :boolean } }
94
+ rule(enum_value: simple(:a)) { { value: a, kind: :enum } }
95
+ rule(list_value: sequence(:a)) { { value: a, kind: :list } }
96
+ rule(object_value: subtree(:a)) { { value: a, kind: :object } }
97
+
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,24 @@
1
+ module GraphQL
2
+ module Language
3
+ Value = Struct.new('Value', :kind, :value) do
4
+
5
+
6
+ def materialize(type, variables)
7
+ case type
8
+ when GraphQLNonNull
9
+ materialize(type.of_type, variables)
10
+ when GraphQLList
11
+ value.map { |value| value.materialize(type.of_type, variables) }
12
+ when GraphQLInputObjectType
13
+ raise "Not. Implemented. Yet."
14
+ when GraphQLScalarType, GraphQLEnumType
15
+ type.parse_literal(self)
16
+ else
17
+ raise "Must be input type"
18
+ end
19
+ end
20
+
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module GraphQL
2
+ module Language
3
+ Variable = Struct.new('Variable', :name) do
4
+
5
+ def materialize(type, variables)
6
+ variables[name.to_sym]
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,34 @@
1
+ module GraphQL
2
+ module Language
3
+ VariableDefinition = Struct.new('VariableDefinition', :variable, :type, :default_value) do
4
+
5
+
6
+ def materialize(schema, param)
7
+ schema_type = type.materialize(schema)
8
+
9
+ if schema_type.nil? || !schema_type.is_a?(GraphQLInputType)
10
+ raise GraphQLError, "Variable '#{variable.name}' expected value " +
11
+ "of type '#{schema_type}' which cannot be used as input type."
12
+ end
13
+
14
+ if Validator.valid_value?(param, schema_type)
15
+ if !param && default_value
16
+ return default_value.materialize(schema_type)
17
+ end
18
+ return Validator.coerce_value(param, schema_type)
19
+ end
20
+
21
+ if param
22
+ raise GraphQLError, "Variable '#{variable.name}' expected value " +
23
+ "of type '#{schema_type}' but got '#{param}'."
24
+ else
25
+ raise GraphQLError, "Variable '#{variable.name}' " +
26
+ "of required type '#{type}' was not provided."
27
+ end
28
+
29
+ end
30
+
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ require_relative 'language/document'
2
+ require_relative 'language/operation_definition'
3
+ require_relative 'language/fragment_definition'
4
+ require_relative 'language/fragment_spread'
5
+ require_relative 'language/inline_fragment'
6
+ require_relative 'language/selection_set'
7
+ require_relative 'language/variable_definition'
8
+ require_relative 'language/variable'
9
+ require_relative 'language/named_type'
10
+ require_relative 'language/list_type'
11
+ require_relative 'language/non_null_type'
12
+ require_relative 'language/name'
13
+ require_relative 'language/value'
14
+ require_relative 'language/field'
15
+ require_relative 'language/argument'
16
+ require_relative 'language/directive'
17
+
18
+ require_relative 'language/parser'
19
+ require_relative 'language/transform'
20
+
21
+
22
+ module GraphQL
23
+ module Language
24
+
25
+ def self.parse(query)
26
+ transform.apply(parser.parse(query))
27
+ end
28
+
29
+ private
30
+
31
+ def self.parser
32
+ @parser ||= GraphQL::Language::Parser.new
33
+ end
34
+
35
+ def self.transform
36
+ @transform ||= GraphQL::Language::Transform.new
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ module GraphQL
2
+
3
+ # GraphQLArgument
4
+ #
5
+ class GraphQLArgumentConfiguration < GraphQL::Configuration::Base
6
+ slot :name, String, coerce: -> (v) { v.to_s }
7
+ slot :type, GraphQLInputType
8
+ slot :default_value, Object, null: true
9
+ slot :description, String, null: true
10
+ end
11
+
12
+ class GraphQLArgument < GraphQL::Configuration::Configurable
13
+ configure_with GraphQLArgumentConfiguration
14
+ end
15
+
16
+ end
@@ -0,0 +1,37 @@
1
+ module GraphQL
2
+
3
+ # GraphQLDirectiveConfiguration
4
+ #
5
+ class GraphQLDirectiveConfiguration < GraphQL::Configuration::Base
6
+ slot :name, String, coerce: -> (v) { v.to_s }
7
+ slot :args, [-> { GraphQLArgument }], singular: :arg
8
+ slot :description, String, null: true
9
+ slot :on_operation, Object, coerce: -> (v) { !!v }
10
+ slot :on_fragment, Object, coerce: -> (v) { !!v }
11
+ slot :on_field, Object, coerce: -> (v) { !!v }
12
+ end
13
+
14
+ class GraphQLDirective < GraphQL::Configuration::Configurable
15
+ configure_with GraphQLDirectiveConfiguration
16
+
17
+ def arg_map
18
+ @arg_map ||= @configuration.args.reduce({}) { |memo, arg| memo[arg.name.to_sym] = arg ; memo }
19
+ end
20
+
21
+ def arg_names
22
+ @arg_names ||= arg_map.keys
23
+ end
24
+
25
+ def args
26
+ @args ||= arg_map.values
27
+ end
28
+
29
+ def arg(name)
30
+ arg_map(name.to_sym)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ require_relative 'directives'
@@ -0,0 +1,25 @@
1
+ module GraphQL
2
+
3
+ GraphQLIncludeDirective = GraphQLDirective.new do
4
+ name :include
5
+ description 'Directs the executor to include this field or fragment only when the `if` argument is true.'
6
+
7
+ arg :if, ! GraphQLBoolean, description: 'Included when true'
8
+
9
+ on_operation false
10
+ on_fragment true
11
+ on_field true
12
+ end
13
+
14
+ GraphQLSkipDirective = GraphQLDirective.new do
15
+ name :skip
16
+ description 'Directs the executor to skip this field or fragment only when the `if` argument is true.'
17
+
18
+ arg :if, ! GraphQLBoolean, description: 'Skipped when true'
19
+
20
+ on_operation false
21
+ on_fragment true
22
+ on_field true
23
+ end
24
+
25
+ end
@@ -0,0 +1,100 @@
1
+ module GraphQL
2
+
3
+ #
4
+ # == GraphQLEnumType Definition
5
+ #
6
+ # Some leaf values of requests and input values are Enums. GraphQL serializes
7
+ # Enum values as strings, however internally Enums can be represented by any
8
+ # kind of type, often integers.
9
+ #
10
+ # Examples:
11
+ #
12
+ # RGBAType = GraphQLEnum.new do
13
+ # name 'RGBA'
14
+ #
15
+ # value :RED, 0
16
+ # value :GREEN, 1
17
+ # value :BLUE, 2
18
+ # value :ALPHA, description: 'Alpha channel'
19
+ # end
20
+ #
21
+ # Note: If a value is not provided in a definition, the name of the enum value
22
+ # will be used as it's internal value.
23
+ #
24
+
25
+
26
+ # GraphQLEnumValueConfiguration
27
+ #
28
+ class GraphQLEnumValue < GraphQL::Configuration::Base
29
+ slot :name, String, coerce: -> (v) { v.to_s }
30
+ slot :value, Object, null: true
31
+ slot :description, String, null: true
32
+ slot :deprecation_reason, String, null: true
33
+ end
34
+
35
+
36
+ # GraphQLEnumTypeConfiguration
37
+ #
38
+ class GraphQLEnumTypeConfiguration < GraphQL::Configuration::Base
39
+ slot :name, String
40
+ slot :values, [GraphQLEnumValue], singular: :value
41
+ slot :description, String, null: true
42
+ end
43
+
44
+
45
+ # GraphQLEnumType
46
+ #
47
+ class GraphQLEnumType < GraphQL::Configuration::Configurable
48
+
49
+ include GraphQLType
50
+ include GraphQLInputType
51
+ include GraphQLOutputType
52
+ include GraphQLLeafType
53
+ include GraphQLNullableType
54
+ include GraphQLNamedType
55
+
56
+ configure_with GraphQLEnumTypeConfiguration
57
+
58
+
59
+ def value_map
60
+ @value_map ||= @configuration.values.reduce({}) do |memo, value|
61
+ value.value = value.name if value.value.nil?
62
+ memo[value.name.to_sym] = value
63
+ memo
64
+ end
65
+ end
66
+
67
+ def value_map_by_values
68
+ @value_map_by_values ||= values.reduce({}) { |memo, value| memo[value.value.to_s] = value ; memo }
69
+ end
70
+
71
+ def value_names
72
+ @value_names ||= value_map.keys
73
+ end
74
+
75
+ def values
76
+ @values ||= value_map.values
77
+ end
78
+
79
+ def value(name)
80
+ value_map[name.to_sym]
81
+ end
82
+
83
+ def serialize(v)
84
+ value_map_by_values[v.to_s].name rescue nil
85
+ end
86
+
87
+ def parse_value(v)
88
+ value(v).value rescue nil
89
+ end
90
+
91
+ def parse_literal(ast)
92
+ value(ast[:value]).value rescue nil if ast[:kind] == :enum
93
+ end
94
+
95
+ def to_s
96
+ name
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,50 @@
1
+ module GraphQL
2
+
3
+ # GraphQLFieldConfiguration
4
+ #
5
+ class GraphQLFieldConfiguration < GraphQL::Configuration::Base
6
+ slot :name, String, coerce: -> (v) { v.to_s }
7
+ slot :type, -> { GraphQLOutputType }
8
+ slot :args, [ -> { GraphQLArgument } ], singular: :arg
9
+ slot :resolve, Proc
10
+ slot :deprecation_reason, String, null: true
11
+ slot :description, String, null: true
12
+ end
13
+
14
+ # GraphQLField
15
+ #
16
+ class GraphQLField < GraphQL::Configuration::Configurable
17
+ configure_with GraphQLFieldConfiguration
18
+
19
+ DEFAULT_RESOLVE = lambda { |name|
20
+ lambda { |object|
21
+ object.public_send(name)
22
+ }
23
+ }
24
+
25
+ def type
26
+ @type ||= @configuration.type.is_a?(Proc) ? @configuration.type.call : @configuration.type
27
+ end
28
+
29
+ def arg_map
30
+ @arg_map ||= @configuration.args.reduce({}) { |memo, arg| memo[arg.name.to_sym] = arg ; memo }
31
+ end
32
+
33
+ def arg_names
34
+ @arg_names ||= arg_map.keys
35
+ end
36
+
37
+ def args
38
+ @args ||= arg_map.values
39
+ end
40
+
41
+ def arg(name)
42
+ arg_map(name.to_sym)
43
+ end
44
+
45
+ def resolve
46
+ @resolve ||= @configuration.resolve || DEFAULT_RESOLVE.call(name)
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,47 @@
1
+ module GraphQL
2
+
3
+ class GraphQLInputObjectField < GraphQL::Configuration::Base
4
+ slot :name, String
5
+ slot :type, GraphQLInputType
6
+ slot :default_value, Object, null: true
7
+ slot :description, String, null: true
8
+ end
9
+
10
+ class GraphQLInputObjectTypeConfiguration < GraphQL::Configuration::Base
11
+ slot :name, String
12
+ slot :fields, [GraphQLInputObjectField], singular: :field
13
+ slot :description, String, null: true
14
+ end
15
+
16
+ class GraphQLInputObjectType < GraphQL::Configuration::Configurable
17
+
18
+ include GraphQLType
19
+ include GraphQLInputType
20
+ include GraphQLNullableType
21
+ include GraphQLNamedType
22
+
23
+ configure_with GraphQLInputObjectTypeConfiguration
24
+
25
+
26
+ def field_map
27
+ @field_map ||= @configuration.fields.reduce({}) { |memo, field| memo[field.name.to_sym] = field ; memo }
28
+ end
29
+
30
+ def field_names
31
+ @field_names ||= field_map.keys
32
+ end
33
+
34
+ def fields
35
+ @fields ||= field_map.values
36
+ end
37
+
38
+ def field(name)
39
+ field_map[name.to_sym]
40
+ end
41
+
42
+ def to_s
43
+ name
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,64 @@
1
+ module GraphQL
2
+
3
+ class GraphQLInterfaceTypeConfiguration < GraphQL::Configuration::Base
4
+ slot :name, String
5
+ slot :fields, [-> { GraphQLField }], singular: :field
6
+ slot :resolve_type, Proc, null: true
7
+ slot :description, String, null: true
8
+ end
9
+
10
+ class GraphQLInterfaceType < GraphQL::Configuration::Configurable
11
+
12
+ include GraphQLType
13
+ include GraphQLOutputType
14
+ include GraphQLCompositeType
15
+ include GraphQLAbstractType
16
+ include GraphQLNullableType
17
+ include GraphQLNamedType
18
+
19
+ configure_with GraphQLInterfaceTypeConfiguration
20
+
21
+ def initialize(configuration)
22
+ super
23
+ @implementations = []
24
+ end
25
+
26
+ def implement!(type)
27
+ raise RuntimeError.new("#{self} can implement instances of GraphQLType. Got #{type.class}.") unless type.is_a?(GraphQLType)
28
+ @implementations << type unless possible_type?(type)
29
+ end
30
+
31
+ def field_map
32
+ @field_map ||= @configuration.fields.reduce({}) { |memo, field| memo[field.name.to_sym] = field ; memo}
33
+ end
34
+
35
+ def field_names
36
+ @field_names ||= field_map.keys
37
+ end
38
+
39
+ def fields
40
+ @fields ||= field_map.values
41
+ end
42
+
43
+ def field(name)
44
+ field_map(name.to_sym)
45
+ end
46
+
47
+ def possible_types
48
+ @implementations
49
+ end
50
+
51
+ def possible_type?(type)
52
+ possible_types.include?(type)
53
+ end
54
+
55
+ def resolve_type(type)
56
+ @configuration.resolve_type.nil? ? type_of(type) : @configuration.resolve_type.call(type)
57
+ end
58
+
59
+ def to_s
60
+ name
61
+ end
62
+ end
63
+
64
+ end
@@ -0,0 +1,23 @@
1
+ module GraphQL
2
+
3
+ class GraphQLList
4
+
5
+ include GraphQLType
6
+ include GraphQLInputType
7
+ include GraphQLOutputType
8
+ include GraphQLNullableType
9
+
10
+ attr_reader :of_type
11
+
12
+ def initialize(of_type)
13
+ raise "Expecting #{GraphQLType}, got #{of_type.class}." unless of_type.is_a?(GraphQLType)
14
+ @of_type = of_type
15
+ end
16
+
17
+ def to_s
18
+ '[' + of_type.to_s + ']'
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,25 @@
1
+ module GraphQL
2
+
3
+ class GraphQLNonNull
4
+
5
+ include GraphQLType
6
+ include GraphQLInputType
7
+ include GraphQLOutputType
8
+
9
+ NESTING_ERROR = 'Cannot nest NonNull inside NonNull'
10
+
11
+ attr_reader :of_type
12
+
13
+ def initialize(of_type)
14
+ raise "Expecting #{GraphQLType}, got #{of_type.class}." unless of_type.is_a?(GraphQLType)
15
+ raise NESTING_ERROR if of_type.is_a?(GraphQLNonNull)
16
+ @of_type = of_type
17
+ end
18
+
19
+ def to_s
20
+ of_type.to_s + '!'
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,57 @@
1
+ module GraphQL
2
+
3
+ class GraphQLObjectTypeConfiguration < GraphQL::Configuration::Base
4
+ slot :name, String
5
+ slot :interfaces, [-> { GraphQLInterfaceType }], null: true, singular: :interface
6
+ slot :fields, [-> { GraphQLField }], singular: :field
7
+ slot :type_of?, Proc, null: true
8
+ slot :description, String, null: true
9
+ end
10
+
11
+
12
+ class GraphQLObjectType < GraphQL::Configuration::Configurable
13
+
14
+ include GraphQLType
15
+ include GraphQLOutputType
16
+ include GraphQLCompositeType
17
+ include GraphQLNullableType
18
+ include GraphQLNamedType
19
+
20
+ configure_with GraphQLObjectTypeConfiguration
21
+
22
+ def initialize(configuration)
23
+ super
24
+ interfaces.each { |interface| interface.implement!(self) }
25
+ end
26
+
27
+
28
+ def field_map
29
+ @field_map ||= @configuration.fields.reduce({}) { |memo, field| memo[field.name.to_sym] = field ; memo}
30
+ end
31
+
32
+ def field_names
33
+ @field_names ||= field_map.keys
34
+ end
35
+
36
+ def fields
37
+ @fields ||= field_map.values
38
+ end
39
+
40
+ def field(name)
41
+ field_map[name.to_sym]
42
+ end
43
+
44
+ def interfaces
45
+ @configuration.interfaces
46
+ end
47
+
48
+ def type_of?(type)
49
+ @configuration.type_of?.nil? ? false : @configuration.type_of?.call(type)
50
+ end
51
+
52
+ def to_s
53
+ name
54
+ end
55
+ end
56
+
57
+ end