Astazi vom incarca imagini cu Lucky si crystal! Pentru a demonstra acest lucru, voi face o aplicatie care permite incarcarea de imagini intr-o galerie care este legata de adresa dvs. IP. Deoarece vom gazdui aceasta aplicatie pe eroku, putem folosi antetul X-FORWARDED_FOR al lui heroku pentru a obtine adresa IP a utilizatorului.

Retineti ca, datorita proxys-urilor si potentialului de spoofing ip, aceasta nu este o metoda sigura de restrictionare a accesului utilizatorului si nu va recomand sa o utilizati pentru date importante.

Codul finalizat

Pentru a vedea codul terminat si a-l rula local, puteti clona repo-ul si puteti vedea filiera incarcata de imagini.

git clone [email protected]: mikeeus / lucky_demo.git git checkout imagini-incarcari CD imagini-incarcari bin / setup norocos db.create && lucky db.migrate

Si puteti rula specificatiile pentru a vedea rezultatul verde frumos :).

spec. cristal / fluxuri / imagini_spec.cr

Tabel imagine

Mai intai permiteti crearea tabelului Image.

noroc gen.migration CreateImages

Vom adauga urmatoarele coloane pentru a pastra numele de fisier, adresa IP a proprietarului si vom inregistra chiar numarul de ori cand imaginea este vizualizata de utilizatori.

clasa Image :: V20180728000000 <LuckyMigrator :: Migratie: V1 def migreaza crea: imaginile adauga nume de fisier: Sir adaugati proprietar_ip: Sir adaugati vizualizari: Int32 final executa “CREATE INDEX proprietar_ip_index ON imagini (proprietar_ip);” executati “CREATE UNIQUE INDEX filename_index ON images (nume de fisier);” end def rollback drop: imaginile end end

Vom adauga, de asemenea, un index unic pe numele de fisier si un index normal in coloana de proprietar_ip, astfel incat sa putem obtine rapid colectii de imagini bazate pe acesta.

Specificatii

Cand permiteti incarcarea in aplicatia noastra, vom dori sa restrictionam fisierele in functie de tipul si dimensiunile posibile. Vom crea specificatii pentru a verifica acest lucru pentru noi. Din pacate, Crystal nu ne ofera informatii despre dimensiunile unei imagini a cutiei noastre, asa ca mai tarziu vom folosi crymagic pentru a obtine aceste informatii pentru noi.

Limitele pe care le vom pune in incarcarile noastre sunt urmatoarele:

  • formate: JPG, GIF, PNG
  • dimensiuni max .: 1000×1000
  • dimensiune max: 250kb

Am adaugat cateva imagini in folderul nostru de active care incalca fiecare din aceste reguli, precum si o imagine care este perfecta.

DE asemenea: Am primit aceste imagini de pe acest site uimitor: africandigitalart.com, pe care recomand sa le verific.

public / asset / images / test / perfect_960x981_56kb.jpg too_big_900x900_256kb.jpg too_tall_1001x1023_95kb.jpg gresit_format_240x245_235kb.bmp

In continuare, vom crea un ImageBox in cazul in care avem nevoie sa instantaneze Imagini in testele noastre.

# spec / support / boxes / image_box.cr clasa ImageBox <LuckyRecord :: Box def initializeaza numele de fisier “perfect_960x981_56kb.jpg” proprietar_ip “0.0.0.0” vizualizari 1 final

Fluxul norocos

Lucky foloseste conceptul de Flows, care sunt clase care incapsuleaza comportamentul testelor browserului. Vom crea una acum care incarca o imagine pe pagina noastra de pornire si are doua metode pentru a verifica daca aceasta a reusit sau nu.

Putem simula incarcarea unui fisier adaugand calea completa a fisierului la introducerea fisierului formularului. Apoi, faceti clic pe metoda „@ upload-image” va cauta un element cu eticheta [flow_id = upload-image] in pagina si faceti clic pe ea.

# spec / support / flow / images_flow.cr clasa ImagesFlow <BaseFlow def upload_image (filepath) vizitati Home :: Index file_form ImageForm, imagine: File.expand_path (filepath) faceti clic pe “@ upload-image” final def image_should_be_created (filepath) image = find_image_by_filename? iLike ( “% # {filename}%”). mai intai? sfarsit final

