Widoczność zmiennych

Proszę o wyjaśnienie kilku spraw związanych z zasadami funkcjonowania frameworka.
Może źle rozumiem ale wydawało mi się, że ApplicationController mogę traktować jako front kontroler i zmienne w nim ustawione będą widoczne we wszystkich kontrolerach potomnych jak też widokach z nich wywoływanych. Niestety nic z tego.

[code=ruby]class ApplicationController < ActionController::Base
@site_defaults = Settings.all

before_filter :set_theme

(…)
def set_theme
# select theme for site
@theme = Settings[‘theme’]
prepend_view_path File.join(RAILS_ROOT, ‘app/views’, @theme)
end
end[/code]
Zmienna @theme jest widoczna w kontrolerach i widokach i jest to zrozumiałe bo metoda set_theme jest dziedziczona przez kontrolery potomne.
Ale co zrobić ze zmienną @site_defaults?

Jak widać zamiast set_theme mogłbym dać set_defaults ale nie o to chodzi. Chciałbym dowiedzieć się jak to zrobić aby zmienne ustawiane w ApplicationController były widoczne w widokach.

@site_defaults = Settings.all
To jest chyba wykonywane tylko raz, przy łądowaniu aplikacji, z kolei set_theme jest uruchamiane przy kazdym wywolaniu strony, wiec poprawnie ustawia zmienna @theme.

Ogólnie to kwestia samego Ruby’iego, a nie frameworku.

Aczkolwiek niech ktoś bardziej światły się wypowie, bo ja nie jestem tego pewien.

Bardziej potrzebujesz wyjaśnienia kilku podstawowych zasad object-oriented programming :wink:
Możesz zacząć od różnicy pomiędzy klasą a obiektem (instancją klasy) oraz analogicznych różnic typu zmienna klasowa, zmienna instancyjna, zmienna lokalna.

@Tuptus: Poczytaj o podstawach programowania obiektowego w Rubim jak radzi Tomash :slight_smile:

A poniżej krótki przykład który mam nadzieję pokaże jak działa Twój przykład vs. jak chciałbyś żeby działał:

Twój przykład, @site_defaults nie jest w ogóle ustawiana:

[code=ruby]class Parent
@site_defaults = ‘defaults set in parent’

def test
puts @site_defaults
end
end

class Child < Parent
end

p = Parent.new
c = Child.new

p.test
c.test[/code]
Przykład z inicjalizacją zmiennej:

[code=ruby]class Parent
def initialize
@site_defaults = ‘defaults set in parent’
end

def test
puts @site_defaults
end
end

class Child < Parent
end

p = Parent.new
c = Child.new

p.test
c.test[/code]
Przykład ze zmienną klasy (class var):

[code=ruby]class Parent
@@site_defaults = ‘defaults set in parent’

def test
puts @@site_defaults
end
end

class Child < Parent
end

p = Parent.new
c = Child.new

p.test
c.test[/code]
oraz ze stałą:

[code=ruby]class Parent
SITE_DEFAULTS = ‘defaults set in parent’

def test
puts SITE_DEFAULTS
end
end

class Child < Parent
end

p = Parent.new
c = Child.new

p.test
c.test[/code]
słowem do wyboru do koloru :slight_smile:

Dziękuję za odpowiedzi choć nie uzyskałem wyjaśnienia. “Poczytaj o podstawach programowania obiektowego w Rubim” w niczym nie pomaga. Gdybym wiedział czego szukać to oczywiście bym poszukał i nie zawracał głowy. Wprawdzie znalazłem ciekawy dokument ( http://railstips.org/2006/11/18/class-and-instance-variables-in-ruby ) ale nie wyjaśnia wszystkich wątpliwości.
@hosiawak: w pierwszym przykładzie @site_defaults jest ustawiana ale dostępna jest nie przez metody obiektowe tylko klasowe klasy, w której ta zmienna jest ustawiana. Trzeci przykład również nie daje oczekiwanych rezultatów.

[code=ruby]class ApplicationController < ActionController::Base

  @@site_defaults = Settings.all
  (...)
  end[/code]

W kontrolerach zmienna @@site_defaults wprawdzie jest dostępna ale już nie w widoku

<title><%= @@site_defaults['title'] -%></title>

Niestety nie działa.

Problem rozwiązałem wykorzystując metodę initialize

[code=ruby]class ApplicationController < ActionController::Base

def initialize
	super
	@site_defaults = Settings.all
end

(…)[/code]
Nadal jednak nie załapałem w jaki sposób zmienne obiektowe z kontrolerów stają się zmiennymi obiektowymi widoków. A może to nie są zmienne obiektowe?

Dostałeś trochę więcej wskazówek niż to. I serio nadrób te zaległości.

Obie Fernandez “The Rails Way”, jeden z pierwszych rozdziałów.

Dobra, streszczę Ci :wink: Generalnie to jedno z tych miejsc, gdzie railsy mają trochę “magii” – otóż po zakończeniu przetwarzania kontrolera i zainicjalizowaniu widoku, wszystkie zmienne instancyjne (te z @) są kopiowane z kontrolera do analogicznych zmiennych widoku.

Zmienne klasowe, czyli @@, co prawda są widziane ze wszystkich instancji kontrolera, ale nie są już kopiowane do instancji widoku, stąd zachowanie które zauważyłeś.

Dlaczego klasa w ruby może mieć zmienną instancyjną, niemającą nic wspólnego ze zmiennymi instancyjnymi obiektów (instancji) tej klasy – patrz pierwszy akapit. :wink:

Zatem trzeba ustawiać zmienną instancyjną, jeśli ma być dostępna w widokach. Zrobienie tego dla wszystkich można osiągnąć albo metodą podaną przez Hosiawaka (bardzo “rubiowa” :slight_smile: ), albo – bardziej railsowo – opakować w metodę i dodać ją do before_filter.

Poczytaj więcej o klasach. Tak się składa, że w Rubim klasy to też obiekty, tyle, że klasy Class.

Jak masz konstrukcję rodzaju:

class A @b = "b" end
To @b jest zmienną instancyjną klasy A :slight_smile: (nie jest “zwykłą” zmienną instancyjną, ani też zmienną klasową). Dlatego jest widoczna w metodach klasowych, bo są do de facto metody singletownowe (czyli metody definiowane per obiekt) odpowiednich klas (czyli obiektów klasy Class, przypominam).

Inna sprawa, że w interakcji między kontrolerem a widokiem jest trochę magii - i za tym stoją Railsy. Po prostu wszystkie zwykłe zmienne instancyjne (czyli zmienne pojawiające się w definicjach metod instancyjnych, zawierające jeden @), są kopiowane do widoków. Pozwala to w prosty sposób przekazać odpowiednie wartości do widoków.

Swoją drogą nie dziwię się, że czytając informacje na temat metod klasowych i instancyjnych nie znalazłeś informacji na ten temat. Po prostu “zmienne instancyjne klas” (czyli zmienne z jednym @ pojawiające się w ciałach klas a nie metod) raczej nie są tam omawiane. Wykorzystywane są zazwyczaj do meta programowania i innych zaawansowanych zagadnień. Swoją drogą zastanawiam się skąd wpadłeś na pomysł aby zrobić to co opisałeś powyżej.

http://www.pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming

polecam :slight_smile: mnie sporo rozjaśniły

Ładnie się, Olek, zdublowaliśmy :wink:

:smiley:

[quote=sevos]http://www.pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming

polecam :slight_smile: mnie sporo rozjaśniły[/quote]
Dave Thomas miał podobną prezentację na Scotland on rails: http://www.engineyard.com/blog/community/scotland-on-rails/page-2/, jak ktoś nie chce bulić za screencasta :wink:

Dopiero co uczę się Rails i mam taki problem. W widoku ‘wiadomości’ chcę użyć metody z kontrolera ‘uzytkownika’. W kontrolerze ‘użytkownika’ napisalem helper_method :list_students, ale jak chce użyć tego w widoku ‘wiadomości’ to wyskakuje błąd, że metoda niezdefiniowana.

A czy ‘wiadomości’ są jednym z widoków kont. ‘użytkownika’? Zgduję, że nie. Lepiej chyba umieścić tę metodę w ApplicationController i tam dać helper_method. Generalnie jednak upewnij się, czy taka metoda jest Ci faktycznie potrzebna - raczej powinieneś odpowiednio ustawić zmienne w kontrolerach, a nie korzystać z metod pomocniczych typu “list_students”.

Mam kontrolery Users i Messages. Oraz tabele users i messages. W jaki sposób można dobrać się do wartości z tabeli ‘users’ w kontrolerze ‘Messages’?

Poprzez model User. Warstwą komunikującą się z bazą danych sa modele.

Ale w innym kontrolerze niz ‘Users’ nie moge korzystac z danych z tabeli ‘users’. Może czegoś nie rozumiem, bo dopiero co zaczynam z Rails. Więc jak mógłbyś mi tak wyjaśnić bardziej.

W którym kontrolerze zrobisz User.find(…) nie ma znaczenia.

Robię taką metodę w kontrolerze Users: def list_students @students = User.find :all, :conditions => { user.role => "Student" } end
i pisze helper_method :list_students no i w widoku ‘Messages’ nie widzi mi tej metody.

Ale tu jest mowa o dwóch różnych rzeczach - czym innym jest “helper_method”, która zasadniczo nie służy do tego, co chcesz zrobić.
Po prostu w kontrolerze MessagesController możesz zrobić coś takiego (np. w metodzie show)

def show #... @students = User.find(:all, ...) #... end
A potem w widoku messages/show:

# na przykład <%- for student in @students %> <%= student %> <%- end %>
Poczytaj więceję o podstawach Railsów, bo bez tego pójdziesz zupełnie nia tą drogą co trzeba.

Mam taki problem w kontrolerze. Mam dwie metody:[code]def remove
binContent
for message in list_messages
if current_user.id == message.id_recepient && @id_message == message.id
mess.update_attribute(‘recepient_delete’, true)
end
if current_user.id == message.id_sender && @id_message == message.id
message.update_attribute(‘sender_delete’, true)
end
end
end

def binContent
for message in list_messages
if message.id.to_s == params[:par]
@id_message = message.id
end
end
end[/code]
Chciałem żeby @id_message z binContent było widoczne w metodzie remove. Gdy w innych metodach tak robiłem wszystko działało, jakoś teraz nie chce…
remove wykonuje sie po nacisnieciu przycisku, a binContent to mam tez taki widok.