Ruby on Rails: Di mana mendefinisikan konstanta global?

213

Saya baru memulai dengan aplikasi web Ruby on Rails pertama saya. Saya punya banyak model, tampilan, pengontrol, dan sebagainya yang berbeda.

Saya ingin menemukan tempat yang baik untuk tetap definisi konstan konstanta global, yang berlaku di seluruh aplikasi saya. Secara khusus, mereka berlaku baik dalam logika model saya, dan dalam keputusan yang diambil dalam pandangan saya. Saya tidak dapat menemukan tempat KERING untuk meletakkan definisi ini di mana mereka tersedia baik untuk semua model saya dan juga dalam semua pandangan saya.

Untuk mengambil contoh spesifik, saya ingin sebuah konstanta COLOURS = ['white', 'blue', 'black', 'red', 'green']. Ini digunakan di semua tempat, baik dalam model maupun tampilan. Di mana saya dapat mendefinisikannya hanya di satu tempat sehingga dapat diakses?

Apa yang saya coba:

  • Variabel kelas konstan dalam file model.rb yang paling sering dikaitkan dengan mereka, seperti @@COLOURS = [...]. Tapi saya tidak bisa menemukan cara yang waras untuk mendefinisikannya sehingga saya bisa menulis dalam pandangan saya Card.COLOURSdaripada sesuatu yang suka kludgy Card.first.COLOURS.
  • Metode pada model, sesuatu seperti def colours ['white',...] end- masalah yang sama.
  • Metode di application_helper.rb - inilah yang saya lakukan sejauh ini, tetapi pembantu hanya dapat diakses dalam tampilan, bukan dalam model
  • Saya pikir saya mungkin telah mencoba sesuatu di application.rb atau environment.rb, tetapi itu sepertinya tidak benar (dan mereka juga tidak berfungsi)

Apakah tidak ada cara untuk mendefinisikan sesuatu yang dapat diakses baik dari model maupun dari tampilan? Maksud saya, saya tahu model dan tampilan harus terpisah, tetapi pasti di beberapa domain akan ada saatnya mereka perlu merujuk ke pengetahuan khusus domain yang sama?

AlexC
sumber
Saya menghargai bahwa ini BENAR-BENAR terlambat, tetapi untuk pembaca lain saya bertanya-tanya mengapa Anda tidak hanya mendefinisikan mereka dalam model Anda dan menggunakan pengendali Anda untuk meneruskannya kepada pandangan Anda. Dengan cara ini, Anda akan memiliki pemisahan masalah yang lebih bersih - daripada membuat dependensi antara controller / view DAN model / view.
Tom Tom
2
@ TomTom: Berikan konstanta ini ke setiap tampilan dan helper yang membutuhkannya? Dengan kata lain, buat controller menyadari pandangan mana yang membutuhkan konstanta? Itu terdengar seperti pelanggaran terhadap MVC.
AlexC

Jawaban:

229

Jika model Anda benar-benar "bertanggung jawab" untuk konstanta, Anda harus menempelkannya di sana. Anda bisa membuat metode kelas untuk mengaksesnya tanpa membuat instance objek baru:

class Card < ActiveRecord::Base
  def self.colours
    ['white', 'blue']
  end
end

# accessible like this
Card.colours

Atau, Anda bisa membuat variabel kelas dan accessor. Namun ini tidak dianjurkan karena variabel kelas mungkin bertindak agak mengejutkan dengan pewarisan dan dalam lingkungan multi-utas.

class Card < ActiveRecord::Base
  @@colours = ['white', 'blue']
  cattr_reader :colours
end

# accessible the same as above

Dua opsi di atas memungkinkan Anda untuk mengubah array yang dikembalikan pada setiap pemanggilan metode accessor jika diperlukan. Jika Anda memiliki konstanta yang benar-benar tidak dapat diubah, Anda juga dapat mendefinisikannya pada kelas model:

class Card < ActiveRecord::Base
  COLOURS = ['white', 'blue'].freeze
end

# accessible as
Card::COLOURS

Anda juga bisa membuat konstanta global yang dapat diakses dari mana saja di penginisialisasi seperti pada contoh berikut. Ini mungkin tempat terbaik, jika warna Anda benar-benar global dan digunakan dalam lebih dari satu konteks model.

