Gdy zaczynacie swoją przygodę z programowaniem wydaje wam się, że na wszystko można zrobić aplikacje, w końcu to tylko kolejna warstwa nad bazą danych. W pewnym momencie dochodzi się jednak do wniosku, że bez sensu jest pisać nową aplikacje skoro to samo można zrobić w excelu.
Excel to potężne narzędzie i raczej prędzej niż później będzie potrzebować importu lub exportu danych do tego programu. Biznes wymaga excela. Albo chociaż pliku CSV.
Na potrzeby tego video utworzę nową aplikacje
rails new excel-example -d postgresql
gdy mamy już aplikacje skorzystam z generatora scaffold i utworzę model Item by nasza aplikacja nie była pusta
rails generate scaffold Item name:string price:float
pamiętajmy odpalić migracje
rails db:migrate
chociaż z racji, że to nowa aplikacja to jeszcze trzeba by bazę danych utworzyć rails db:create
na początek może export danych utwórzmy jakieś by było co exportować
rails console
10.times do |i|
Item.create(name: "item-#{i}", price: rand(1...100))
end
włączmy sobie naszą aplikacje by zobaczyć jak to wygląda
na samym dole jak widzicie mamy przycisk new item
dodajmy sobie do niego przycisk export
w widoku dodajmy
<%= link_to 'Export Items', export_items_path %>
jeżeli odświeżycie stronę powinniście zobaczyć błąd ponieważ nie ma takiego routa
musimy go dodać
zajrzyjmy do routes.rb
i dodajmy
Rails.application.routes.draw do
resources :items do
get :export, on: :collection
end
end
adres jest już poprawny. Z tym, że nic nam się nie wydarzy
przejdźmy więc do kontrolera items_controller
i utwórzmy nową metodę export
def export
end
teraz musimy stworzyć właściwy export
dodajmy gemy odpowiedzialne za generowanie plików
gem 'caxlsx'
gem 'caxlsx_rails'
i pamiętajmy o bundle install
w metodzie export
w naszym kontrolerze zmieńmy
def export
@items = Item.all
render xlsx: "create", template: "items/export"
end
musimy jeszcze utworzyć widok
export.axlsx
a w nim
wb = xlsx_package.workbook
wb.add_worksheet(name: "Items") do |sheet|
@items.each do |item|
sheet.add_row [item.id, item.name, item.price]
end
end
jeżeli chcecie mieć jeszcze nazwy tych pól to plik musi wyglądać tak
wb = xlsx_package.workbook
wb.add_worksheet(name: "Items") do |sheet|
sheet.add_row ['Id', 'Name', 'Price']
@items.each do |item|
sheet.add_row [item.id, item.name, item.price]
end
end
wtedy przed pętlą z każdym przedmiotem dodamy tytuły pól
przy tak prostym exporcie pewnie wystarczył by export do pliku CSV, a jeżeli chcecie robić bardziej skomplikowane rzeczy odsyłam do dokumentacji gema.
zrestartujmy serwer i zobaczmy jak to działa
na http://localhost:300/items
jeżeli wszystko jest ok pora na import pliku.
Na cele tego filmu załóżmy, że importować będziemy ten sam plik, numery ID są dla nas unikalne więc ID z pliku uznajemy, za ID elementu, resztę danych będziemy nadpisywać
wizualnie niestety ja nie umiem zrobić linka który by otwierał wybór plików więc dodamy prosty formularz. Multipart używane jest właśnie przy uploadzie plików Dodam jeszcze nad nim nową linie (tag HR) by było to lepiej widoczne
<hr>
<%= form_tag import_items_path, multipart: true do %>
<%= file_field_tag :file %>
<%= submit_tag "Import" %>
<% end %>
w routes.rb
należy dodać
resources :items do
get :export, on: :collection
post :import, on: :collection
end
teraz zajmijmy się naszą metodą import
Do importu pliku potrzebujemy innego gema
...
gem "roo", "~> 2.8.0"
def import
file = params[:file].tempfile
spreadsheet = Roo::Excelx.new(file).to_a
do tego dodajmy pętle dla każdego wpisu i przypiszmy czym jest każde pole
spreadsheet.each do |item_array|
id = item_array[0]
name = item_array[1]
price = item_array[2]
end
znajdźmy nasz item item = Item.find_or_create_by(id: id)
w ten sposób znajdzie lub utworzy pole o wybranym id. Pamiętajcie plik excela to nasze źródło prawdy
teraz zaktualizujmy nasze dane
item.update(name: name, price: price)
na koniec dodajmy jeszcze redirect redirect_to items_path
by odświeżyć nasz widok
ostatecznie metoda wygląda tak
def import
file = params[:file].tempfile
spreadsheet = Roo::Excelx.new(file).to_a
spreadsheet.each do |item_array|
id = item_array[0]
name = item_array[1]
price = item_array[2]
item = Item.find_or_create_by(id: id)
item.update(name: name, price: price)
end
redirect_to items_path
end
Miejcie na uwadze, że to są proste operacje które wykonają się bardzo szybko, w przypadku bardziej skomplikowanych, bardziej czasochłonnych rzeczy powinniśmy pomyśleć o tym by nasze akcje działy się w background jobie
możemy wyedytować nasz plik i zobaczyć co się stanie gdy go wrzucimy
Na dzisiaj to już wszystko
Życzę miłego kodowania