Як створити примірник класу з рядка імен у 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.