# put this into config/initializers/my_constants.rb
COLOURS = ['white', 'blue'].freeze

Catatan: ketika kita mendefinisikan konstanta di atas, seringkali kita ingin freezearray. Itu mencegah kode lain dari nanti (secara tidak sengaja) memodifikasi array dengan misalnya menambahkan elemen baru. Setelah suatu objek dibekukan, itu tidak dapat diubah lagi.

Holger Just
sumber
1
Terima kasih banyak. Sepertinya saya melewatkan Ruby class-fu untuk mendefinisikan metode kelas. Tapi saya sebenarnya suka opsi penginisialisasi dalam hal ini, karena warna digunakan dalam beberapa model dan tampilan. Terimakasih banyak!
AlexC
21
Jika pergi config/initializers/my_constants.rbrute, ingatlah untuk me-restart server:touch tmp/restart.txt
user664833
4
The def self.colourscontoh adalah tidak ideal. Setiap kali Anda menelepon def self.colours, instance array baru akan dikembalikan . #freezetidak akan membantu dalam hal ini. Praktik terbaik adalah mendeklarasikannya sebagai konstanta Ruby, dalam hal ini Anda akan selalu mendapatkan kembali objek yang sama.
Zabba
@Zabba Jika alokasi array tunggal membuat perbedaan nyata untuk aplikasi Anda, Anda mungkin seharusnya tidak menggunakan Ruby di tempat pertama ... Yang mengatakan, menggunakan metode dan mengembalikan array yang sama sekali baru setiap kali dapat memiliki pasangan keuntungan: (1) itu adalah hal terdekat yang bisa Anda dapatkan terhadap objek yang tidak dapat diubah pada batas kelas Anda di Ruby dan (2) Anda menjaga antarmuka yang seragam di kelas Anda dengan kemungkinan untuk menyesuaikan nilai kembali nanti berdasarkan keadaan inheren (misalnya dengan membaca warna dari DB) tanpa mengubah antarmuka.
Holger Tepat
@ Holger Hanya, setidaknya salah satu tujuan Anda masih dapat dicapai dengan menggunakan konstanta: class Card; COLOURS = ['white', 'blue'].freeze; def self.colours; COLOURS; end; endYang mengatakan, mengalokasikan array dalam bahasa apa pun berpotensi bermasalah; untuk satu, itu menggunakan memori tanpa alasan (baik). Jika memuat dari DB, dan ingin men-cache nilai, kita juga bisa menggunakan variabel instance kelas, yang dapat dimuat dengan malas menggunakan def self.coloursmetode. Setuju tentang aspek imutabilitas.
Zabba
70

Beberapa opsi:

Menggunakan konstanta:

class Card
  COLOURS = ['white', 'blue', 'black', 'red', 'green', 'yellow'].freeze
end

Malas dimuat menggunakan variabel instance kelas:

class Card
  def self.colours
    @colours ||= ['white', 'blue', 'black', 'red', 'green', 'yellow'].freeze
  end
end

Jika itu adalah konstanta yang benar-benar global ( hindari konstanta global seperti ini ), Anda juga dapat mempertimbangkan untuk memasukkan konstanta tingkat config/initializers/my_constants.rbatas sebagai contoh.

Zabba
sumber
1
Heh. Komentar wajar - kesalahan sintaksis saat mengetik-dari-memori contoh saya :) Terima kasih atas tipnya!
AlexC
2
Kemudian extendmodul di kelas sehingga akan tersedia Card.COLOURS.
undefinedvariable
Saat menggunakan extenditu tidak bekerja untuk saya. Saat menggunakan includesaya dapat mengakses seperti:Card::COLOURS
Abhi
Anda seharusnya TIDAK menempatkan ini di bawah /models. Jauh lebih baik jika Anda membuat penginisialisasi.
linkyndy
@linkyndy Saya akan mengatakan tidak apa-apa untuk meletakkannya di bawah /models, tetapi hanya jika itu di dalam modul, misalnya module Constants; COLOURS = ...; enddalam file yang disebut models/constants.rb.
Kelvin
57

Pada Rails 4.2, Anda dapat menggunakan config.xproperti:

# config/application.rb (or config/custom.rb if you prefer)
config.x.colours.options = %w[white blue black red green]
config.x.colours.default = 'white'

Yang akan tersedia sebagai:

Rails.configuration.x.colours.options
# => ["white", "blue", "black", "red", "green"]
Rails.configuration.x.colours.default
# => "white"

Metode lain pemuatan konfigurasi khusus:

# config/colours.yml
default: &default
  options:
    - white
    - blue
    - black
    - red
    - green
  default: white
development:
  *default
production:
  *default
# config/application.rb
config.colours = config_for(:colours)
Rails.configuration.colours
# => {"options"=>["white", "blue", "black", "red", "green"], "default"=>"white"}
Rails.configuration.colours['default']
# => "white"

Dalam Rails 5 & 6 , Anda dapat menggunakan configurationobjek langsung untuk konfigurasi kustom, selain config.x. Namun, itu hanya dapat digunakan untuk konfigurasi non-bersarang:

# config/application.rb
config.colours = %w[white blue black red green]

Ini akan tersedia sebagai:

Rails.configuration.colours
# => ["white", "blue", "black", "red", "green"]
Halil Özgür
sumber
2
Saya suka yang Rails.configuration.coloursterbaik (meskipun saya berharap itu tidak begitu lama)
Tom Rossi
@ TomRossi saya setuju, mis. Sama configbaiknya configuration. Kami mungkin berharap untuk mendapatkan jalan pintas di beberapa titik :)
Halil Özgür
apakah ini masih cara terbaik dalam rel 6 untuk mendefinisikan konstanta untuk dibagikan di beberapa pengendali? Terima kasih atas jawabannya!
Crashalot
@ Crashalot Masih terdaftar di dokumen. "Terbaik"? Tergantung. Itu bisa di leluhur bersama. Atau ApplicationControllerjika tidak ada yang lain di antara keduanya. Jika konstanta tidak terkait langsung dengan pengontrol, saya masih akan mempertimbangkan konfigurasi global, dll.
Halil Özgür
@ HalilÖzgür terima kasih atas jawabannya. bagaimana Anda mendefinisikan konstanta pada leluhur yang sama?
Crashalot
18

Jika sebuah konstanta diperlukan di lebih dari satu kelas, saya memasukkannya ke config / initializers / contant.rb selalu dalam semua huruf besar (daftar negara di bawah ini terpotong).

STATES = ['AK', 'AL', ... 'WI', 'WV', 'WY']

Mereka tersedia di seluruh aplikasi kecuali dalam kode model seperti:

    <%= form.label :states, %>
    <%= form.select :states, STATES, {} %>

Untuk menggunakan konstanta dalam model, gunakan attr_accessor untuk membuat konstanta tersedia.

class Customer < ActiveRecord::Base
    attr_accessor :STATES

    validates :state, inclusion: {in: STATES, message: "-- choose a State from the drop down list."}
end
Terima kasih Snow
sumber
1
bagus, config/initializers/constants.rbmungkin akan menjadi pilihan yang lebih baik
Adit Saxena
saya juga menggunakan ini, tetapi baru-baru ini menemukan masalah bahwa konstanta ini tidak dapat diakses di application.rb
Zia Ul Rehman Mughal
konstanta saya berfungsi tetapi berhenti karena alasan tertentu (Karena entah bagaimana file saya keluar dari inisialisasi). Setelah memeriksa jawaban ini, saya melihat dan memindahkan mereka kembali dan Sekarang bekerja. Terima kasih
Muhammad Nasir Shamshad
Saya tidak berpikir attr_accessor diperlukan. Apakah Anda berbicara tentang versi Rails tertentu?
Mayuresh Srivastava
16

Untuk pengaturan di seluruh aplikasi dan untuk konstanta global, saya sarankan untuk menggunakan Settingslogic . Pengaturan ini disimpan dalam file YML dan dapat diakses dari model, tampilan, dan pengontrol. Selain itu, Anda dapat membuat pengaturan berbeda untuk semua lingkungan Anda:

  # app/config/application.yml
  defaults: &defaults
    cool:
      sweet: nested settings
    neat_setting: 24
    awesome_setting: <%= "Did you know 5 + 5 = #{5 + 5}?" %>

    colors: "white blue black red green"

  development:
    <<: *defaults
    neat_setting: 800

  test:
    <<: *defaults

  production:
    <<: *defaults

