praxis 0.22.pre.2 → 2.0.pre.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +5 -20
  3. data/CHANGELOG.md +333 -324
  4. data/lib/praxis.rb +14 -9
  5. data/lib/praxis/action_definition.rb +8 -10
  6. data/lib/praxis/action_definition/headers_dsl_compiler.rb +1 -1
  7. data/lib/praxis/api_definition.rb +27 -44
  8. data/lib/praxis/api_general_info.rb +23 -3
  9. data/lib/praxis/application.rb +15 -142
  10. data/lib/praxis/bootloader.rb +1 -2
  11. data/lib/praxis/bootloader_stages/environment.rb +13 -0
  12. data/lib/praxis/config.rb +1 -1
  13. data/lib/praxis/controller.rb +0 -2
  14. data/lib/praxis/dispatcher.rb +4 -6
  15. data/lib/praxis/docs/generator.rb +19 -24
  16. data/lib/praxis/docs/link_builder.rb +1 -1
  17. data/lib/praxis/docs/open_api_generator.rb +255 -0
  18. data/lib/praxis/docs/openapi/info_object.rb +31 -0
  19. data/lib/praxis/docs/openapi/media_type_object.rb +59 -0
  20. data/lib/praxis/docs/openapi/operation_object.rb +40 -0
  21. data/lib/praxis/docs/openapi/parameter_object.rb +69 -0
  22. data/lib/praxis/docs/openapi/paths_object.rb +58 -0
  23. data/lib/praxis/docs/openapi/request_body_object.rb +51 -0
  24. data/lib/praxis/docs/openapi/response_object.rb +63 -0
  25. data/lib/praxis/docs/openapi/responses_object.rb +44 -0
  26. data/lib/praxis/docs/openapi/schema_object.rb +87 -0
  27. data/lib/praxis/docs/openapi/server_object.rb +24 -0
  28. data/lib/praxis/docs/openapi/tag_object.rb +21 -0
  29. data/lib/praxis/error_handler.rb +5 -5
  30. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +1 -1
  31. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +4 -0
  32. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +125 -0
  33. data/lib/praxis/extensions/field_selection.rb +1 -12
  34. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +28 -34
  35. data/lib/praxis/extensions/field_selection/field_selector.rb +4 -0
  36. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +35 -39
  37. data/lib/praxis/extensions/rendering.rb +1 -1
  38. data/lib/praxis/file_group.rb +1 -1
  39. data/lib/praxis/handlers/xml.rb +1 -1
  40. data/lib/praxis/links.rb +4 -0
  41. data/lib/praxis/mapper/active_model_compat.rb +98 -0
  42. data/lib/praxis/mapper/resource.rb +242 -0
  43. data/lib/praxis/mapper/selector_generator.rb +150 -0
  44. data/lib/praxis/mapper/sequel_compat.rb +76 -0
  45. data/lib/praxis/media_type_identifier.rb +2 -1
  46. data/lib/praxis/middleware_app.rb +13 -15
  47. data/lib/praxis/multipart/part.rb +8 -7
  48. data/lib/praxis/notifications.rb +1 -1
  49. data/lib/praxis/plugins/mapper_plugin.rb +64 -0
  50. data/lib/praxis/request.rb +14 -7
  51. data/lib/praxis/request_stages/response.rb +2 -3
  52. data/lib/praxis/resource_definition.rb +15 -19
  53. data/lib/praxis/response.rb +6 -5
  54. data/lib/praxis/response_definition.rb +6 -8
  55. data/lib/praxis/response_template.rb +3 -4
  56. data/lib/praxis/responses/http.rb +36 -0
  57. data/lib/praxis/responses/internal_server_error.rb +12 -3
  58. data/lib/praxis/responses/multipart_ok.rb +11 -4
  59. data/lib/praxis/responses/validation_error.rb +10 -1
  60. data/lib/praxis/route.rb +1 -1
  61. data/lib/praxis/router.rb +3 -3
  62. data/lib/praxis/routing_config.rb +1 -1
  63. data/lib/praxis/tasks/api_docs.rb +24 -9
  64. data/lib/praxis/tasks/routes.rb +0 -1
  65. data/lib/praxis/trait.rb +1 -1
  66. data/lib/praxis/types/media_type_common.rb +12 -2
  67. data/lib/praxis/types/multipart.rb +1 -1
  68. data/lib/praxis/types/multipart_array.rb +64 -2
  69. data/lib/praxis/types/multipart_array/part_definition.rb +1 -1
  70. data/lib/praxis/version.rb +1 -1
  71. data/praxis.gemspec +11 -9
  72. data/spec/functional_spec.rb +0 -1
  73. data/spec/praxis/action_definition_spec.rb +16 -27
  74. data/spec/praxis/api_definition_spec.rb +8 -13
  75. data/spec/praxis/api_general_info_spec.rb +8 -3
  76. data/spec/praxis/application_spec.rb +8 -14
  77. data/spec/praxis/collection_spec.rb +3 -2
  78. data/spec/praxis/config_spec.rb +2 -2
  79. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +106 -0
  80. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +147 -0
  81. data/spec/praxis/extensions/field_selection/support/spec_resources_active_model.rb +130 -0
  82. data/spec/praxis/extensions/field_selection/support/spec_resources_sequel.rb +106 -0
  83. data/spec/praxis/handlers/xml_spec.rb +2 -2
  84. data/spec/praxis/mapper/resource_spec.rb +169 -0
  85. data/spec/praxis/mapper/selector_generator_spec.rb +325 -0
  86. data/spec/praxis/media_type_spec.rb +0 -10
  87. data/spec/praxis/middleware_app_spec.rb +16 -10
  88. data/spec/praxis/request_spec.rb +7 -17
  89. data/spec/praxis/request_stages/action_spec.rb +8 -1
  90. data/spec/praxis/request_stages/validate_spec.rb +1 -1
  91. data/spec/praxis/resource_definition_spec.rb +10 -12
  92. data/spec/praxis/response_definition_spec.rb +19 -30
  93. data/spec/praxis/response_spec.rb +6 -13
  94. data/spec/praxis/responses/internal_server_error_spec.rb +5 -2
  95. data/spec/praxis/router_spec.rb +5 -9
  96. data/spec/spec_app/app/controllers/instances.rb +1 -1
  97. data/spec/spec_app/config.ru +6 -1
  98. data/spec/spec_app/config/environment.rb +3 -21
  99. data/spec/spec_app/design/api.rb +6 -0
  100. data/spec/spec_helper.rb +13 -17
  101. data/spec/support/be_deep_equal_matcher.rb +39 -0
  102. data/spec/support/spec_resources.rb +124 -0
  103. metadata +86 -53
  104. data/lib/praxis/extensions/attribute_filtering.rb +0 -28
  105. data/lib/praxis/extensions/attribute_filtering/query_builder.rb +0 -39
  106. data/lib/praxis/extensions/mapper_selectors.rb +0 -16
  107. data/lib/praxis/media_type_collection.rb +0 -127
  108. data/lib/praxis/plugins/praxis_mapper_plugin.rb +0 -246
  109. data/spec/praxis/media_type_collection_spec.rb +0 -157
  110. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +0 -142
  111. data/spec/spec_app/app/models/person.rb +0 -3
