parse-stack-next 5.7.6 → 5.8.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/CHANGELOG.md +830 -0
- data/README.md +14 -4
- data/docs/TEST_SERVER.md +2 -2
- data/docs/acl_clp_guide.md +7 -0
- data/docs/atlas_vector_search_guide.md +181 -13
- data/docs/client_sdk_guide.md +11 -0
- data/docs/mcp_guide.md +317 -6
- data/docs/mongodb_direct_guide.md +27 -0
- data/docs/usage_guide.md +38 -0
- data/docs/webhooks_guide.md +74 -17
- data/lib/parse/acl_scope.rb +159 -41
- data/lib/parse/agent/approval_gate.rb +0 -0
- data/lib/parse/agent/constraint_translator.rb +42 -15
- data/lib/parse/agent/describe.rb +3 -1
- data/lib/parse/agent/field_names.rb +53 -0
- data/lib/parse/agent/field_policy.rb +74 -0
- data/lib/parse/agent/mcp_deployments.rb +426 -0
- data/lib/parse/agent/mcp_rack_app.rb +424 -45
- data/lib/parse/agent/mcp_server.rb +23 -1
- data/lib/parse/agent/mcp_subscriptions.rb +124 -6
- data/lib/parse/agent/metadata_registry.rb +67 -8
- data/lib/parse/agent/prompt_hardening.rb +9 -3
- data/lib/parse/agent/tools.rb +378 -29
- data/lib/parse/agent.rb +93 -1
- data/lib/parse/api/batch.rb +10 -1
- data/lib/parse/api/schema.rb +23 -4
- data/lib/parse/api/sessions.rb +6 -2
- data/lib/parse/api/users.rb +88 -14
- data/lib/parse/atlas_search/protected_paths.rb +236 -0
- data/lib/parse/atlas_search.rb +95 -23
- data/lib/parse/authorization.rb +54 -1
- data/lib/parse/client/batch.rb +231 -35
- data/lib/parse/client/body_builder.rb +21 -0
- data/lib/parse/client/caching.rb +371 -27
- data/lib/parse/client/request.rb +26 -14
- data/lib/parse/client/response.rb +49 -6
- data/lib/parse/client.rb +201 -38
- data/lib/parse/clp_scope.rb +281 -23
- data/lib/parse/console.rb +2 -2
- data/lib/parse/embeddings/voyage.rb +181 -17
- data/lib/parse/graphql/type_generator.rb +3 -0
- data/lib/parse/model/acl.rb +119 -21
- data/lib/parse/model/associations/belongs_to.rb +25 -3
- data/lib/parse/model/associations/collection_proxy.rb +138 -17
- data/lib/parse/model/associations/has_many.rb +38 -9
- data/lib/parse/model/associations/has_one.rb +3 -1
- data/lib/parse/model/associations/pointer_collection_proxy.rb +109 -17
- data/lib/parse/model/associations/relation_collection_proxy.rb +134 -28
- data/lib/parse/model/bytes.rb +13 -5
- data/lib/parse/model/classes/role.rb +72 -0
- data/lib/parse/model/classes/session.rb +43 -0
- data/lib/parse/model/classes/user.rb +78 -3
- data/lib/parse/model/core/actions.rb +269 -67
- data/lib/parse/model/core/builder.rb +100 -8
- data/lib/parse/model/core/create_lock.rb +27 -2
- data/lib/parse/model/core/describe.rb +2 -0
- data/lib/parse/model/core/fetching.rb +21 -3
- data/lib/parse/model/core/pluralized_aliases.rb +8 -4
- data/lib/parse/model/core/properties.rb +488 -39
- data/lib/parse/model/core/querying.rb +7 -0
- data/lib/parse/model/core/schema.rb +5 -3
- data/lib/parse/model/core/search_indexing.rb +63 -0
- data/lib/parse/model/core/vector_searchable.rb +35 -6
- data/lib/parse/model/file.rb +9 -2
- data/lib/parse/model/geopoint.rb +61 -13
- data/lib/parse/model/model.rb +160 -9
- data/lib/parse/model/object.rb +265 -17
- data/lib/parse/model/phone.rb +54 -5
- data/lib/parse/model/pointer.rb +40 -6
- data/lib/parse/mongodb.rb +170 -60
- data/lib/parse/pipeline_security.rb +415 -26
- data/lib/parse/query/constraint.rb +30 -0
- data/lib/parse/query/constraints.rb +58 -32
- data/lib/parse/query/cursor.rb +3 -1
- data/lib/parse/query/operation.rb +62 -8
- data/lib/parse/query/ordering.rb +34 -6
- data/lib/parse/query.rb +1100 -134
- data/lib/parse/retrieval/agent_tool.rb +225 -8
- data/lib/parse/retrieval/benchmark.rb +149 -0
- data/lib/parse/retrieval/profiles.rb +320 -0
- data/lib/parse/retrieval/retriever.rb +10 -1
- data/lib/parse/retrieval.rb +2 -0
- data/lib/parse/schema/search_index_migrator.rb +23 -5
- data/lib/parse/schema.rb +74 -18
- data/lib/parse/stack/tasks.rb +6 -4
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/stack.rb +72 -14
- data/lib/parse/two_factor_auth/user_extension.rb +14 -2
- data/lib/parse/two_factor_auth.rb +11 -0
- data/lib/parse/vector_search/hybrid.rb +36 -18
- data/lib/parse/vector_search/index_definition.rb +237 -0
- data/lib/parse/vector_search.rb +46 -17
- data/lib/parse/webhooks/payload.rb +93 -6
- data/lib/parse/webhooks/replay_protection.rb +58 -20
- data/lib/parse/webhooks.rb +412 -40
- metadata +8 -1
|
@@ -64,6 +64,31 @@ module Parse
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
class Constraint
|
|
67
|
+
# @!visibility private
|
|
68
|
+
# The constraint value as a list for the array operators (`$in`, `$nin`,
|
|
69
|
+
# `$all`, `$containedBy`). A Set or a finite Range is expanded into its
|
|
70
|
+
# members; before, it was wrapped as a single element (`[Set[1, 2]]`,
|
|
71
|
+
# `["1..3"]`) and matched nothing. A Range that cannot be enumerated
|
|
72
|
+
# (endless, beginless, or Float bounds) raises ArgumentError; use
|
|
73
|
+
# `:field.between` for a numeric interval.
|
|
74
|
+
# @return [Array]
|
|
75
|
+
def list_value
|
|
76
|
+
raw = @value
|
|
77
|
+
if raw.is_a?(Set)
|
|
78
|
+
raw = raw.to_a
|
|
79
|
+
elsif raw.is_a?(Range)
|
|
80
|
+
unless raw.begin.respond_to?(:succ) && !raw.end.nil?
|
|
81
|
+
raise ArgumentError,
|
|
82
|
+
"Cannot expand #{raw.inspect} into a list for #{key}. " \
|
|
83
|
+
"Pass an Array, or use :field.between for an interval."
|
|
84
|
+
end
|
|
85
|
+
raw = raw.to_a
|
|
86
|
+
end
|
|
87
|
+
val = self.class.formatted_value(raw)
|
|
88
|
+
val = [val].compact unless val.is_a?(Array)
|
|
89
|
+
val
|
|
90
|
+
end
|
|
91
|
+
|
|
67
92
|
# A constraint for matching by a specific objectId value.
|
|
68
93
|
#
|
|
69
94
|
# # where this Parse object equals the object in the column `field`.
|
|
@@ -396,8 +421,7 @@ module Parse
|
|
|
396
421
|
|
|
397
422
|
# @return [Hash] the compiled constraint.
|
|
398
423
|
def build
|
|
399
|
-
val =
|
|
400
|
-
val = [val].compact unless val.is_a?(Array)
|
|
424
|
+
val = list_value
|
|
401
425
|
|
|
402
426
|
# Convert Parse objects to pointers for array contains queries
|
|
403
427
|
if val.is_a?(Array)
|
|
@@ -447,8 +471,7 @@ module Parse
|
|
|
447
471
|
|
|
448
472
|
# @return [Hash] the compiled constraint.
|
|
449
473
|
def build
|
|
450
|
-
val =
|
|
451
|
-
val = [val].compact unless val.is_a?(Array)
|
|
474
|
+
val = list_value
|
|
452
475
|
|
|
453
476
|
# Convert Parse objects to pointers for array contains queries
|
|
454
477
|
if val.is_a?(Array)
|
|
@@ -493,8 +516,7 @@ module Parse
|
|
|
493
516
|
|
|
494
517
|
# @return [Hash] the compiled constraint.
|
|
495
518
|
def build
|
|
496
|
-
val =
|
|
497
|
-
val = [val].compact unless val.is_a?(Array)
|
|
519
|
+
val = list_value
|
|
498
520
|
{ @operation.operand => { key => val } }
|
|
499
521
|
end
|
|
500
522
|
end
|
|
@@ -522,8 +544,7 @@ module Parse
|
|
|
522
544
|
|
|
523
545
|
# @return [Hash] the compiled constraint.
|
|
524
546
|
def build
|
|
525
|
-
val =
|
|
526
|
-
val = [val].compact unless val.is_a?(Array)
|
|
547
|
+
val = list_value
|
|
527
548
|
{ @operation.operand => { key => val } }
|
|
528
549
|
end
|
|
529
550
|
end
|
|
@@ -864,8 +885,7 @@ module Parse
|
|
|
864
885
|
|
|
865
886
|
# @return [Hash] the compiled constraint using aggregation pipeline.
|
|
866
887
|
def build
|
|
867
|
-
val =
|
|
868
|
-
val = [val].compact unless val.is_a?(Array)
|
|
888
|
+
val = list_value
|
|
869
889
|
|
|
870
890
|
field_name = Parse::Query.format_field(@operation.operand)
|
|
871
891
|
|
|
@@ -960,8 +980,7 @@ module Parse
|
|
|
960
980
|
|
|
961
981
|
# @return [Hash] the compiled constraint using aggregation pipeline.
|
|
962
982
|
def build
|
|
963
|
-
val =
|
|
964
|
-
val = [val].compact unless val.is_a?(Array)
|
|
983
|
+
val = list_value
|
|
965
984
|
|
|
966
985
|
field_name = Parse::Query.format_field(@operation.operand)
|
|
967
986
|
|
|
@@ -1050,8 +1069,7 @@ module Parse
|
|
|
1050
1069
|
|
|
1051
1070
|
# @return [Hash] the compiled constraint using aggregation pipeline.
|
|
1052
1071
|
def build
|
|
1053
|
-
val =
|
|
1054
|
-
val = [val].compact unless val.is_a?(Array)
|
|
1072
|
+
val = list_value
|
|
1055
1073
|
|
|
1056
1074
|
field_name = Parse::Query.format_field(@operation.operand)
|
|
1057
1075
|
|
|
@@ -1140,8 +1158,7 @@ module Parse
|
|
|
1140
1158
|
|
|
1141
1159
|
# @return [Hash] the compiled constraint using aggregation pipeline.
|
|
1142
1160
|
def build
|
|
1143
|
-
val =
|
|
1144
|
-
val = [val].compact unless val.is_a?(Array)
|
|
1161
|
+
val = list_value
|
|
1145
1162
|
|
|
1146
1163
|
field_name = Parse::Query.format_field(@operation.operand)
|
|
1147
1164
|
|
|
@@ -1307,8 +1324,7 @@ module Parse
|
|
|
1307
1324
|
|
|
1308
1325
|
# @return [Hash] the compiled constraint using aggregation pipeline.
|
|
1309
1326
|
def build
|
|
1310
|
-
val =
|
|
1311
|
-
val = [val].compact unless val.is_a?(Array)
|
|
1327
|
+
val = list_value
|
|
1312
1328
|
|
|
1313
1329
|
field_name = Parse::Query.format_field(@operation.operand)
|
|
1314
1330
|
|
|
@@ -3085,16 +3101,20 @@ module Parse
|
|
|
3085
3101
|
target_field = @value[:field]
|
|
3086
3102
|
local_field = @operation.operand
|
|
3087
3103
|
|
|
3088
|
-
# Format field names according to Parse conventions
|
|
3089
|
-
# Pointer fields in MongoDB are stored with _p_ prefix
|
|
3090
|
-
formatted_through = "_p_" + Parse::Query.format_field(through_field)
|
|
3091
|
-
formatted_target = "_p_" + Parse::Query.format_field(target_field)
|
|
3092
|
-
formatted_local = "_p_" + Parse::Query.format_field(local_field)
|
|
3093
|
-
|
|
3094
3104
|
# Determine the target collection name from the through field
|
|
3095
3105
|
# Use classify to convert field name to class name (e.g., :project -> "Project")
|
|
3096
3106
|
target_collection = through_field.to_s.classify
|
|
3097
3107
|
|
|
3108
|
+
# Format field names according to Parse conventions
|
|
3109
|
+
# Pointer fields in MongoDB are stored with _p_ prefix. The local and
|
|
3110
|
+
# through fields are columns of the queried class; the target field is
|
|
3111
|
+
# a column of the linked class, so it uses that class's names.
|
|
3112
|
+
formatted_through = "_p_" + Parse::Query.format_field(through_field)
|
|
3113
|
+
formatted_target = "_p_" + Parse::Query.with_field_aliases(target_collection) do
|
|
3114
|
+
Parse::Query.format_field(target_field)
|
|
3115
|
+
end
|
|
3116
|
+
formatted_local = "_p_" + Parse::Query.format_field(local_field)
|
|
3117
|
+
|
|
3098
3118
|
# Build the aggregation pipeline
|
|
3099
3119
|
# Use clean alias name without _p_ prefix for readability
|
|
3100
3120
|
lookup_alias = "#{through_field.to_s.camelize(:lower)}_data"
|
|
@@ -3168,15 +3188,20 @@ module Parse
|
|
|
3168
3188
|
through_field = @value[:through]
|
|
3169
3189
|
target_field = @value[:field]
|
|
3170
3190
|
|
|
3171
|
-
# Convert field names to Parse format (snake_case to camelCase) with _p_ prefix for pointers
|
|
3172
|
-
local_field_name = format_field_name(@operation.operand, is_pointer: true)
|
|
3173
|
-
through_field_name = format_field_name(through_field, is_pointer: true)
|
|
3174
|
-
target_field_name = format_field_name(target_field, is_pointer: true)
|
|
3175
|
-
|
|
3176
3191
|
# Determine the collection name for the lookup (Rails pluralization)
|
|
3177
3192
|
through_class_name = through_field.to_s.classify
|
|
3178
3193
|
lookup_collection = through_class_name
|
|
3179
3194
|
|
|
3195
|
+
# Convert field names to Parse format with _p_ prefix for pointers.
|
|
3196
|
+
# The local and through fields are columns of the queried class; the
|
|
3197
|
+
# target field is a column of the linked class, so it uses that
|
|
3198
|
+
# class's names.
|
|
3199
|
+
local_field_name = format_field_name(@operation.operand, is_pointer: true)
|
|
3200
|
+
through_field_name = format_field_name(through_field, is_pointer: true)
|
|
3201
|
+
target_field_name = Parse::Query.with_field_aliases(lookup_collection) do
|
|
3202
|
+
format_field_name(target_field, is_pointer: true)
|
|
3203
|
+
end
|
|
3204
|
+
|
|
3180
3205
|
# Generate unique alias name for the joined data (use clean name without _p_ prefix)
|
|
3181
3206
|
lookup_alias = "#{through_field.to_s.camelize(:lower)}_data"
|
|
3182
3207
|
|
|
@@ -3229,13 +3254,14 @@ module Parse
|
|
|
3229
3254
|
|
|
3230
3255
|
private
|
|
3231
3256
|
|
|
3232
|
-
#
|
|
3233
|
-
#
|
|
3257
|
+
# Formats a field name with {Parse::Query.format_field} (so declared
|
|
3258
|
+
# `field:` names in the current alias scope are honored) and adds the
|
|
3259
|
+
# _p_ prefix for pointer fields in MongoDB.
|
|
3234
3260
|
# @param field [Symbol, String] the field name to format
|
|
3235
3261
|
# @param is_pointer [Boolean] whether this field is a pointer field
|
|
3236
3262
|
# @return [String] the formatted field name
|
|
3237
3263
|
def format_field_name(field, is_pointer: true)
|
|
3238
|
-
formatted =
|
|
3264
|
+
formatted = Parse::Query.format_field(field)
|
|
3239
3265
|
# Add _p_ prefix for pointer fields as they're stored that way in MongoDB
|
|
3240
3266
|
is_pointer ? "_p_#{formatted}" : formatted
|
|
3241
3267
|
end
|
data/lib/parse/query/cursor.rb
CHANGED
|
@@ -365,7 +365,9 @@ module Parse
|
|
|
365
365
|
# Returns: (field < last_value) OR (field = last_value AND objectId < last_id)
|
|
366
366
|
# for descending order, or the inverse for ascending.
|
|
367
367
|
def build_cursor_constraint
|
|
368
|
-
formatted_field = Parse::Query.
|
|
368
|
+
formatted_field = Parse::Query.with_field_aliases(@query.table) do
|
|
369
|
+
Parse::Query.format_field(@order_field)
|
|
370
|
+
end
|
|
369
371
|
|
|
370
372
|
if @order_direction == :desc
|
|
371
373
|
# Descending: (field < last_value) OR (field = last_value AND objectId < last_id)
|
|
@@ -10,7 +10,8 @@ module Parse
|
|
|
10
10
|
# queries. It contains an operand (the Parse field) and an operator (the Parse
|
|
11
11
|
# operation). These combined with a value, provide you with a constraint.
|
|
12
12
|
#
|
|
13
|
-
# All operation registrations add methods to the Symbol class
|
|
13
|
+
# All operation registrations add methods to the Symbol class, through
|
|
14
|
+
# the {Operation::SymbolMethods} module.
|
|
14
15
|
class Operation
|
|
15
16
|
|
|
16
17
|
# @!attribute operand
|
|
@@ -87,18 +88,71 @@ module Parse
|
|
|
87
88
|
handler.new(self, value)
|
|
88
89
|
end
|
|
89
90
|
|
|
91
|
+
# Operator names that are never installed as `Symbol` methods because
|
|
92
|
+
# they would replace or shadow behavior other code relies on:
|
|
93
|
+
#
|
|
94
|
+
# - `:size` is Ruby's own `Symbol#size`; replacing it broke
|
|
95
|
+
# `sort_by(&:size)` and any code that measures a symbol.
|
|
96
|
+
# - `:id` makes every symbol `respond_to?(:id)`, which ActiveRecord
|
|
97
|
+
# treats as a record. `where(status: :draft)` then raised
|
|
98
|
+
# `TypeError: can't cast Parse::Operation` (issue #83).
|
|
99
|
+
#
|
|
100
|
+
# Each one is installed under the alternate name given here instead.
|
|
101
|
+
# The original name stays registered in {operators}, so an explicit
|
|
102
|
+
# `Parse::Operation.new(:tags, :size)` still resolves.
|
|
103
|
+
SYMBOL_METHOD_RENAMES = { id: :pointer_id, size: :array_size }.freeze
|
|
104
|
+
|
|
105
|
+
# The module that carries the query DSL methods (`:plays.gt`,
|
|
106
|
+
# `:tags.in`, ...). It is included into `Symbol` rather than defining
|
|
107
|
+
# methods on `Symbol` directly, so a method another library defines on
|
|
108
|
+
# `Symbol` (Mongoid's `Symbol#gt`, Sequel's `Symbol#desc`) is never
|
|
109
|
+
# replaced by Parse, whichever library loads first.
|
|
110
|
+
module SymbolMethods; end
|
|
111
|
+
|
|
112
|
+
class << self
|
|
113
|
+
# Operator names that were not installed on `Symbol` because another
|
|
114
|
+
# library already defines a method of that name. Use the explicit
|
|
115
|
+
# form `Parse::Operation.new(:field, :op) => value` for these.
|
|
116
|
+
# @return [Array<Symbol>]
|
|
117
|
+
def symbol_conflicts
|
|
118
|
+
@symbol_conflicts ||= []
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
90
122
|
# Register a new symbol operator method mapped to a specific {Parse::Constraint}.
|
|
123
|
+
# @param op [Symbol] the operator name.
|
|
124
|
+
# @param klass [Class] the {Parse::Constraint} subclass that handles it.
|
|
91
125
|
def self.register(op, klass)
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
126
|
+
op = op.to_sym
|
|
127
|
+
Operation.operators[op] = klass
|
|
128
|
+
method_name = SYMBOL_METHOD_RENAMES.fetch(op, op)
|
|
129
|
+
Operation.operators[method_name] = klass
|
|
130
|
+
install_symbol_method(method_name)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Defines `method_name` on {SymbolMethods} unless `Symbol` already has
|
|
134
|
+
# a method of that name from somewhere other than Parse.
|
|
135
|
+
# @!visibility private
|
|
136
|
+
def self.install_symbol_method(method_name)
|
|
137
|
+
existing = ::Symbol.method_defined?(method_name) &&
|
|
138
|
+
::Symbol.instance_method(method_name).owner
|
|
139
|
+
if existing && existing != SymbolMethods
|
|
140
|
+
symbol_conflicts << method_name unless symbol_conflicts.include?(method_name)
|
|
141
|
+
return false
|
|
142
|
+
end
|
|
143
|
+
if SymbolMethods.method_defined?(method_name, false)
|
|
144
|
+
SymbolMethods.send(:remove_method, method_name)
|
|
145
|
+
end
|
|
146
|
+
op = method_name
|
|
147
|
+
SymbolMethods.send :define_method, method_name do |value = nil|
|
|
99
148
|
operation = Operation.new self, op
|
|
100
149
|
value.nil? ? operation : operation.constraint(value)
|
|
101
150
|
end
|
|
151
|
+
true
|
|
102
152
|
end
|
|
103
153
|
end
|
|
104
154
|
end
|
|
155
|
+
|
|
156
|
+
# Included (not prepended) so methods defined on Symbol itself, by Ruby or
|
|
157
|
+
# by another library, keep precedence over the Parse query DSL.
|
|
158
|
+
Symbol.include(Parse::Operation::SymbolMethods)
|
data/lib/parse/query/ordering.rb
CHANGED
|
@@ -56,11 +56,39 @@ module Parse
|
|
|
56
56
|
end # Order
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
class
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
module Parse
|
|
60
|
+
class Order
|
|
61
|
+
# Carries the `:field.asc` / `:field.desc` sort helpers. Included into
|
|
62
|
+
# `Symbol` rather than defined on it, and skipped for any name another
|
|
63
|
+
# library already defines on `Symbol` (Mongoid, Sequel core extensions),
|
|
64
|
+
# so Parse never replaces another library's `Symbol#asc` or `#desc`.
|
|
65
|
+
# Use `Parse::Order.new(:field, :desc)` when such a library is loaded.
|
|
66
|
+
module SymbolMethods; end
|
|
67
|
+
|
|
68
|
+
# Converts another library's sort key (Mongoid's `:field.desc` when
|
|
69
|
+
# Mongoid owns `Symbol#desc`) into a {Parse::Order}.
|
|
70
|
+
# @param value [Object]
|
|
71
|
+
# @return [Parse::Order, nil] nil when `value` is not a known foreign sort key.
|
|
72
|
+
def self.from_foreign(value)
|
|
73
|
+
return value if value.is_a?(Parse::Order)
|
|
74
|
+
if defined?(::Mongoid::Criteria::Queryable::Key) &&
|
|
75
|
+
value.is_a?(::Mongoid::Criteria::Queryable::Key)
|
|
76
|
+
case value.operator
|
|
77
|
+
when 1, "1" then return new(value.name, :asc)
|
|
78
|
+
when -1, "-1" then return new(value.name, :desc)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
nil
|
|
64
82
|
end
|
|
65
|
-
|
|
83
|
+
|
|
84
|
+
ORDERING.keys.each do |sym|
|
|
85
|
+
existing = ::Symbol.method_defined?(sym) && ::Symbol.instance_method(sym).owner
|
|
86
|
+
next if existing && existing != SymbolMethods
|
|
87
|
+
SymbolMethods.send(:define_method, sym) do
|
|
88
|
+
Parse::Order.new self, sym
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
66
92
|
end
|
|
93
|
+
|
|
94
|
+
Symbol.include(Parse::Order::SymbolMethods)
|