Metody nie związane z bazą danych

Potrzebuję wykonać program, który nie tylko będzie operował na danych z bazy, ale również dokonywał obliczeń na tych danych i wielkościach spoza bazy. Mimo poszukiwań w sieci i w książkach nie potrafię poradzić sobie z takim problemem: stworzyłem osobny kontroler calculation_controller gdzie mam metodę powiedzmy

def wynik
@wynik = 10.0
end

i jak teraz wyświetlić to na widoku INNYM NIŻ SPECJALNIE DLA TEJ METODY?
Próbuję w zdefiniowanym przez generator widoku dla danej tabeli ale ciągle wyłazi mi błąd. Nawet jak metodę przeniosę do kontrolera “tabeli” czyli klasy tej tabeli to też nic z tego. O co tu chodzi? W jednej z książek był opisany kalkulator ale kod był wprowadzony do “kalkulatorowego” widoku, w innym miejscu tej książki było napisane, że nie należy kodu pisać w widokach (słusznie). Proszę o wyjaśnienie tego problemu, co robię nie tak?

Nie powinien się tym zajmować kontroler tylko model. Możesz w katalogu app/models/ utworzyć zwykłą klasę rubiego

[code=Ruby]class Calculation

def initalize(a, b)
@sum = a + b
end

def sum
@sum
end
end[/code]
I potem skorzystać z tego już w kontrolerze, czy widoku:

[code=Ruby]class SomeController < ApplicationController
def index
@calculation = Calculation.new(5, 10)
end
end

#index.erb
<%= @calculation.sum %>[/code]
Dużo programistów zapomina, że nie wszystkie klasy muszą mieć odzwierciedlenie w bazie.

P.S. Kluczowe jest tutaj to, że klasa Calculation nie dziedziczy po ActiveRecord::Base. (Pierwsza linijka pierwszego przykładu)

Działa! Wielkie dzięki. Rozumiem więc, że wszystkie metody nie związane z bazą danych mają być umieszczane w modelach a nie kontrolerach. W odpowiednim kontrolerze należy jedynie stworzyć obiekt klasy by z niego móc skorzystać w widoku związanym z tym kontrolerem. Przyznam szczerze, że nie znalazłem w literaturze takich informacji.

Teraz mam jakby problem odwrotny. W klasie obliczeniowej, którą stworzyłem w modelu, potrzebuję się odwołać do pól z bazy, tabeli. W taki sposób, by dla każdego z rekordów była obliczana wartość, np @wartosc = 2 * “wartość z kolumny bazy”. Pisząc

@wartosc = Tabela.find(1).wartosc

dostaję to co chcę, ale, oczywiście dla pierwszego rekordu. Pisząc

@wartosc = Tabela.find(params[:id]).wartosc

czyli chcąc się odwołać do bieżącego rekordu, mam błąd - nieznana metoda params.
Dlaczego tak się dzieje? Taka konstrukcja w kontrolerze działa bez zarzutu. Jak takie coś rozwiązać?

params to są atrybuty żądania, do których masz dostęp w kontrolerze. Jeśli chcesz ich użyć w modelu, musisz je przekazać do modelu na przykład tak:

[code=Ruby]class SomeController < ApplicationController
def index
@calculation = Calculation.new(params)
end
end

class Calculation

attr_accessor :params

def initialize(params={})
@params = params
end
end[/code]
I wtedy w Calculation masz już do nich dostęp. O wiele lepiej jednak byłoby, gdybyś przekazał tylko te parametry, które wykorzystasz w modelu, w twoim przypadku params[:id].

[quote=MaxZorin]Teraz mam jakby problem odwrotny. W klasie obliczeniowej, którą stworzyłem w modelu, potrzebuję się odwołać do pól z bazy, tabeli. W taki sposób, by dla każdego z rekordów była obliczana wartość, np @wartosc = 2 * “wartość z kolumny bazy”. Pisząc

@wartosc = Tabela.find(1).wartosc

dostaję to co chcę, ale, oczywiście dla pierwszego rekordu. Pisząc

@wartosc = Tabela.find(params[:id]).wartosc

czyli chcąc się odwołać do bieżącego rekordu, mam błąd - nieznana metoda params.
Dlaczego tak się dzieje? Taka konstrukcja w kontrolerze działa bez zarzutu. Jak takie coś rozwiązać?[/quote]
Jest w ogole sens tworzyć oddzielny “model” Calculation? Do podanego przykladu wystarczy metoda w modelu od tej Tabeli…

