Як створити примірник класу з рядка імен у Rails?


77

Як ми можемо створити екземпляр класу з його рядка імен у Ruby-on-Rails?

Наприклад, ми маємо це ім'я у базі даних у такому форматі, як "Ім'я класу" або "ім'я_класу_нашого_класу".

Як ми можемо створити з нього об’єкт?

Рішення:

Шукав сам, але не знайшов, от і ось. Метод API Ruby-on-Rails

name = "ClassName"
instance = name.constantize.new  

Він може бути навіть не відформатований, ми можемо користувальницький метод .classify

name = "my_super_class"
instance = name.classify.constantize.new

Звичайно, можливо, це не дуже «шлях Rails», але це вирішує його мету.


4
Просто FYI, constantize - це зручний метод ActiveSupport, який робить, Object.const_getа Classify - метод ActiveSupport, який намагається перетворити рядок у стандартне форматування класу. Те, що ви робите, ідентично відповіді Євгінея, з деякими додатковими перевірками. Хоча constantize, мабуть, є кращим рішенням (оскільки він перевіряє осудність), він допомагає зрозуміти інструменти, якими ви користуєтесь.
quandrum

Дякую за це, чесно кажучи, не перевірив, що це робить в інструкції.
В’яцеслав Гедровіц

Відповіді:


77
klass = Object.const_get "ClassName"

про методи класу

class KlassExample
    def self.klass_method
        puts "Hello World from Class method"
    end
end
klass = Object.const_get "KlassExample"
klass.klass_method

irb(main):061:0> klass.klass_method
Hello World from Class method

При цьому ви не можете отримати доступ до методів класу. Моє рішення вище працює коректно з методами класів.
Вяцеслав Гедровіц

Loading development environment (Rails 3.2.9) irb(main):001:0> name = "PartnerGateway" => "PartnerGateway" irb(main):002:0> klass = name.constantize.new => #<PartnerGateway id: nil, name: nil, partner_id: nil, gateway: nil, changed_by_id: nil, deleted_at: nil, created_at: nil, updated_at: nil> irb(main):003:0> klass.name => nil
В’яцеслав Гедровіц

irb(main):005:0> klass2 = Object.const_get name => PartnerGateway(id: integer, name: string, partner_id: integer, gateway: string, changed_by_id: integer, deleted_at: datetime, created_at: datetime, updated_at: datetime) irb(main):006:0> klass2.name => "PartnerGateway" irb(main):008:0> klass2.id NoMethodError: undefined method id 'for # <Class: 0xac3e4c0> `
Vjatseslav Gedrovits

За допомогою мого методу ви можете зробити обидва шляхи. "KlassExample" .constantize.self_method_name і klass = "KlassExample" .constantize.new klass.normal_method
В'яцеслав Гедровіц

так ... це не доводить нічого, що ви, очевидно, не називаєте новими, ви використовуєте "Object.const_get", спробуйте klass2.new.name
Юджин Рурк,

40

Інші також можуть шукати альтернативу, яка не видасть помилки, якщо не вдається знайти клас. safe_constantizeце саме це.

class MyClass
end

"my_class".classify.safe_constantize.new #  #<MyClass:0x007fec3a96b8a0>
"omg_evil".classify.safe_constantize.new #  nil 

8

Ви можете просто перетворити рядок та ініціалізувати клас з нього, виконавши:

klass_name = "Module::ClassName"
klass_name.constantize

Щоб ініціалізувати новий об’єкт:

klass_name.constantize.new

Сподіваюся, це виявиться корисним. Дякую!


Не використовуйте це! Це дуже вразливий підхід: praetorian.com/blog/ruby-unsafe-reflection-vulnerabilities
TiSer

@TiSer Чи можете ви пояснити, як це вразливо, якщо я не константірую рядок із параметрів мого запиту?
Pushp Raj Saurabh

Дякуємо за посилання на блог. Це дуже корисно. Поки ми використовуємо constantize, не передаючи йому жодних параметрів запиту та не вкладаючи його в приватну функцію, я не бачу загрози.
Pushp Raj Saurabh

Цей рядок у спільному для вас блозі надзвичайно важливий, про що слід пам’ятати під час роботи з рефлексіями: Reflection використовується для зміни характеру програми під час виконання та не повинен використовуватися з рядками з ненадійних джерел.
Pushp Raj Saurabh

1
У вашому прямому прикладі - так, але я не знаю, де ви захочете зробити таке перетворення об'єкта з цілого одного рядка в клас у реальному світі.
TiSer

5

Я здивований, що ніхто не розглядає питання безпеки та злому у своїх відповідях. Примірник довільного рядка, який, мабуть, навіть опосередковано надійшов від введення користувачем, вимагає проблем та злому. Ми всі повинні / повинні вносити до білого списку, якщо не впевнені, що рядок повністю контролюється і контролюється

def class_for(name)
  {
    "foo" => Foo,
    "bar" => Bar,
  }[name] || raise UnknownClass
end

class_for(name_wherever_this_came_from).create!(params_somehow)

Як би ви довільно знали відповідні параметри, не маючи білого списку, було б складним завданням, але ви зрозуміли ідею.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.