@@ -0,0 +1,24 @@
1
+ module Praxis
2
+ module Docs
3
+ module OpenApi
4
+ class ServerObject
5
+ # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#server-object
6
+ attr_reader :url, :description, :variables
7
+ def initialize(url: , description: nil, variables: [])
8
+ @url = url
9
+ @description = description
10
+ @variables = variables
11
+ raise "OpenApi docs require a 'url' for your server object." unless url
12
+ end
13
+
14
+ def dump
15
+ result = {url: url}
16
+ result[:description] = description if description
17
+ result[:variables] = variables unless variables.empty?
18
+
19
+ result
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ module Praxis
2
+ module Docs
3
+ module OpenApi
4
+ class TagObject
5
+ attr_reader :name, :description
6
+ def initialize(name:,description: )
7
+ @name = name
8
+ @description = description
9
+ end
10
+
11
+ def dump
12
+ {
13
+ name: name,
14
+ description: description,
15
+ #externalDocs: ???,
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,15 +1,15 @@
1
1
  module Praxis
2
2
  class ErrorHandler
3
-
4
- def handle!(request, error, app:)
5
- app.logger.error error.inspect
3
+
4
+ def handle!(request, error)
5
+ Application.instance.logger.error error.inspect
6
6
  error.backtrace.each do |line|
7
- app.logger.error line
7
+ Application.instance.logger.error line
8
8
  end
9
9
 
10
10
  response = Responses::InternalServerError.new(error: error)
11
11
  response.request = request
12
- response.finish(application: app)
12
+ response.finish
13
13
  end
14
14
 
15
15
  end
@@ -23,7 +23,7 @@ module Praxis
23
23
  end
24
24
 
25
25
  # Base query to build upon
26
- def initialize(query: , model: )
26
+ def initialize(query: , model:)
27
27
  @query = query
28
28
  @table = model.table_name
29
29
  @last_join_alias = model.table_name
@@ -62,6 +62,10 @@ module Praxis
62
62
  end
63
63
  end
64
64
 
65
+ def json_schema_type
66
+ :string
67
+ end
68
+
65
69
  def add_filter(name, operators:, fuzzy:)
66
70
  components = name.to_s.split('.').map(&:to_sym)
67
71
  attribute, enclosing_type = find_filter_attribute(components, media_type)
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+ # rubocop:disable all
3
+ module Praxis
4
+ module Extensions
5
+ class SequelFilterQueryBuilder
6
+ attr_reader :query, :root
7
+
8
+ # Abstract class, which needs to be used by subclassing it through the .for method, to set the mapping of attributes
9
+ class << self
10
+ def for(definition)
11
+ Class.new(self) do
12
+ @attr_to_column = case definition
13
+ when Hash
14
+ definition
15
+ when Array
16
+ definition.each_with_object({}) { |item, hash| hash[item.to_sym] = item }
17
+ else
18
+ raise "Cannot use FilterQueryBuilder.of without passing an array or a hash (Got: #{definition.class.name})"
19
+ end
20
+ class << self
21
+ attr_reader :attr_to_column
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ # Base query to build upon
28
+ # table is necessary when use the strin queries, when the query has multiple tables involved
29
+ # (to disambiguate)
30
+ def initialize(query:, model: )
31
+ @query = query
32
+ @root = model.table_name
33
+ end
34
+
35
+ # By default we'll simply use the incoming op and value, and will map
36
+ # the attribute based on what's on the `attr_to_column` hash
37
+ def build_clause(filters)
38
+ seen_associations = Set.new
39
+ filters.each do |(attr, spec)|
40
+ column_name = attr_to_column[attr]
41
+ raise "Filtering by #{attr} not allowed (no mapping found)" unless column_name
42
+ if column_name.is_a?(Proc)
43
+ bindings = column_name.call(spec)
44
+ # A hash of bindings, consisting of a key with column name and a value to the query value
45
+ bindings.each{|col,val| expand_binding(column_name: col, op: spec[:op], value: val )}
46
+ else
47
+ expand_binding(column_name: column_name, **spec)
48
+ end
49
+ end
50
+ query
51
+ end
52
+
53
+ def expand_binding(column_name:,op:,value:)
54
+ assoc_or_field, *rest = column_name.to_s.split('.')
55
+ if rest.empty?
56
+ column_name = Sequel.qualify(root,column_name)
57
+ else
58
+ puts "Adding eager graph for #{assoc_or_field} due to being used in filter"
59
+ # Ensure the joined table is aliased properly (to the association name) so we can add the condition appropriately
60
+ @query = query.eager_graph(Sequel.as(assoc_or_field.to_sym, assoc_or_field.to_sym) )
61
+ column_name = Sequel.qualify(assoc_or_field, rest.first)
62
+ end
63
+ add_clause(attr: column_name, op: op, value: value)
64
+ end
65
+
66
+ def attr_to_column
67
+ # Class method defined by the subclassing Class (using .for)
68
+ self.class.attr_to_column
69
+ end
70
+
71
+ # Private to try to funnel all column names through `build_clause` that restricts
72
+ # the attribute names better (to allow more difficult SQL injections )
73
+ private def add_clause(attr:, op:, value:)
74
+ # TODO: partial matching
75
+ #components = attr.to_s.split('.')
76
+ #attr_selector = Sequel.qualify(*components)
77
+ attr_selector = attr
78
+ # HERE!! if we have "association.name" we should properly join it ...!
79
+
80
+ #> ds.eager_graph(:device).where{{device[:name] => 'A%'}}.select(:accountID)
81
+ #=> #<Sequel::Mysql2::Dataset: "SELECT `accountID` FROM `EventData`
82
+ # LEFT OUTER JOIN `Device` AS `device` ON
83
+ # ((`device`.`accountID` = `EventData`.`accountID`) AND (`device`.`deviceID` = `EventData`.`deviceID`))
84
+ # WHERE (`device`.`name` = 'A%')">
85
+ likeval = get_like_value(value)
86
+ @query = case op
87
+ when '='
88
+ if likeval
89
+ query.where(Sequel.like(attr_selector, likeval))
90
+ else
91
+ query.where(attr_selector => value)
92
+ end
93
+ when '!='
94
+ if likeval
95
+ query.exclude(Sequel.like(attr_selector, likeval))
96
+ else
97
+ query.exclude(attr_selector => value)
98
+ end
99
+ when '>'
100
+ #query.where(Sequel.lit("#{attr_selector} > ?", value))
101
+ query.where{attr_selector > value}
102
+ when '<'
103
+ query.where{attr_selector < value}
104
+ when '>='
105
+ query.where{attr_selector >= value}
106
+ when '<='
107
+ query.where{attr_selector <= value}
108
+ else
109
+ raise "Unsupported Operator!!! #{op}"
110
+ end
111
+ end
112
+
113
+ # Returns nil if the value was not a fuzzzy pattern
114
+ def get_like_value(value)
115
+ if value.is_a?(String) && (value[-1] == '*' || value[0] == '*')
116
+ likeval = value.dup
117
+ likeval[-1] = '%' if value[-1] == '*'
118
+ likeval[0] = '%' if value[0] == '*'
119
+ likeval
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ # rubocop:enable all
@@ -1,13 +1,2 @@
1
1
  require 'attributor/extras/field_selector'
2
-
3
- require 'praxis/extensions/field_selection/field_selector'
4
- # TODO: we should conditionally require it based on what ORM/s we want...
5
- require 'praxis/extensions/field_selection/active_record_query_selector'
6
-
7
-
8
- module Praxis
9
- module Extensions
10
- module FieldSelection
11
- end
12
- end
13
- end
2
+ require 'praxis/extensions/field_selection/field_selector'
@@ -3,53 +3,47 @@ module Praxis
3
3
  module Extensions
4
4
  module FieldSelection
5
5
  class ActiveRecordQuerySelector
6
- attr_reader :selector, :ds, :top_model, :resolved, :root
6
+ attr_reader :selector, :query
7
7
  # Gets a dataset, a selector...and should return a dataset with the selector definition applied.
8
- def initialize(ds:, model:, selectors:, resolved:)
8
+ def initialize(query:, selectors:)
9
9
  @selector = selectors
10
- @ds = ds
11
- @top_model = model
12
- @resolved = resolved
13
- @seen = Set.new
14
- @root = model.table_name
10
+ @query = query
15
11
  end
16
12
 
17
- def add_select(ds:, model:, table_name:)
18
- if (fields = fields_for(model))
19
- # Note, let's always add the pk fields so that associations can load properly
20
- fields = fields | [model.primary_key.to_sym]
21
- ds.select(*fields)
22
- else
23
- ds
24
- end
25
- end
26
-
27
- def generate
13
+ def generate(debug: false)
28
14
  # TODO: unfortunately, I think we can only control the select clauses for the top model
29
15
  # (as I'm not sure ActiveRecord supports expressing it in the join...)
30
- @ds = add_select(ds: ds, model: top_model, table_name: root)
16
+ @query = add_select(query: query, selector_node: selector)
17
+ eager_hash = _eager(selector)
31
18
 
32
- @ds.includes(_eager(top_model, resolved) )
19
+ @query = @query.includes(eager_hash)
20
+ explain_query(query, eager_hash) if debug
21
+
22
+ @query
33
23
  end
34
24
 
35
- def _eager(model, resolved)
36
- # Cannot select fields in included rels...boooo :()
37
- # d = add_select(ds: dset, model: model, table_name: model.table_name)
38
- tracks = only_assoc_for(model, resolved)
39
- tracks.inject([]) do |dataset, track|
40
- next dataset if @seen.include?([model, track])
41
- @seen << [model, track]
42
- assoc_model = model.associations[track][:model]
43
- dataset << { track => _eager(assoc_model, resolved[track]) }
44
- end
25
+ def add_select(query:, selector_node:)
26
+ # We're gonna always require the PK of the model, as it is a special case for AR, and the app itself
27
+ # might assume it is always there and not be surprised by the fact that if it isn't, it won't blow up
28
+ # in the same way as any other attribute not being loaded...i.e., ActiveModel::MissingAttributeError: missing attribute: xyz
29
+ select_fields = selector_node.select + [selector_node.resource.model.primary_key.to_sym]
30
+ select_fields.empty? ? query : query.select(*select_fields)
45
31
  end
46
32
 
47
- def only_assoc_for(model, hash)
48
- hash.keys.reject { |assoc| model.associations[assoc].nil? }
33
+ def _eager(selector_node)
34
+ selector_node.tracks.each_with_object({}) do |(track_name, track_node), h|
35
+ h[track_name] = _eager(track_node)
36
+ end
49
37
  end
50
38
 
51
- def fields_for(model)
52
- selector[model][:select].to_a
39
+ def explain_query(query, eager_hash)
40
+ prev = ActiveRecord::Base.logger
41
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
42
+ ActiveRecord::Base.logger.debug("Query plan for ...#{selector.resource.model} with selectors: #{JSON.generate(selector.dump)}")
43
+ ActiveRecord::Base.logger.debug(" ActiveRecord query: #{selector.resource.model}.includes(#{eager_hash})")
44
+ query.explain
45
+ ActiveRecord::Base.logger.debug("Query plan end")
46
+ ActiveRecord::Base.logger = prev
53
47
  end
54
48
  end
55
49
  end
@@ -7,6 +7,10 @@ module Praxis
7
7
  include Attributor::Type
8
8
  include Attributor::Dumpable
9
9
 
10
+ def self.json_schema_type
11
+ :string
12
+ end
13
+
10
14
  def self.native_type
11
15
  self
12
16
  end
@@ -1,63 +1,59 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'sequel'
4
+
2
5
  module Praxis
3
6
  module Extensions
4
7
  module FieldSelection
5
8
  class SequelQuerySelector
6
- attr_reader :selector, :ds, :top_model, :resolved, :root
9
+ attr_reader :selector, :query
7
10
  # Gets a dataset, a selector...and should return a dataset with the selector definition applied.
8
- def initialize(ds:, model:, selectors:, resolved:)
11
+ def initialize(query:, selectors:)
9
12
  @selector = selectors
10
- @ds = ds
11
- @top_model = model
12
- @resolved = resolved
13
- @seen = Set.new
14
- @root = model.table_name
13
+ @query = query
15
14
  end
16
15
 
17
- def add_select(ds:, model:, table_name:)
18
- if (fields = fields_for(model))
19
- # Note, let's always add the pk fields so that associations can load properly
20
- fields = fields | model.primary_key | [:id]
21
- qualified = fields.map { |f| Sequel.qualify(table_name, f) }
22
- ds.select(*qualified)
23
- else
24
- ds
16
+ def generate(debug: false)
17
+ @query = add_select(query: query, selector_node: @selector)
18
+
19
+ @query = @selector.tracks.inject(@query) do |ds, (track_name, track_node)|
20
+ ds.eager(track_name => _eager(track_node) )
25
21
  end
26
- end
27
22
 
28
- def generate
29
- @ds = add_select(ds: ds, model: top_model, table_name: root)
30
-
31
- tracks = only_assoc_for(top_model, resolved)
32
- @ds = tracks.inject(@ds) do |dataset, track|
33
- next dataset if @seen.include?([top_model, track])
34
- @seen << [top_model, track]
35
- assoc_model = top_model.associations[track][:model]
36
- # hash[track] = _eager(assoc_model, resolved[track])
37
- dataset.eager(track => _eager(assoc_model, resolved[track]))
38
- end
23
+ explain_query(query) if debug
24
+ @query
39
25
  end
40
26
 
41
- def _eager(model, resolved)
27
+ def _eager(selector_node)
42
28
  lambda do |dset|
43
- d = add_select(ds: dset, model: model, table_name: model.table_name)
29
+ dset = add_select(query: dset, selector_node: selector_node)
44
30
 
45
- tracks = only_assoc_for(model, resolved)
46
- tracks.inject(d) do |dataset, track|
47
- next dataset if @seen.include?([model, track])
48
- @seen << [model, track]
49
- assoc_model = model.associations[track][:model]
50
- dataset.eager(track => _eager(assoc_model, resolved[track]))
31
+ dset = selector_node.tracks.inject(dset) do |ds, (track_name, track_node)|
32
+ ds.eager(track_name => _eager(track_node) )
51
33
  end
34
+
52
35
  end
53
36
  end
54
37
 
55
- def only_assoc_for(model, hash)
56
- hash.keys.reject { |assoc| model.associations[assoc].nil? }
38
+ def add_select(query:, selector_node:)
39
+ # We're gonna always require the PK of the model, as it is a special case for Sequel, and the app itself
40
+ # might assume it is always there and not be surprised by the fact that if it isn't, it won't blow up
41
+ # in the same way as any other attribute not being loaded...i.e., NoMethodError: undefined method `foobar' for #<...>
42
+ select_fields = selector_node.select + [selector_node.resource.model.primary_key.to_sym]
43
+
44
+ table_name = selector_node.resource.model.table_name
45
+ qualified = select_fields.map { |f| Sequel.qualify(table_name, f) }
46
+ query.select(*qualified)
57
47
  end
58
48
 
59
- def fields_for(model)
60
- selector[model][:select].to_a
49
+ def explain_query(ds)
50
+ prev_loggers = Sequel::Model.db.loggers
51
+ stdout_logger = Logger.new($stdout)
52
+ Sequel::Model.db.loggers = [stdout_logger]
53
+ stdout_logger.debug("Query plan for ...#{selector.resource.model} with selectors: #{JSON.generate(selector.dump)}")
54
+ ds.all
55
+ stdout_logger.debug("Query plan end")
56
+ Sequel::Model.db.loggers = prev_loggers
61
57
  end
62
58
  end
63
59
  end
@@ -24,7 +24,7 @@ module Praxis
24
24
  response.body = render(object, include_nil: include_nil)
25
25
  response
26
26
  rescue Praxis::Renderer::CircularRenderingError => e
27
- Praxis::Application.current_instance.validation_handler.handle!(
27
+ Praxis::Application.instance.validation_handler.handle!(
28
28
  summary: "Circular Rendering Error when rendering response. " +
29
29
  "Please especify a view to narrow the dependent fields, or narrow your field set.",
30
30
  exception: e,
@@ -7,7 +7,7 @@ module Praxis
7
7
  def initialize(base, &block)
8
8
  if base.nil?
9
9
  raise ArgumentError, "base must not be nil." \
10
- "Have you forgot to call 'setup' on the Praxis application instance?"
10
+ "Are you missing a call Praxis::Application.instance.setup?"
11
11
  end
12
12
 
13
13
 
@@ -60,7 +60,7 @@ module Praxis
60
60
  when "symbol"
61
61
  return node.content.to_sym
62
62
  when "decimal"
63
- return BigDecimal.new(node.content)
63
+ return BigDecimal(node.content)
64
64
  when "float"
65
65
  return Float(node.content)
66
66
  when "boolean"