Ваша проблема заключается в том, что вы пытаетесь использовать имя field
слишком много вещей здесь:
def eval_mongo(klass, field)
_field = field['field'].to_sym
_type = FieldType.where(_id: field['field_type_id']).first.type_from_field
klass.class_eval <<-EOS
field :'#{ _field }', type: #{ _type }
EOS
end
field
является аргументом eval_mongo
, но вы также можете использовать его в качестве имени метода класса внутри звонок class_eval
. Внутри class_eval
Ruby думает, что вы хотите аргумент field
, чтобы получить синтаксическую ошибку. Если вы называете аргумент f
вместо:
def eval_mongo(klass, f)
_field = f['field'].to_sym
_type = FieldType.where(_id: f['field_type_id']).first.type_from_field
klass.class_eval <<-EOS
field :'#{ _field }', type: #{ _type }
EOS
end
то все должно работать.
Хотелось бы, чтобы я мог дать четкое объяснение того, что здесь происходит, но я не могу. Вместо этого я подведу итог тому, что я обнаружил, бродив по источнику магнитно-резонансной томографии и пытаясь дразнить поведение посредством экспериментов. Этот подход очень подвержен ошибкам, но часто у нас есть Ruby.
The documentation говорит:
class_eval(string [, filename [, lineno]]) → obj
Evaluates the string or block in the context of mod, except that when a block is given, constant/class variable lookup is not affected. [...]
Это, как обычно, с Ruby, не достаточно подробно или достаточно конкретным, чтобы быть очень полезным.
Если посмотреть на источник, мы увидим, что на самом деле class_eval
rb_mod_module_eval
in vm_eval.c
который просто вызывает specific_eval
который вызывает eval_under
который вызывает eval_string_with_cref
со значением Qnil
для scope
аргумента. Это scope
обрабатывается следующим образом:
if (!NIL_P(scope)) {
/* ... */
}
else {
rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);
if (cfp != 0) {
block = *RUBY_VM_GET_BLOCK_PTR_IN_CFP(cfp);
base_block = █
base_block->self = self;
base_block->iseq = cfp->iseq; /* TODO */
}
else {
rb_raise(rb_eRuntimeError, "Can't eval on top of Fiber or Thread");
}
}
, а затем base_block
используется для компиляции строки исходного кода. Я не очень хорошо знаком с источником MRI, но похоже, что он намеренно настраивает возможности использования области вокруг class_eval
.
Упрощенный пример может помочь:
class K
def self.field(*args)
puts args.inspect
end
end
def eval_mongo(klass, f)
klass.class_eval <<-EOS
field :'#{f}', type: String
f
EOS
end
puts eval_mongo(K, 'pancakes house').inspect
Что скажут:
[:"pancakes house", {:type=>String}]
"pancakes house"
Если оставить имя в одиночку, но называть field
с строковым аргументом:
def eval_mongo(klass, field)
klass.class_eval <<-EOS
field '#{field}', type: String
field
EOS
end
затем он также работает и говорит:
["pancakes house", {:type=>String}]
"pancakes house"
, но если мы будем использовать field
в качестве имени аргумента и использовать символ:
def eval_mongo(klass, field)
klass.class_eval <<-EOS
field :'#{field}', type: String
field
EOS
end
мы получаем нашу синтаксическую ошибку:
in `class_eval': (eval):1: syntax error, unexpected ':', expecting end-of-input (SyntaxError)
field :'pancakes house', type: String
^
Интересно, что вы получите тот же синтаксис если вы попытаетесь использовать несколько скрытую функцию вставки строк в Ruby:
> s = 'a' 'b'
=> "ab"
с символом:
> s = 'a' :'b'
SyntaxError: (irb):2: syntax error, unexpected ':', expecting end-of-input
s = 'a' :'b'
^
Возможно, разные вещи оцениваются в разное время и Руби путаться о том, что это строка, а что нет.
Такое поведение для меня неожиданно и неожиданно, поэтому я соблазнился назвать это ошибкой или, возможно, ошибкой, я мог бы пропустить что-то очевидное. Было бы неплохо, если бы поведение class_eval
было лучше указано (с обоснованием и обоснованием для удивительного поведения), но это, похоже, противоречит быстрой культуре Ruby.
Буду признателен, если кто-нибудь сможет разъяснить, почему это происходит.
Не должно быть 'field: # {_field}, type: # {_type}'? Вы хотите создать символ, а не строку? – marc
@marc Мне нужна строка, потому что информация last_apple без строки приведет к недопустимому методу с пробелом и вызовет неопределенную информацию об ошибке. – Donato
Это должно сработать. Какую версию Ruby вы используете? Использование 'eval' всегда вызывает большое беспокойство, поэтому я надеюсь, что вы сможете избежать этого, используя' klass.send (_field.to_sym, _type) ' – tadman