Acum putem folosi acest flux si imaginile noastre de testare pentru a scrie specificatiile noastre. Crystal are suport de clasa intai pentru specificatii si putem vedea asta cat de simplu este sa le scriem. Folosim Spec.after_each pentru a sterge imaginile cu o stergere! metoda care va sterge, de asemenea, fisierul de baza dupa fiecare spec.

# specs / flow / images_spec.cr necesita “../spec_helper” descrie “Fluxul de imagini” do Spec.after_each do ImageQuery.new.map (&. sterge!) final descrie “incarcarea” do it “functioneaza cu o imagine valida” nu flow = ImagesFlow.new flow.upload_image (valid_image_path) flow.image_should_be_created (valid_image_path) sfarseste “nu functioneaza cu imaginea de mai sus de 250kb” do flow = ImagesFlow.new flow.upload_image (too_big_image_path) flow.image_path_flou_image_sh_ cu dimensiuni peste 1000×1000 “do flow = ImagesFlow.new flow.upload_image (too_tall_image_path) flow.image_should_not_be_created (too_tall_image_path) se incheie” nu functioneaza cu imaginea formatului gresit “do flow = ImagesFlow.new flow.upload_image”image_should_not_be_created (wrong_format_image_path) end end end privat def valid_image_path “public / active / images / test / perfect_960x981_56kb.jpg” end private def too_tall_image_path “public / active / images / test / too_tall_1001x1023_95” /test/too_big_900x900_256kb.jpg “end private def wrong_format_image_path” public / asset / images / test / wrong_format_240x245_235kb.bmp “finalpublic / asset / images / test / gresit_format_240x245_235kb.bmp “final”public / asset / images / test / gresit_format_240x245_235kb.bmp “final

Rularea acestor specificatii le va determina sa esueze, deoarece nu am implementat nimic. Sa construim acum modelele, actiunile si paginile noastre pentru a le face sa functioneze.

Model de imagine

Va trebui sa persistam referintele la imaginile noastre, ip-ul proprietarului lor si numarul de vizualizari la baza de date. Deci, sa generam un model care sa faca asta.

Si putem completa modelul Image cu coloanele sale si cateva metode ajutatoare pentru a construi stergerea caii, url-ului si gestionarea. Imaginile vor fi salvate la public / asset / images / … si vor fi disponibile public pe www.example.com/assets/images / …. Vom adauga si un caz pentru imaginile de testare care vor fi stocate in directorul public / active / imagini / test /.

# src / models / image.cr clasa Imagine <Tabela BaseModel: imaginile fac numele de fisier al coloanei: String column propriet_ip: String column views: Int32 end def url “# {Lucky :: RouteHelper.settings.base_uri} # {path}” end def cale daca Lucky :: Env.test? “/assets/images/test/# patentself.filename}” else “/assets/images/# patentself.filename}” end end def full_path “public # {path}” end def delete! File.delete (full_path) sterge capatul final

In continuare, putem completa ImageForm. Formularele din Lucky sunt responsabile pentru crearea si actualizarea modelelor. Folosim fisiere pentru a declara ce coloane vom actualiza si vom declara o imagine de camp virtual pentru a pastra imaginea incarcata pana cand o putem salva. Vom adauga, de asemenea, fisierul de nevoi si ip necesitati, deoarece le vom transmite atunci cand instantam formularul.

uuid este folosit pentru a ne asigura ca avem nume de fisiere unice si pentru a face aproape imposibil pentru cineva sa vizualizeze imaginea fara numele de fisier.

Am reunit toate acestea impreuna in metoda de pregatire care salveaza imaginea si seteaza coloanele. In prezent nu face validari, dar vom ajunge la asta mai tarziu.

necesita clasa „uuid” ImageForm <Image :: BaseForm fisier nume de fisier fillable vizualizari fillable proprietar_ip imagine virtuala: String are nevoie de fisier: Lucky :: UploadedFile, pe:: create needs ip: String, on:: create getter new_filename def prepare save_image views.value = 1 filename.value = new_filename owner_ip.value = IP end privat def incarcat fisier.not_nil! end private def save_image File.write (save_path, File.read (uploaded.tempfile.path)) end private def new_filename @new_filename || = “# {UUID.random} _ # {uploaded.metadata.filename}” end private def image_path daca Lucky :: Env.test? “asset / images / test /” + new_filename else “active / images /” + new_filename end end private def save_path “public /” + image_path end end