Dziękuję za podpowiedzi. Coś mi kurcze to nie idzie. Jak ma wyglądać to w metodzie liczącej? Jak bym tego nie skonstruował to mam błąd: Couldn’t find Tabela with ID=284338, czyli traktuje parametr id jak niezainicjalizowaną zmienną(?). Mam się odwołać do metody dostępowej z attr_accessor (to takie jakby property z Delphi chyba)? Też to niczego nie zmienia. Poza tym, tu nie do końca kapuję, konstrukcja typu

@x = Tabela.find(params[:id]).wartosc

nie działa również w modelu Tabela(!?) Czyli nie jest tak, że wszystko co w kontrolerze, modelu czy widoku jest wszędzie w obrębie klasy widoczne. Myślałem, że na tym polega idea. Metody w kontrolerze i ich parametry są widoczne w modelu, który to się komunikuje z bazą. Trochę (nawet więcej niż trochę) mam kłopoty z taką ideą.

[quote=grk][quote=MaxZorin]Teraz mam jakby problem odwrotny. W klasie obliczeniowej, którą stworzyłem w modelu, potrzebuję się odwołać do pól z …
Dlaczego tak się dzieje? Taka konstrukcja w kontrolerze działa bez zarzutu. Jak takie coś rozwiązać?[/quote]
Jest w ogole sens tworzyć oddzielny “model” Calculation? Do podanego przykladu wystarczy metoda w modelu od tej Tabeli…[/quote]
Pewnie i racja. Ale przy okazji chcę po prostu trochę poćwiczyć. Tym bardziej, że: jestem kompletnie zielony, 2. dziedzina, którą się zajmuję to technika i dużo obliczeń. Więc nie są to aplikacje czysto bazodanowe typu księgowość, magazyn, sklep. A na takich przykładach głównie opierają się wszystkie pomoce do railsów.

Chyba źle zrozumiałeś komunikat błędu. To oznacza, że w bazie danych nie masz obiektu klasy Tabela z identyfikatorem równym 284338.

To nie jest ‘niezainicjowana zmienna’, tylko wynik zapytania SQL “SELECT * FROM tabelas WHERE id = 284338” zwraca zero rekordów.

Co do tej property to dobrze trafiłeś. “Makro” attr_accessor :params definiuje dwie metody w klasie, w której zostało toto uzyte. W tym przypadku zaoszczędza Ci to ręcznego pisania:

def params @params end def params=(v) @params = v end
Spokojnie możesz te dwie metody wpisać ręcznie zamiast używać attr_accessor i efekt będzie dokładnie taki sam.

[quote=MaxZorin]Poza tym, tu nie do końca kapuję, konstrukcja typu

@x = Tabela.find(params[:id]).wartosc

nie działa również w modelu Tabela(!?) Czyli nie jest tak, że wszystko co w kontrolerze, modelu czy widoku jest wszędzie w obrębie klasy widoczne. Myślałem, że na tym polega idea. Metody w kontrolerze i ich parametry są widoczne w modelu, który to się komunikuje z bazą. Trochę (nawet więcej niż trochę) mam kłopoty z taką ideą.[/quote]
Nie do końca rozumiem co chcesz powiedzieć. Co znaczy, że konstrukcja nie działa? Błędy zapewne masz różne. Musisz się w nie wczytać.
Jeżeli użyłeś kontrukcji find(params[:id]) w modelu, to dostałeś pewnie błąd związany z brakiem metody ‘params’, albo z tym, że zwracany prze nią obiekt nie rozumie metody “[]”.

Najbardziej mnie zastanawia co właściwie chcesz zrobić. Czy tworzysz program, który ma jakiś konkretny cel, czy po prostu bawisz się językiem?

Proponuję Ci wybrać jakiś rzeczywisty problem, który będziesz programem rozwiązywał. Zalety masz dwie - cel będzie Ci pomagał odrzucać rozwiązania nie mające sensu, a na końcu uzyskasz program, który będziesz mógł zaoferować światu (za odpowiednią kasę lub nie).

Zdefiniuj na początek zadanie. Wtedy łatwiej będzie Ci podpowiedzieć rozwiązania, które można by określić mianem dobrej praktyki - a znajomość dobrej praktyki osobiście cenię bardziej niż znajomość kruczków danego języka (lub frameworka).

Edit: połknąłem kila wyrazów. Pisanie w językach naturalnych przychodzi mi trudniej.

