Konwersja łańcucha z snake_case do CamelCase w Rubim

171

Próbuję przekonwertować nazwę z etui węża na etui na wielbłąda. Czy są jakieś wbudowane metody?

Np .: "app_user"do"AppUser"

(Mam ciąg, "app_user"który chcę przekonwertować na model AppUser).

Lohith MV
źródło

Odpowiedzi:

251

Jeśli używasz Railsów, to String # camelize jest tym, czego szukasz.

  "active_record".camelize                # => "ActiveRecord"
  "active_record".camelize(:lower)        # => "activeRecord"

Jeśli chcesz otrzymać aktualną klasę, powinieneś dodać do tego stałą String # .

"app_user".camelize.constantize
Sergio Tulentsev
źródło
44
Powinieneś dodać, że jest to dodatek Rails do String, nie działa z czystym Rubim.
iGEL,
2
Jest oznaczony ruby-on-rails, więc myślę, że to nie problem. Ale dzięki za wzmiankę.
Sergio Tulentsev
6
Nie musisz kamelizować przed ustaleniem. Użyj #classifyzamiast tego. "some_namespace/module/class_name".classify => "SomeNamespace::Module::ClassName"
Chris Heald
5
@chris #classify: To nie to samo. #classify zwraca ciąg, podczas gdy #constantize wyszukuje stałą w kontekście (i potrzebuje camelize). „active_record” .constantize zwraca błąd, „active_record” .camelize.constantize zwraca stałą ActiveRecord, „active_record” .classify zwraca ciąg „ActiveRecord”. A jeśli wykonałeś 'no_class'.camelize.constantize, dostaniesz błąd (brak takiej stałej NoClass), ale' no_class'.classify szczęśliwie zwraca ciąg 'NoClass'.
Kanat Bolazar
Aby użyć tych metod Railsów z czystego Rubiego, require "active_support/core_ext/string"wystarczy pod warunkiem, że Railsy są już zainstalowane.
Masa Sakano
120

A co z tym?

"hello_world".split('_').collect(&:capitalize).join #=> "HelloWorld"

Znalezione w komentarzach tutaj: Klasyfikuj łańcuch Ruby

Zobacz komentarz Wayne Conrad

user3869936
źródło
10
Jesteś niesamowity, dziękuję. Nie chciałem włączać bibliotek railsowych tylko dla tak małego zadania. To jest piękne. :)
Gerry
11
To jedyna prawdziwa odpowiedź na to pytanie. Nieużywanie bibliotek Railsów.
Luis Ortega Araneda,
40

Jeśli używasz Railsów, użyj classify. Dobrze radzi sobie z krawędziami.

"app_user".classify # => AppUser
"user_links".classify   # => UserLink

Uwaga:

Ta odpowiedź jest specyficzna dla opisu podanego w pytaniu (nie jest specyficzna dla tytułu pytania). Jeśli ktoś próbuje zamienić sznurek na skrzynkę wielbłąda, powinien użyć odpowiedzi Sergio . Pytający stwierdza, że ​​chce przejść app_userna AppUser(nie App_user), stąd ta odpowiedź.

Harish Shetty
źródło
4
W przypadku środowisk Railsowych jest to idealne rozwiązanie.
Ghayes
Zwróć uwagę, że classifyzwraca łańcuch, musisz constantizepóźniej wywołać, aby przekonwertować go na rzeczywistą klasę.
Stefan
1
Ważnym zastrzeżeniem classifyjest to, że ciągi w liczbie mnogiej staną się pojedyncze ... 'age_in_years'.classifystaje sięAgeInYear
br3nt
@ br3nt nie ma liczby mnogiej, ponieważ activerecord4.2.11
Ulysse BN
23

Źródło: http://rubydoc.info/gems/extlib/0.9.15/String#camel_case-instance_method

Do celów edukacyjnych:

class String
  def camel_case
    return self if self !~ /_/ && self =~ /[A-Z]+.*/
    split('_').map{|e| e.capitalize}.join
  end
end

"foo_bar".camel_case          #=> "FooBar"

A dla wariantu lowCase:

class String
  def camel_case_lower
    self.split('_').inject([]){ |buffer,e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
  end
end

"foo_bar".camel_case_lower          #=> "fooBar"
Pan Black
źródło
6
@pguardiario Jeśli koło nazywa się ActiveSupport , wymyśl je na nowo.
shime
Myślę, że wariant lowerCase jest zły. Blok wstrzykiwania nie powinien bezpośrednio manipulować buforem, ale zwracać nową wartość dla bufora:self.split('_').inject([]){ |buffer,e| buffer + [buffer.empty? ? e : e.capitalize] }.join
Sven Koschnicke
19

Benchmark dla czystych rozwiązań Ruby

Wykorzystałem wszystkie możliwości, jakie miałem na myśli, aby zrobić to za pomocą czystego kodu rubinowego, oto one:

  • wielkie litery i gsub

    'app_user'.capitalize.gsub(/_(\w)/){$1.upcase}
  • podziel i &mapuj używając skrótu (dzięki odpowiedzi użytkownika3869936)

    'app_user'.split('_').map(&:capitalize).join
  • split i map (dzięki odpowiedzi pana Blacka)

    'app_user'.split('_').map{|e| e.capitalize}.join

A tutaj jest Benchmark dla wszystkich z nich, widzimy, że gsub jest do tego całkiem zły. Użyłem 126 080 słów.

                              user     system      total        real
capitalize and gsub  :      0.360000   0.000000   0.360000 (  0.357472)
split and map, with &:      0.190000   0.000000   0.190000 (  0.189493)
split and map        :      0.170000   0.000000   0.170000 (  0.171859)
Ulysse BN
źródło
11

Przyszedłem tutaj, szukając odwrotności twojego pytania, przechodząc od przypadku wielbłąda do przypadku węża. Użyj do tego podkreślenia (nie dekamelizuj):

AppUser.name.underscore # => "app_user"

lub, jeśli masz już ciąg wielbłąda:

"AppUser".underscore # => "app_user"

lub, jeśli chcesz uzyskać nazwę stołu, prawdopodobnie dlatego chciałbyś mieć etui węża:

AppUser.name.tableize # => "app_users"

mikrofon
źródło
Dlaczego nie używać AppUser.table_name? Upewnij się również, że masz prawdziwą nazwę tabeli, jeśli nie jest to app_users, ale coś zdefiniowanego w innym miejscu.
Ulysse BN
3

Czuję się trochę nieswojo, dodając tutaj więcej odpowiedzi. Zdecydowałem się na najbardziej czytelne i minimalne podejście do czystego rubinu, pomijając ładny wzorzec z @ ulysse-bn. Chociaż :classtryb jest kopią @ user3869936, :methodtryb, którego nie widzę w żadnej innej odpowiedzi tutaj.

  def snake_to_camel_case(str, mode: :class)
    case mode
    when :class
      str.split('_').map(&:capitalize).join
    when :method
      str.split('_').inject { |m, p| m + p.capitalize }
    else
      raise "unknown mode #{mode.inspect}"
    end
  end

Wynik to:

[28] pry(main)> snake_to_camel_case("asd_dsa_fds", mode: :class)
=> "AsdDsaFds"
[29] pry(main)> snake_to_camel_case("asd_dsa_fds", mode: :method)
=> "asdDsaFds"
akostadinov
źródło
1
Obudowa wielbłąda jest w rzeczywistości pierwsza niższa. W przeciwnym razie nosi nazwę PascalCase (lub czasami wielkie litery wielbłąda). Chociaż w tym pytaniu jest to niejednoznaczne!
Ulysse BN
2
@UlysseBN, tbh Nie interesuje mnie historia słów. Twierdzenia Wikipedii PascalCasesą podzbiorem CamelCase. I to właśnie wiedziałem - ten przypadek wielbłąda dotyczył obu. Ale nigdy nie badałem. Dziękuję za wspomnienie PascalCase. en.wikipedia.org/wiki/Camel_case
akostadinov
2
To najlepsza odpowiedź na stronie imo. Byłoby miło, gdyby :methodwersja zrobiła downcasepierwszą, więc można jej używać zarówno na, jak lower_snake_casei UPPER_SNAKE_CASE.
skagedal
0

Większość innych wymienionych tutaj metod jest specyficzna dla Railsów. Jeśli chcesz to zrobić z czystym Rubim, oto najbardziej zwięzły sposób, jaki wymyśliłem (dzięki @ ulysse-bn za sugerowane ulepszenie)

x="this_should_be_camel_case"
x.gsub(/(?:_|^)(\w)/){$1.upcase}
    #=> "ThisShouldBeCamelCase"
masukomi
źródło
Twoja definicja „przypadku wielbłąda” jest zbyt ograniczona. Nazwy klas w Javie, Ruby, na przykład, camel case MyFavoriteClass ... ale one również nie mają niższy powlekanego początkową literę. czasami wielbłąd ma początkowe czapki. czasami tak nie jest.
masukomi
Używanie 2 Regexów, w których możesz użyć tylko jednego, jest przesadą. Możesz użyć tylko grupy bez przechwytywania:x.gsub(/(?:_|^)(\w)/){$1.upcase}
Ulysse BN
@UlysseBN i wracamy do Twojego gsubrozwiązania, które wydaje się wolniejsze w porównaniu do maprozwiązania.
akostadinov
0

Przedłuż ciąg, aby dodać Camelize

W czystym Rubim możesz rozszerzyć klasę stringów używając dokładnie tego samego kodu z Railsów .camelize

class String
  def camelize(uppercase_first_letter = true)
    string = self
    if uppercase_first_letter
      string = string.sub(/^[a-z\d]*/) { |match| match.capitalize }
    else
      string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
    end
    string.gsub(/(?:_|(\/))([a-z\d]*)/) { "#{$1}#{$2.capitalize}" }.gsub("/", "::")
  end
end
Cameron Lowell Palmer
źródło