Acum trebuie sa cream interfata de utilizator pentru a permite incarcarile si actiunile pentru salvarea formularelor.

Pagina principala

In prezent, aplicatia noastra afiseaza pagina principala implicita a lui Lucky. Vom crea o noua pagina principala care ne contine formularul si ne va permite sa incarcam fisiere. Sa generam pagina.

pagina gen. norocoasa Acasa :: IndexPage

Apoi, vom adauga un formular care are enctype: „multipart / form-data” si postari la Images :: Create care se va ocupa de crearea imaginii noastre. Adaugam formularul de nevoi: ImageForm pentru a spune actiunea care face ca aceasta pagina sa fie transmisa intr-un nou formular. Vom reda, de asemenea, orice eroare intr-o lista sub intrare.

clasa Acasa :: IndexPage <GuestLayout are nevoie de formular: ImageForm def continut render_form (@form) end privat def render_form (f) form_pentru Imagini :: Creati, enctype: “multipart / form-data” do text_input f.image, tip: “file “, flow_id:” file-input “ul do f.image.errors.each do | err | li “Image # {err}”, class: “eroare” end end send “Upload Image”, flow_id: “upload-image” end end end end

Si sa ne schimbam actiunea Pagina principala :: Index pentru a arata pagina noastra de index mai degraba decat pagina de bun venit a lui Lucky.

# src / actions / home / index.cr clasa Home :: Index <BrowserAction include Auth :: SkipRequireSignInexposepose current_user get “/” do daca current_user? redirectioneaza-ma :: Afiseaza altceva: Home :: IndexPage end end end end

Obtineti curent_ip in actiuni

Nu vom folosi current_user pentru autentificare, ci trebuie sa obtinem adresa IP a cererii. Cand aplicatia noastra este pe eroku, putem folosi antetul X-FORWARDED-FOR care este setat automat. La nivel local o vom seta doar la nivel local.

Vom adauga aceste metode in BrowserAction. Intrucat celelalte actiuni ale noastre mostenesc din clasa Acasa :: Index <BrowserAction, acestea vor face aceste metode disponibile pentru noi.

# src / actions / browser_action.cr clasa abstracta BrowserAction <Lucky :: Action … def current_ip current_ip? .not_nil! sfarseste def def current_ip? daca Lucky :: Productie env.? context.request.headers [ “X-Transmis-FOR”]? altfel sfarsit „local” sfarsit … sfarsit

Imaginile creeaza actiune

Acum avem nevoie de o actiune pentru gestionarea crearii imaginii dupa ce trimitem formularul pe pagina de pornire. Sa generam una cu:

Norocos gen.action.browser Imagini :: Creare

Pentru mai multe informatii despre cum actioneaza actiunile, puteti consulta ghidurile lui Lucky.

Aceasta actiune va obtine fisierul din paramele care vor fi sub forma {“image”: {“image”: “fisierul este aici”}}. Daca nu este nul, vom trece fisierul si actual_ip la ImageForm, care va valida si va salva noua noastra imagine.

Pentru a verifica daca fisierul nostru exista, ne vom asigura ca nu este nul si ca numele fisierului exista.

# src / actions / images / create.cr clasa Imagini :: Creare <BrowserAction include Auth :: SkipRequireSignInexposepose current_user route do # lucky extinde asta la: post “/ images” file = params.nested_file? (: image) [“image „]? if is_invalid (file) flash.danger = “Va rugam sa selectati un fisier de incarcat” redirectionati catre: Home :: Index else ImageForm.create (fisier: file.not_nil !, ip: current_ip) do | formular, imagine | if image flash.success = “Imaginea incarcata cu succes din # {current_ip}!” redirectioneaza catre: Home :: Index else flash.danger = “Incarcarea imaginii a esuat” face Home :: IndexPage, form: form end end end end def def privat_final (file) file.nil? || file.metadata.filename.nil? || file.not_nil! .metadata.filename.not_nil! .empty? sfarsit final

Si voila! Aplicatia noastra poate face fata incarcarilor de imagini.