Jak pamiętam z czasów kiedy zaczynałem przygodę z Rubim i z Railsami (jeszcze nie do końca umiałem ich odróżnić), pewnym odkryciem (efektem “AHA!”) było dla mnie uświadomienie sobie, że łatwiej się pracuje, jeśli zawsze wiem z jakim obiektem działam.

Pamiętaj zawsze, że model i kontroler to dwa różne obiekty. Pamiętaj, że każda nazwa której używasz w kodzie (o ile nie jest zmienną) jest metodą zdefiniowaną w jakiejś klasie, i niekoniecznie musi być zdefiniowana w innej - wtedy łatwiej będzie pamiętać dlaczego ‘params’ w modelu nie jest widoczne.

Metody, które wyglądają na globalne (jak np. puts) są zwyczajnie zdefiniowane w klasie Object, z której to klasy wszystko dziedziczy.

Problem, który chcę oprogramować jest jak najbardziej realny i właściwie dość banalny. Ale służy mi on do nauki. W tej części wątku chodzi mi o to jak z poziomu klasy nie związanej z tabelą dobrać się do danych tabeli (Tabela). W widoku muszę wyświetlić tabelarycznie pewne wielkości z Tabeli i dla każdego rekordu wielkości obliczone (w klasie Calculation) na podstawie stałych i danych z tabeli. Czyli w uproszczeniu:
nazwa v_tab1 v_tab2 v_obl1 v_obl1 …
x 12.3 45.6 67.8 90.1
y …
gdzie np. v_obl1 = 2 * v_tab2

czyli muszę w klasie liczącej odwołać się do pola bieżącego rekordu w tabeli. I z tym sobie nie radzę.
Zgodnie z sugestiami z wątku w kontrolerze stworzyłem w metodzie index obiekt klasy @calculation = Calculation.new(params) i przekazałem params. W klasie Calculation:
attr_accessor :params

def initialize(params={})
@params = params
end

a co w metodzie
def v_obl1
@v_obl1 = ???
end

konstrukcja @v_obl1 = Tabela.find(params[:id]).v_tab1

nie działa. Dlaczego skoro params przenosi? Zresztą to tylko jedna ze złych kombinacji, którą "stestowałem.

Nie dobieraj się do danych tabeli z klasy nie związanej z tabelą.
Jeśli chcesz czegoś od tabeli, poproś o to klasę związaną.

Nie rób klasy Calculation jeśli masz klase Tabela.
Interesujące Cię obliczenie dodaj jako metodę do klasy Tabela i wynik tej metody wyświetlaj w widoku.

Zaproponowano Ci klasę Calculation, bo nie podałeś szerszego kontekstu problemu. Jeśli dobrze rozumiem teraz Twój problem, to to był zły pomysł.

[code]class Tabela < ActiveRecord::Cośtam
def v_obl_1
v_tab2 * 2
end
end

W kontrolerze:

def show
@tabele = Tabela.find(:all)
end

W widoku:

<% @tabele.each do |tabela| %>

<%=h tabela.v_tab2 %> <%=h tabela.v_obl_1 %> <% end %>[/code] (Z tym helperem 'h' w widoku to dopasuj się do wersji rails, z której korzystasz. W nowych podobno nie jest potrzebny )

Rzeczywiście, wygląda na to, że w takim podejściu działa jak należy. Dziękuję. Rozumiem więc, że jeśli, z jakiś ważnych powodów nie muszę, to mam nie kombinować z klasami nie związanymi z bazą. Widać muszę się mocno przestawić z delphi, co nie do końca jest chyba łatwe.

Może stwierdzenie “z klasami nie związanymi z bazą” niezupełnie oddaje to, co chciałem powiedzieć.

Bardziej mi chodzi o to, że każde działanie powinien wykonywać ten obiekt, który posiada najwięcej danych do tego potrzebnych. W tym przypadku wszystkie dane posiadał obiekt klasy Tablica, więc oddzielna klasa była całkiem zbędna.

Zaproponuję porównanie:
Można albo poprosić Kowalskiego o dowód osobisty i sprawdzić wpisany tam adres zamieszkania, albo można spytać Kowalskiego gdzie mieszka.

O ile w życiu to pierwsze podejście czasami ma większy sens, w programowaniu to drugie częściej wydaje mi się lepsze. Szczególnie w obiektowym, gdzie enkapsulacja jest jedną z piękniejszych koncepcji.