Di suatu tempat dalam pandangan (saya lebih suka metode pembantu untuk hal-hal semacam itu) atau dalam model Anda bisa mendapatkan, misalnya, berbagai warna Settings.colors.split(/\s/). Ini sangat fleksibel. Dan Anda tidak perlu menciptakan sepeda.

Voldy
sumber
7

Gunakan metode kelas:

def self.colours
  ['white', 'red', 'black']
end

Kemudian Model.coloursakan mengembalikan array itu. Atau, buat penginisialisasi dan bungkus konstanta dalam modul untuk menghindari konflik namespace.

Steve Ross
sumber
4

Cobalah untuk menjaga semua konstan di satu tempat, Dalam aplikasi saya, saya telah membuat folder konstanta di dalam inisialisasi sebagai berikut:

masukkan deskripsi gambar di sini

dan saya biasanya menjaga semua konstan dalam file ini.

Dalam kasus Anda, Anda dapat membuat file di bawah folder konstanta sebagai colors_constant.rb

colors_constant.rb

masukkan deskripsi gambar di sini

Jangan lupa me-restart server

Ghanshyam Anand
sumber
1
Ini jawaban terbaik yang saya temukan di sini. Terima kasih.
Promise Preston
3

Opsi lain, jika Anda ingin mendefinisikan konstanta di satu tempat:

module DSL
  module Constants
    MY_CONSTANT = 1
  end
end

Tetapi tetap membuatnya terlihat secara global tanpa harus mengaksesnya dengan cara yang sepenuhnya memenuhi syarat:

DSL::Constants::MY_CONSTANT # => 1
MY_CONSTANT # => NameError: uninitialized constant MY_CONSTANT
Object.instance_eval { include DSL::Constants }
MY_CONSTANT # => 1
meringis
sumber
3

Tempat umum untuk meletakkan konstanta global aplikasi-lebar ada di dalamnya config/application.

module MyApp
  FOO ||= ENV.fetch('FOO', nil)
  BAR ||= %w(one two three)

  class Application < Rails::Application
    config.foo_bar = :baz
  end
end
Dennis
sumber
2

Saya biasanya memiliki model / tabel 'pencarian' dalam program rel saya dan menggunakannya untuk konstanta. Ini sangat berguna jika konstanta akan berbeda untuk lingkungan yang berbeda. Selain itu, jika Anda memiliki rencana untuk memperpanjangnya, katakan Anda ingin menambahkan 'kuning' di kemudian hari, Anda bisa menambahkan baris baru ke tabel pencarian dan selesai dengan itu.

Jika Anda memberikan izin admin untuk memodifikasi tabel ini, mereka tidak akan datang kepada Anda untuk pemeliharaan. :) KERING.

Begini tampilannya kode migrasi saya:

class CreateLookups < ActiveRecord::Migration
  def change
    create_table :lookups do |t|
      t.string :group_key
      t.string :lookup_key
      t.string :lookup_value
      t.timestamps
    end
  end
end

Saya menggunakan seed.rb untuk mengisinya terlebih dahulu.

Lookup.find_or_create_by_group_key_and_lookup_key_and_lookup_value!(group_key: 'development_COLORS', lookup_key: 'color1', lookup_value: 'red');
SriSri
sumber
1

Variabel global harus dinyatakan dalam config/initializersdirektori

COLOURS = %w(white blue black red green)
burung
sumber
Terima kasih! Yang lain sudah menyebutkan ini. Ini adalah baris terakhir dari jawaban Holger, dan Zabba menyebutkan teknik ini juga, meskipun Zabba memperingatkannya.
AlexC
0

Menurut kondisi Anda, Anda juga dapat mendefinisikan beberapa variabel lingkungan, dan mengambilnya melalui ENV['some-var']kode ruby, solusi ini mungkin tidak cocok untuk Anda, tetapi saya harap ini dapat membantu orang lain.

Contoh: Anda dapat membuat file yang berbeda .development_env, .production_env, .test_envdan beban itu sesuai lingkungan aplikasi Anda, periksa ini gen dotenv-rel yang mengotomatisasi ini untuk Anda.

Fangxing
sumber