Daca rulam specificatiile cu spec. Norocos / flow / images_spec.cr, vom vedea ca primul nostru specimen care verifica imaginile valide va trece, dar din moment ce nu am implementat validari de imagine, restul va esua.

validari

Pentru a verifica dimensiunea, tipul si dimensiunile fisierelor imaginilor, vom folosi o mica bijuterie dintr-un fragment numit crymagick. Este necesar ca ImageMagick sa fie instalat, ceea ce din fericire pentru noi este prezent in mod implicit pe Heroku. Daca nu este instalat pe masina dvs. locala, il puteti obtine de pe site-ul oficial aici.

Va permite sa instalati fragmentul adaugandu-l in partea de jos a dependentelor noastre in shard.yml si ruland fragmente.

# shard.yml … dependente: … crymagick: github: imdrasil / crymagick

Acum il putem folosi in ImageForm pentru a ne valida imaginile. Adaugam trei metode validate_is_correct_size, validate_is_correct_dimensions si validate_is_correct_type care vor folosi CryMagick :: Image pentru a verifica tipul, dimensiunea si dimensiunile fisierului. Daca nu exista erori, trecem la salvarea fisierului si setarea coloanelor imaginii.

necesita „uuid” necesita clasa „crymagick” ImageForm <Image :: BaseForm … getter crymagick_image: CryMagick :: Image? def prepare validate_is_correct_size validate_is_correct_dimensions validate_is_correct_type if errors.empty? # save if validations pass save_image views.value = 1 filename.value = new_filename owner_ip.value = ip end end private def validate_is_correct_type ext = crymagick_image.type, cu exceptia cazului in Image :: VALID_FORMATS.include? “# {ext}”. downcase image.add_error “tip ar trebui sa fie jpg, jpeg, gif sau png, dar a fost # {ext}” end end privat def validate_is_correct_size size = crymagick_image.size # returneaza marimea in octeti daca marimea> 250_000 # 250kb limit image.add_error “dimensiunea ar trebui sa fie mai mica de 250kb, dar a fost # {size / 1000} kb”

Acum, daca rulam specificatiile, vom vedea ca toate trec! Ura!

Tot ce a ramas acum este sa adaugati suport pentru stergerea si vizualizarea imaginilor noastre.

Afisarea si stergerea imaginilor

Ceea ce ne dorim este sa ne afisam imaginile pe pagina de start ca o galerie. Fiecare imagine ar trebui sa aiba un buton de sters si ar trebui sa afiseze adresa URL.

Sa incepem cu o specificatie care viziteaza pagina de pornire si verifica imaginile de pe ecran si o alta care face clic pe butonul de stergere si verifica daca imaginea este stersa.

# specs / flow / images_spec.cr necesita “../spec_helper” descrie “Fluxul de imagini” face … descrie “afiseaza” faceti-le “imagini proprii” do flow = ImagesFlow.new detinute = ImageBox.new.owner_ip (“local “) .create not_owned = ImageBox.new.owner_ip (” nu detine “). Creeaza flow.homepage_should_display_image (proprjeta.id) flow.homepage_should_not_display_image (not_owned.id) final final descrie” stergerea “faceti-o” este permis pentru proprietar ” do flow = ImagesFlow.new flow.upload_image (valid_image_path) image = ImageQuery.new.first flow.delete_image_from_homepage (image.id) flow.image_should_not_exist (image.id) end it “nu este permis pentru alte adrese ip” do flow = ImagesFlow .new not_owned = ImageBox.new.owner_ip (“nu-local”). Creeaza fluxul flow.delete_image_from_action (not_owned.id).image_should_exist (not_owned.id) final end end end …

Si permiteti sa adaugati fluxurile care vor vizita pagina principala, sa verificati imagini, sa verificati imaginile din baza de date si sa stergeti imaginile apasand butoane sau vizitand actiunile direct.

clasa ImagesFlow <BaseFlow … def homepage_should_display_image (id) vizitati Acasa :: Imagine imagine (id). :: Faceti clic pe index “@ sterge-imagine – # {id}” end def delete_image_from_action (id) vizitati Images :: Delete.with (id: id) end def image_should_exist (id) ImageQuery.find (id) .should_nil be_nil end def image_should_not_exist (id) ImageQuery.new.id (id) .first? .should be_nil end … private def image (id) el (“@ image – # {id}”) final

Testele noastre vor esua acum, astfel incat sa adaugam asistenta pentru afisarea imaginilor prin actualizarea Home :: IndexPage. Vom solicita ca pagina sa fie redata cu un suport de imagini care este un ImageQuery. Apoi vom folosi imaginile intr-o noua metoda de galerie care reda fiecare imagine, inclusiv link-uri pentru a o sterge si o adresa URL pentru a o afisa.

clasa Acasa :: IndexPage <GuestLayout are nevoie de formular: ImageForm are nevoie de imagini: ImageQuery # ADAUGA ACEST! def continut div class: “homepage-container” do render_form (@form) gallery # add gallery erhere end end private def gallery # define it here h2 “Image Gallery” ul class: “image-gallery” do @ images.map do | imagine | li class: “image”, flow_id: “image – # {image.

porno negri http://klickverdienst.net/out.php?url=https://adult69.ro/
actrite romance porno http://sock.supadsl.net/__media__/js/netsoltrademark.php?d=adult69.ro/
porno românia http://www.backupmycomputer.com/__media__/js/netsoltrademark.php?d=adult69.ro/
porno tata si fica http://aquanutsolutions.com/__media__/js/netsoltrademark.php?d=adult69.ro/filme-porno/amatori
porno cu limbi http://somil.com/__media__/js/netsoltrademark.php?d=adult69.ro/filme-porno/anal
filme porno cu vecini http://rarebooks.com/__media__/js/netsoltrademark.php?d=adult69.ro/filme-porno/asiatice
dvd porno http://www.nordic-land.com/go.php?url=https://adult69.ro/filme-porno/beeg
porno rusia http://www.ex-tour.com/__media__/js/netsoltrademark.php?d=adult69.ro/filme-porno/blonde
porno games http://www.ceonline.org/__media__/js/netsoltrademark.php?d=adult69.ro/filme-porno/brazzers
filime porno http://www.mastertgp.net/tgp/click.php?id=62381&u=https://adult69.ro/filme-porno/brunete
milf anal porno http://bassworkshop.com/__media__/js/netsoltrademark.php?d=adult69.ro/filme-porno/chaturbate
porno hd category http://eu-nn.net/__media__/js/netsoltrademark.php?d=adult69.ro/fiica-neastamparata-iubeste-sa-si-futa-tatal
filme porno copii http://michaelbigalke.de/index.php?show=link&link=https://adult69.ro/o-fac-pe-curva-asta-fericita-anal
porno fri http://worldwidewinesltd.com/__media__/js/netsoltrademark.php?d=adult69.ro/mama-ma-invata-cum-sa-fut
porno cur mare http://blenderartists.com/__media__/js/netsoltrademark.php?d=adult69.ro/tanara-dansatoare-violata-in-grup
porno xtril http://www.azimuth.com/__media__/js/netsoltrademark.php?d=adult69.ro/nu-i-spune-tatei-ca-ne-futem
porno hd 1080 http://54-news.ru/go.php?url=https://adult69.ro/atacata-si-violata-dur-in-garaj
porno gangbang http://proflagfootball.biz/__media__/js/netsoltrademark.php?d=adult69.ro/mama-si-fiica-ii-fac-fericit-pe-tata
porno romance http://the.immensechandeliers.com/__media__/js/netsoltrademark.php?d=adult69.ro/lezbiene-virgine-violate-la-un-casting
maduras porno http://www.clipz.com/__media__/js/netsoltrademark.php?d=adult69.ro/scolarite-futabile-in-piscina-scolii

id}” do div class: “picture”, style: “background-image: url (# {image.path});” face div “Vizualizari: # {image.views}”, clasa: “views” link final la: Images :: Delete.with (image.id), flow_id: “delete-image – # {image.id}” do img src: asset (“images / x.png”) end div image.url, class: “image-url”, flow_id: “image-url – # {image.id}” end end end end end end … end

Am adaugat si stiluri la src / css / app.scss pe care nu le voi include in acest articol.

Pentru ca acest lucru sa functioneze, trebuie sa actualizam actiunile noastre care redau Home :: IndexPage, astfel incat acestea sa fie transmise in imagini.

# src / actions / home / index.cr clasa Acasa :: Index <BrowserAction … get “/” do daca “current_user”? redirect Me :: Show other images = ImageQuery.new.owner_ip (current_ip) render Home :: IndexPage, forma: ImageForm.new, images: images # pass it here end end end end end

Si in imaginile noastre: Creeaza actiune.

# src / actions / images / create.cr clasa Imagini :: Creati <BrowserAction … ruta face daca nu este_invalid (fisier) … altceva ImageForm.create (fisier: file.not_nil !, ip: current_ip) do | formular, imagine | if image … else … images = ImageQuery.new.owner_ip (current_ip) render Acasa :: IndexPage, forma: formular, imagini: imaginile # trece-l aici in final end end end end … end

Toate setate! Acasa :: IndexPage nu se va plange ca nu au trecut imagini. Dar va plange pentru un link catre Imagini :: Stergere care nu a fost implementat. Deci, hai sa facem asta acum.

Norocos gen.action.browser Imagini :: Sterge

Actiunea Imagini :: Sterge ar trebui sa verifice daca current_ip se potriveste cu proprietarul_ip al imaginii si daca este apelul sterge !.

Clasa # src / actions / images / delete.cr Imagini :: Sterge <BrowserAction include Auth :: SkipRequireSignInexposepose current_user route do # expands to: delete “/ images /: id” image = ImageQuery.find (id) if image.owner_ip == current_ip image.delete! flash.success = “Imaginea a fost eliminata cu succes!” redirectioneaza catre: Home :: Index else flash.danger = “Nu esti autorizat sa stergi aceasta imagine” redirectioneaza catre: Home :: Index end end end end

Acum executati testele si … Boom! Verde.

Afisati o singura imagine

Ultimul lucru pe care il vom implementa este o pagina de spectacol pentru fiecare imagine care actualizeaza numarul de vizualizari. Sa generam actiunea, pagina si un formular de actualizare a imaginilor pentru noi.

images gen.action.browser norocos :: Afisati imagini nor.page norocos: ShowPage touch src / formulare / imagine_views_form.cr # fara generator pentru formulare atm

Formularul va fi simplu si va fi utilizat doar pentru cresterea valorii. Poate fi folosit astfel: ImageViewsForm.update! (Imagine).

# src / formes / image_views_form.cr clasa ImageViewsForm <Image :: Vizualizari filfabile BaseForm fillable nume de fisier fillable owner_ip def prepare views.value = views.value.not_nil! + 1 capat final

Pentru actiunea noastra vom folosi un traseu personalizat, astfel incat parametrul nostru de ruta sa fie disponibil ca nume de fisier in loc de ID. Apoi verificam ca exista si crescem vizualizarile si redam pagina, altfel redirectionam la actiunea Home :: Index.

# src/actions/images/show.cr class Images::Show < BrowserAction include Auth::SkipRequireSignIn unexpose current_user get “/images/:filename” do image = ImageQuery.new.filename(filename).first? if image.nil? flash.danger = “Image with filename: #{filename} not found” redirect to: Home::Index else ImageViewsForm.update!(image) render Images::ShowPage, image: image end end end

The show page will be very simple. We display the filename, the views and the image using minimal style to keep everything centered while allowing the image to stretch to its full size.

# src/pages/images/show_page.cr class Images::ShowPage < GuestLayout needs image : Image def content div style: “text-align: center;” do h1 @image.filename h2 “Views: #{@image.views}” img src: @image.path, style: “max-width: 100%; height: auto;” end end end

To finish off we’ll make the image displayed on the home page link to the Images::ShowPage.

class Home::IndexPage < GuestLayout … ul class: “image-gallery” do @images.map do |image| li class: “image”, flow_id: “image-#{image.id}” do link to: Images::Show.with(image.filename), # Changed this to link: .. class: “picture”, style: “background-image: url(#{image.path});” do div “Views: #{image.views}”, class: “views” end … end end end … end

And we’re done! The tests should all be green and the app working as expected.

Join Us

I hope you enjoyed this tutorial and found it useful. Join us on the Lucky gitter channel to stay up to date on the framework or checkout the docs for more information on how to bring your app idea to life with Lucky.