diff --git a/Gemfile b/Gemfile index bdfd0b3410..c9d34cd0bb 100644 --- a/Gemfile +++ b/Gemfile @@ -99,6 +99,7 @@ group :test, :development do gem 'rspec-retry' gem 'json_spec' gem 'unicorn-rails' + gem 'atomic' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 2157de2b77..54f7daf530 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -155,6 +155,7 @@ GEM angularjs-rails (1.2.13) ansi (1.4.2) arel (3.0.3) + atomic (1.1.99) awesome_nested_set (2.1.5) activerecord (>= 3.0.0) awesome_print (1.0.2) @@ -547,6 +548,7 @@ DEPENDENCIES angular-rails-templates (~> 0.2.0) angularjs-file-upload-rails (~> 1.1.0) angularjs-rails (= 1.2.13) + atomic awesome_print aws-sdk blockenspiel diff --git a/app/models/enterprise_group.rb b/app/models/enterprise_group.rb index 986dc07d22..63eb63f0b3 100644 --- a/app/models/enterprise_group.rb +++ b/app/models/enterprise_group.rb @@ -1,6 +1,8 @@ require 'open_food_network/locking' +require 'open_food_network/permalink_generator' class EnterpriseGroup < ActiveRecord::Base + include PermalinkGenerator acts_as_list has_and_belongs_to_many :enterprises @@ -81,25 +83,10 @@ class EnterpriseGroup < ActiveRecord::Base private - def self.find_available_value(existing, requested) - return requested unless existing.include?(requested) - used_indices = existing.map do |p| - p.slice!(/^#{requested}/) - p.match(/^\d+$/).to_s.to_i - end - options = (1..used_indices.length + 1).to_a - used_indices - requested + options.first.to_s - end - - def find_available_permalink(requested) - existing = self.class.where(id: !id).where("permalink LIKE ?", "#{requested}%").pluck(:permalink) - self.class.find_available_value(existing, requested) - end - def sanitize_permalink if permalink.blank? || permalink_changed? requested = permalink.presence || permalink_was.presence || name.presence || 'group' - self.permalink = find_available_permalink(requested.parameterize) + self.permalink = create_unique_permalink(requested.parameterize) end end end diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 2cf13baf84..53f960ba63 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -1,4 +1,7 @@ +require 'open_food_network/permalink_generator' + Spree::Product.class_eval do + include PermalinkGenerator # We have an after_destroy callback on Spree::ProductOptionType. However, if we # don't specify dependent => destroy on this association, it is not called. See: # https://github.com/rails/rails/issues/7618 @@ -19,6 +22,8 @@ Spree::Product.class_eval do attr_accessible :variant_unit, :variant_unit_scale, :variant_unit_name, :unit_value attr_accessible :inherits_properties, :sku + before_validation :sanitize_permalink + # validates_presence_of :variants, unless: :new_record?, message: "Product must have at least one variant" validates_presence_of :supplier validates :primary_taxon, presence: { message: "^Product Category can't be blank" } @@ -239,4 +244,11 @@ Spree::Product.class_eval do raise end end + + def sanitize_permalink + if permalink.blank? || permalink_changed? + requested = permalink.presence || permalink_was.presence || name.presence || 'product' + self.permalink = create_unique_permalink(requested.parameterize) + end + end end diff --git a/config/environments/test.rb b/config/environments/test.rb index 46d5dcf7c6..67131b6489 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -41,6 +41,11 @@ Openfoodnetwork::Application.configure do # Print deprecation notices to the stderr config.active_support.deprecation = :stderr config.action_mailer.default_url_options = { :host => "test.host" } + + # To block requests before running the database cleaner + require 'open_food_network/rack_request_blocker' + # Make sure the middleware is inserted first in middleware chain + config.middleware.insert_before('ActionDispatch::Static', 'RackRequestBlocker') end # Allows us to use _url helpers in Rspec diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 851d2b12e4..9141265f86 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -1,7 +1,7 @@ # Localization file for French. # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. -en: +fr: devise: failure: invalid: | diff --git a/config/locales/no.yml b/config/locales/no.yml index a44f0317a9..56af830111 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -1,31 +1,31 @@ # Sample localization file for English. Add more files in this directory for other locales. # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. -en: +no: devise: failure: invalid: | - Invalid email or password. - Were you a guest last time? Perhaps you need to create an account or reset your password. + Ugyldig epost eller passord. + Var du gjest forrige gang? Kanskje du må opprette en konto eller nullstille passordet. enterprise_confirmations: enterprise: - confirmed: Thankyou, your email address has been confirmed. - not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step? - confirmation_sent: "Confirmation email sent!" - confirmation_not_sent: "Could not send a confirmation email." + confirmed: Takk, din epostadressen er bekreftet. + not_confirmed: Din epostadresse kan ikke bekreftes. Kanskje du allerede har fullført dette steget? + confirmation_sent: "Bekreftelse på epost er sendt!" + confirmation_not_sent: "Kunne ikke sende bekreftelse på epost." home: "OFN" title: Open Food Network - welcome_to: 'Welcome to ' - search_by_name: Search by name or suburb... - producers: Aussie Producers - producers_join: Australian producers are now welcome to join the Open Food Network. - charges_sales_tax: Charges GST? + welcome_to: 'Velkommen til ' + search_by_name: Søk på navn eller sted... + producers: Norske Produsenter + producers_join: Norske produsenter er nå velkommen til å bli med i Open Food Network. + charges_sales_tax: MVA-pliktig? logo: "Logo (640x130)" - logo_mobile: "Mobile logo (75x26)" - logo_mobile_svg: "Mobile logo (SVG)" - home_hero: "Hero image" - home_show_stats: "Show statistics" + logo_mobile: "Mobil logo (75x26)" + logo_mobile_svg: "Mobil logo (SVG)" + home_hero: "Heltebilde" + home_show_stats: "Vis statistikk" footer_logo: "Logo (220x76)" footer_facebook_url: "Facebook URL" footer_twitter_url: "Twitter URL" @@ -33,423 +33,418 @@ en: footer_linkedin_url: "LinkedIn URL" footer_googleplus_url: "Google Plus URL" footer_pinterest_url: "Pinterest URL" - footer_email: "Email" - footer_links_md: "Links" - footer_about_url: "About URL" - footer_tos_url: "Terms of Service URL" + footer_email: "Epost" + footer_links_md: "Linker" + footer_about_url: "Om URL" + footer_tos_url: "Vilkår URL" - name: Name - first_name: First Name - last_name: Last Name - email: Email - phone: Phone - next: Next - address: Address - address2: Address (contd.) - city: City - state: State - postcode: Postcode - country: Country - unauthorized: Unauthorized - terms_of_service: "Terms of service" - on_demand: On demand - none: None + name: Navn + first_name: Fornavn + last_name: Etternavn + email: Epost + phone: Telefon + next: Neste + address: Adresse + address2: Adresse (forts.) + city: Kommune + state: Fylke + postcode: Postnummer + country: Land + unauthorized: Uautorisert + terms_of_service: "Vilkår" + on_demand: Ved forespørsel + none: Ingen - alert_selling_on_ofn: "Interested in selling food on the Open Food Network?" - alert_start_here: "Start here" - label_shops: "Shops" - label_map: "Map" - label_producers: "Producers" - label_groups: "Groups" - label_about: "About" - label_shopping: "Shopping" - label_login: "Login" - label_logout: "Logout" - label_signup: "Sign up" - label_administration: "Administration" + alert_selling_on_ofn: "Interessert i å selge mat gjennom Open Food Network?" + alert_start_here: "Start her" + label_shops: "Butikker" + label_map: "Kart" + label_producers: "Produsenter" + label_groups: "Grupper" + label_about: "Om" + label_shopping: "Handle" + label_login: "Logg inn" + label_logout: "Logg ut" + label_signup: "Bli medlem" + label_administration: "Administrasjon" label_admin: "Admin" - label_account: "Account" - label_more: "More" - label_less: "Show less" + label_account: "Konto" + label_more: "Mer" + label_less: "Vis mindre" - items: "items" - cart_headline: "Your shopping cart" - total: "Total" - checkout: "Checkout now" - cart_updating: "Updating cart..." - cart_empty: "Cart empty" - cart_edit: "Edit your cart" + items: "varer" + cart_headline: "Din handlekurv" + total: "Sum" + checkout: "Gå til kassen" + cart_updating: "Oppdaterer handlekurv..." + cart_empty: "Handlekurven er tom" + cart_edit: "Rediger handlekurv" - card_number: Card Number - card_securitycode: "Security Code" - card_expiry_date: Expiry Date + card_number: Kortnummer + card_securitycode: "Sikkerhetskode" + card_expiry_date: Utløpsdato - ofn_cart_headline: "Current cart for:" - ofn_cart_distributor: "Distributor:" - ofn_cart_oc: "Order cycle:" - ofn_cart_from: "From:" - ofn_cart_to: "To:" - ofn_cart_product: "Product:" - ofn_cart_quantitiy: "Quantity:" - ofn_cart_send: "Buy me" + ofn_cart_headline: "Gjeldende handlekurv for:" + ofn_cart_distributor: "Distributør:" + ofn_cart_oc: "Bestillingsrunde:" + ofn_cart_from: "Fra:" + ofn_cart_to: "Til:" + ofn_cart_product: "Produkt:" + ofn_cart_quantitiy: "Antall:" + ofn_cart_send: "Kjøp" - ie_warning_headline: "Your browser is out of date :-(" - ie_warning_text: "For the best Open Food Network experience, we strongly recommend upgrading your browser:" - ie_warning_chrome: Download Chrome - ie_warning_firefox: Download Firefox - ie_warning_ie: Upgrade Internet Explorer - ie_warning_other: "Can't upgrade your browser? Try Open Food Network on your smartphone :-)" + ie_warning_headline: "Din nettleser er for gammel :-(" + ie_warning_text: "For den beste opplevelsen med Open Food Network anbefaler vi på det sterkeste å oppgradere nettleseren din:" + ie_warning_chrome: Last ned Chrome + ie_warning_firefox: Last ned Firefox + ie_warning_ie: Oppgrader Internet Explorer + ie_warning_other: "Kan ikke oppgradere nettleseren din? Prøv Open Food Network på smart-telefonen din :-)" - footer_global_headline: "OFN Global" - footer_global_home: "Home" - footer_global_news: "News" - footer_global_about: "About" - footer_global_contact: "Contact" + footer_global_headline: "OFN Globalt" + footer_global_home: "Hjem" + footer_global_news: "Nyheter" + footer_global_about: "Om" + footer_global_contact: "Kontakt" - footer_sites_headline: "OFN Sites" - footer_sites_developer: "Developer" - footer_sites_community: "Community" - footer_sites_userguide: "User Guide" + footer_sites_headline: "OFN nettsteder" + footer_sites_developer: "Utvikler" + footer_sites_community: "Forum" + footer_sites_userguide: "Brukerhåndbok" - footer_secure: "Secure and trusted." - footer_secure_text: "Open Food Network uses SSL encryption (2048 bit RSA) everywhere to keep your shopping and payment information private. Our servers do not store your credit card details and payments are processed by PCI-compliant services." + footer_secure: "Sikker og klarert." + footer_secure_text: "Open Food Network bruker SSL-kryptering (2048 bit RSA) overalt for å holde handlingen og betalingen din privat. Våre servere lagrer ikke kortopplysninger og betalinger behandles av PCI-kompatible tjenester." - footer_contact_headline: "Keep in touch" - footer_contact_email: "Email us" + footer_contact_headline: "Hold kontakten" + footer_contact_email: "Send oss en epost" - footer_nav_headline: "Navigate" - footer_join_headline: "Join us" - footer_join_producers: "Producers sign-up" - footer_join_hubs: "Hubs sign-up" - footer_join_groups: "Groups sign-up" - footer_join_partners: "Food systems partners" + footer_nav_headline: "Naviger" + footer_join_headline: "Bli med" + footer_join_producers: "Bli med som Produsent" + footer_join_hubs: "Bli med som Hub" + footer_join_groups: "Bli med som Gruppe" + footer_join_partners: "Samarbeidspartnere" - footer_legal_call: "Read our" - footer_legal_tos: "Terms & conditions" - footer_legal_visit: "Find us on" - footer_legal_text: "Open Food Network is a free and open source software platform. Our content is licensed with" - footer_legal_text2: "and our code with" + footer_legal_call: "Les våre" + footer_legal_tos: "Vilkår & betingelser" + footer_legal_visit: "Finn oss på" + footer_legal_text: "Open Food Network er en programvareplattform med fri og åpen kilde. Innholdet er lisensiert under" + footer_legal_text2: "og vår kode under" footer_legal_license_content: "CC BY-SA 3.0" footer_legal_license_code: "AGPL 3" - home_shop: Shop Now + home_shop: Handle nå brandstory_headline: "Food, unincorporated." - brandstory_intro: "Sometimes the best way to fix the system is to start a new one…" - brandstory_part1: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can seriously change the world." - brandstory_part2: "Then we need a way to make it real. A way to empower everyone who grows, sells and buys food. A way to tell all the stories, to handle all the logistics. A way to turn transaction into transformation every day." - brandstory_part3: "So we build an online marketplace that levels the playing field. It’s transparent, so it creates real relationships. It’s open source, so it’s owned by everyone. It scales to regions and nations, so people start versions across the world." - brandstory_part4: "It works everywhere. It changes everything." - brandstory_part5_strong: "We call it Open Food Network." - brandstory_part6: "We all love food. Now we can love our food system too." + brandstory_intro: "Noen ganger er det best å fikse systemet ved å starte et nytt..." + brandstory_part1: "Vi begynner fra grunnen. Med bønder og dyrkere klare til å fortelle sine historier, stolt og virkelig. Med distributører klare til å koble mennesker med produkter på en rettferdig og ærlig måte. Med kjøpere som tror på at ukentlige innkjøpsrutiner kan bidra til å forandre verden." + brandstory_part2: "Da trenger vi en ordentlig måte å gjøre det på. En måte som styrker alle som dyrker, selger og kjøper mat. En måte å fortelle alle historiene på, håndtere all logistikk. En måte å forvandle transaksjon til transformasjon hver dag." + brandstory_part3: "Derfor bygger vi en online markedsplass som endrer spillereglene. Det er transparent, slik at det skaper ekte relasjoner. Det er open-source, slik at det er eid av alle. Metoden er skalérbar for regioner og nasjoner, så folk kan starte versjoner over hele verden." + brandstory_part4: "Det fungerer overalt. Det forandrer alt." + brandstory_part5_strong: "Vi kaller det Open Food Network." + brandstory_part6: "Alle er vi glad i mat. Nå kan vi elske vårt matsystem også." - system_headline: "Here's how it works." - system_step1: "1. Search" - system_step1_text: "Search our diverse, independent shops for seasonal local food. Search by neighbourhood and food category, or whether you prefer delivery or pickup." - system_step2: "2. Shop" - system_step2_text: "Transform your transactions with affordable local food from diverse producers and hubs. Know the stories behind your food and the people who make it!" - system_step3: "3. Pick-up / Delivery" - system_step3_text: "Hang on for your delivery, or visit your producer or hub for a more personal connection with your food. Food shopping as diverse as nature intended it." + system_headline: "Slik fungerer det." + system_step1: "1. Søk" + system_step1_text: "Søk blant våre mangfoldige, uavhengige butikker for lokal mat i sesong. Søk i nabolag og matkategori, eller om du foretrekker levering eller å hente selv." + system_step2: "2. Handle" + system_step2_text: "Omform dine transakjsoner med rimelig lokal mat fra mangfoldige produsenter og hubs. Oppdag historiene bak maten din og de som lager den!" + system_step3: "3. Hent / Få det levert" + system_step3_text: "Vent på din leveranse, eller besøk produsenten eller hub'en for en mer personlig kobling til maten din. Mathandling så mangfoldig som meningen var fra naturens side." - cta_headline: "Shopping that makes the world a better place." - cta_label: "I'm Ready" + cta_headline: "Handling som gjør verden til et bedre sted." + cta_label: "Jeg er klar" - stats_headline: "We're creating a new food system." - stats_producers: "food producers" - stats_shops: "food shops" - stats_shoppers: "food shoppers" - stats_orders: "food orders" + stats_headline: "Vi skaper et nytt matsystem." + stats_producers: "matprodusenter" + stats_shops: "matbutikker" + stats_shoppers: "matkunder" + stats_orders: "matbestillinger" - checkout_title: Checkout - checkout_now: Checkout now - checkout_order_ready: Order ready for - checkout_hide: Hide - checkout_expand: Expand - checkout_headline: "Ok, ready to checkout?" - checkout_as_guest: "Checkout as guest" - checkout_details: "Your details" - checkout_billing: "Billing info" - checkout_shipping: Shipping info - checkout_method_free: Free - checkout_address_same: Shipping address same as billing address? - checkout_ready_for: "Ready for:" - checkout_instructions: "Any comments or special instructions?" - checkout_payment: Payment - checkout_send: Place order now - checkout_your_order: Your order - checkout_cart_total: Cart total - checkout_shipping_price: Shipping - checkout_total_price: Total - checkout_back_to_cart: "Back to Cart" + checkout_title: Kasse + checkout_now: Gå til kassen + checkout_order_ready: Bestilling klar for + checkout_hide: Skjul + checkout_expand: Utvid + checkout_headline: "Ok, klar for betaling?" + checkout_as_guest: "Betal som gjest" + checkout_details: "Dine detaljer" + checkout_billing: "Betalingsinformasjon" + checkout_shipping: Leveringsinformasjon + checkout_method_free: Gratis + checkout_address_same: Leveringsadresse samme som fakturaadresse? + checkout_ready_for: "Klar for:" + checkout_instructions: "Kommentarer eller spesielle instruksjoner?" + checkout_payment: Betaling + checkout_send: Send bestilling + checkout_your_order: Din bestilling + checkout_cart_total: Sum handlekurv + checkout_shipping_price: Levering + checkout_total_price: Sum + checkout_back_to_cart: "Tilbake til Handlekurv" - order_paid: PAID - order_not_paid: NOT PAID - order_total: Total order - order_payment: "Paying via:" - order_billing_address: Billing address - order_delivery_on: Delivery on - order_delivery_address: Delivery address - order_special_instructions: "Your notes:" - order_pickup_instructions: Collection Instructions - order_produce: Produce - order_total_price: Total - order_includes_tax: (includes tax) - order_payment_paypal_successful: Your payment via PayPal has been processed successfully. - order_hub_info: Hub Info + order_paid: BETALT + order_not_paid: IKKE BETALT + order_total: Sum bestilling + order_payment: "Betaler via:" + order_billing_address: Fakturaadresse + order_delivery_on: Levering på + order_delivery_address: Leveringsadresse + order_special_instructions: "Dine kommentarer:" + order_pickup_instructions: Henteinstruksjoner + order_produce: Varer + order_total_price: Sum + order_includes_tax: (inkludert MVA) + order_payment_paypal_successful: Din betaling via PayPal har blitt godkjent. + order_hub_info: Hub info - products: "Products" - products_in: "in %{oc}" - products_at: "at %{distributor}" - products_elsewhere: "Products found elsewhere" + products: "Produkter" + products_in: "i %{oc}" + products_at: "hos %{distributor}" + products_elsewhere: "Produkter funnet andre steder" - email_welcome: "Welcome" - email_confirmed: "Thank you for confirming your email address." - email_registered: "is now part of" - email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here: -%{link}" - email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration." - email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next. -%{link}" - email_help: "If you have any difficulties, check out our FAQs, browse the forum or post a 'Support' topic and someone will help you out!" - email_confirmation_greeting: "Hi, %{contact}!" - email_confirmation_profile_created: "A profile for %{name} has been successfully created! -To activate your Profile we need to confirm this email address." - email_confirmation_click_link: "Please click the link below to confirm your email and to continue setting up your profile." - email_confirmation_link_label: "Confirm this email address »" - email_confirmation_help: "After confirming your email you can access your administration account for this enterprise. -See the %{link} to find out more about %{sitename}'s features and to start using your profile or online store." - email_confirmation_userguide: "User Guide" - email_social: "Connect with Us:" - email_contact: "Email us:" - email_signoff: "Cheers," + email_welcome: "Velkommen" + email_confirmed: "Takk for at du bekrefter din e-postadresse" + email_registered: "er nå en del av" + email_userguide_html: "Brukerhåndboken med detaljert støtte om hvordan man setter opp som Produsent eller Hub finnes her: %{link}" + email_admin_html: "Du kan administrere din konto ved å logge inn på %{link} eller ved klikke på tannhjulet øverst til høyre på hjemmesiden og velge Aministrasjon." + email_community_html: "Vi har også et online forum for diskusjon relatert til OFN programvaren og de forskjellige utfordringene med å drive matfirma. Vi oppfordrer deg til å bli med. Vi utvikler oss hele tiden og dine innspill til dette forumet vil forme det som skjer videre. %{link}" + email_help: "Hvis du har problemer, sjekk vår FAQ, utforsk forumet eller skriv et 'Support'-emne og noen vil hjelpe deg!" + email_confirmation_greeting: "Hei, %{contact}!" + email_confirmation_profile_created: "En profil for %{name} har blitt opprettet! For å aktivere din Profil må du bekrefte denne epostadressen." + email_confirmation_click_link: "Trykk på linken under for å bekrefte din e-post og for å fortsette oppsettet av din profil." + email_confirmation_link_label: "Bekreft denne epostadressen »" + email_confirmation_help: "Etter du har bekreftet e-postadressen får du tilgang til din administrasjonskonto for dette selskapet. Se linken %{link} for å finne ut mer om %{sitename}s funksjoner og for å begynne å bruke din profil eller nettbutikk." + email_confirmation_userguide: "Brukerhåndbok" + email_social: "Her finner du oss:" + email_contact: "Send oss en epost:" + email_signoff: "Mvh," email_signature: "%{sitename} Team" - email_confirm_customer_greeting: "Hi %{name}," - email_confirm_customer_intro_html: "Thanks for shopping at %{distributor}!" - email_confirm_customer_number_html: "Order confirmation #%{number}" - email_confirm_customer_details_html: "Here are your order details from %{distributor}:" - email_confirm_customer_signoff: "Kind regards," - email_confirm_shop_greeting: "Hi %{name}," - email_confirm_shop_order_html: "Well done! You have a new order for %{distributor}!" - email_confirm_shop_number_html: "Order confirmation #%{number}" - email_order_summary_item: "Item" - email_order_summary_quantity: "Qty" - email_order_summary_price: "Price" - email_order_summary_subtotal: "Subtotal:" - email_order_summary_total: "Total:" - email_payment_paid: PAID - email_payment_not_paid: NOT PAID - email_payment_summary: Payment summary - email_order_summary_method: "Paying via:" - email_shipping_delivery_details: Delivery details - email_shipping_delivery_time: "Delivery on:" - email_shipping_delivery_address: "Delivery address:" - email_shipping_collection_details: Collection details - email_shipping_collection_time: "Ready for collection:" - email_shipping_collection_instructions: "Collection instructions:" - email_special_instructions: "Your notes:" + email_confirm_customer_greeting: "Hei %{name}," + email_confirm_customer_intro_html: "Takk for at du handler hos %{distributor}!" + email_confirm_customer_number_html: "Ordrebekreftelse #%{number}" + email_confirm_customer_details_html: "Her er dine ordredetaljer fra %{distributor}:" + email_confirm_customer_signoff: "Vennlig hilsen," + email_confirm_shop_greeting: "Hei %{name}," + email_confirm_shop_order_html: "Bra jobbet! Du har en ny ordre fra %{distributor}!" + email_confirm_shop_number_html: "Ordrebekreftelse #%{number}" + email_confirm_shop_order_detail_html: "%{firstname} %{lastname} gjennomførte følgende bestilling i din butikk:" + email_order_summary_item: "Vare" + email_order_summary_quantity: "Stk" + email_order_summary_price: "Pris" + email_order_summary_subtotal: "Delsum:" + email_order_summary_total: "Sum:" + email_payment_paid: BETALT + email_payment_not_paid: IKKE BETALT + email_payment_summary: Betalingssammendrag + email_order_summary_method: "Betaler via:" + email_shipping_delivery_details: Leveringsdetaljer + email_shipping_delivery_time: "Levering på:" + email_shipping_delivery_address: "Leveringsadresse:" + email_shipping_collection_details: Hentedetaljer + email_shipping_collection_time: "Klar for henting:" + email_shipping_collection_instructions: "Henteinstruksjoner:" + email_special_instructions: "Dine kommentarer:" - email_signup_greeting: Hello! - email_signup_welcome: "Welcome to %{sitename}!" - email_signup_login: Your login - email_signup_email: Your login email is - email_signup_shop_html: "You can start shopping online now at %{link}." - email_signup_text: "Thanks for joining the network. - If you are a customer, we look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food! - If you are a producer or food enterprise, we are excited to have you as a part of the network." - email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at" + email_signup_greeting: Hei! + email_signup_welcome: "Velkommen til %{sitename}!" + email_signup_login: Din login + email_signup_email: Din login e-post er + email_signup_shop_html: "Du kan begynne å handle på nett nå på %{link}." + email_signup_text: "Takk for at du ble med i nettverket. Hvis du er kunde ser vi frem til å vise deg mange fantastiske bønder, flotte mat-hubs og deilig mat! Hvis du er produsent eller selskap er vi glade for å ha deg som en del av nettverket." + email_signup_help_html: "Vi tar i mot alle dine spørsmål og tilbakemeldinger; du kan bruke Send tilbakemelding-knappen på nettsiden eller sende oss en epost på" - shopping_oc_closed: Orders are closed - shopping_oc_closed_description: "Please wait until the next cycle opens (or contact us directly to see if we can accept any late orders)" - shopping_oc_last_closed: "The last cycle closed %{distance_of_time} ago" - shopping_oc_next_open: "The next cycle opens in %{distance_of_time}" - shopping_tabs_about: "About %{distributor}" - shopping_tabs_contact: "Contact" - shopping_contact_address: "Address" - shopping_contact_web: "Contact" - shopping_contact_social: "Follow" - shopping_groups_part_of: "is part of:" - shopping_producers_of_hub: "%{hub}'s producers:" + shopping_oc_closed: Stengt for bestilling + shopping_oc_closed_description: "Vent til neste runde åpner (eller kontakt oss direkte for å se om vi tar i mot sene bestillinger)" + shopping_oc_last_closed: "Den siste runden stengte for %{distance_of_time} siden" + shopping_oc_next_open: "Neste runde åpner om %{distance_of_time}" + shopping_tabs_about: "Om %{distributor}" + shopping_tabs_contact: "Kontakt" + shopping_contact_address: "Adresse" + shopping_contact_web: "Kontakt" + shopping_contact_social: "Følg" + shopping_groups_part_of: "er en del av:" + shopping_producers_of_hub: "%{hub}s produsenter:" - enterprises_next_closing: "Next order closing" - enterprises_ready_for: "Ready for" - enterprises_choose: "Choose when you want your order:" + enterprises_next_closing: "Neste runde stenger" + enterprises_ready_for: "Klar til" + enterprises_choose: "Velg når du ønsker din bestilling:" - hubs_buy: "Shop for:" - hubs_delivery_options: "Delivery options" - hubs_pickup: "Pickup" - hubs_delivery: "Delivery" - hubs_producers: "Our producers" - hubs_filter_by: "Filter by" + hubs_buy: "Handle:" + hubs_delivery_options: "Leveringsvalg" + hubs_pickup: "Henting" + hubs_delivery: "Levering" + hubs_producers: "Våre produsenter" + hubs_filter_by: "Filtrer" hubs_filter_type: "Type" - hubs_filter_delivery: "Delivery" - hubs_matches: "Did you mean?" - hubs_intro: Shop in your local area - hubs_distance: Closest to - hubs_distance_filter: "Show me shops near %{location}" + hubs_filter_delivery: "Levering" + hubs_matches: "Mente du?" + hubs_intro: Handle lokalt + hubs_distance: Nærmest + hubs_distance_filter: "Vis meg butikker nær %{location}" - products_clear_all: Clear all - products_showing: "Showing:" - products_with: with - products_search: "Search by product or producer" - products_loading: "Loading products..." - products_updating_cart: "Updating cart..." - products_cart_empty: "Cart empty" - products_edit_cart: "Edit your cart" - products_from: from + products_clear_all: Fjern alt + products_showing: "Viser:" + products_with: med + products_search: "Søk på produkt eller produsent" + products_loading: "Laster produkter..." + products_updating_cart: "Oppdaterer handlekurv..." + products_cart_empty: "Handlekurv tom" + products_edit_cart: "Rediger handlekurv" + products_from: fra - search_no_results_html: "Sorry, no results found for %{query}. Try another search?" + search_no_results_html: "Beklager, ingen treff på %{query}. Prøv på nytt?" - components_profiles_popover: "Profiles do not have a shopfront on the Open Food Network, but may have their own physical or online shop elsewhere" - components_profiles_show: "Show profiles" - components_filters_nofilters: "No filters" - components_filters_clearfilters: "Clear all filters" + components_profiles_popover: "Profiler har ikke butikkvindu på Open Food Network men kan ha sin egen fysiske butikk eller nettbutikk et annet sted" + components_profiles_show: "Vis profiler" + components_filters_nofilters: "Ingen filter" + components_filters_clearfilters: "Fjern alle filtre" - groups_title: Groups - groups_headline: Groups / regions - groups_search: "Search name or keyword" - groups_no_groups: "No groups found" - groups_about: "About Us" - groups_producers: "Our producers" - groups_hubs: "Our hubs" - groups_contact_web: Contact - groups_contact_social: Follow - groups_contact_address: Address - groups_contact_email: Email us - groups_contact_website: Visit our website - groups_contact_facebook: Follow us on Facebook - groups_signup_title: Sign up as a group - groups_signup_headline: Groups sign up - groups_signup_intro: "We're an amazing platform for collaborative marketing, the easiest way for your members and stakeholders to reach new markets. We're non-profit, affordable, and simple." - groups_signup_email: Email us - groups_signup_motivation1: We transform food systems fairly. - groups_signup_motivation2: It's why we get out of bed every day. We're a global non-profit, based on open source code. We play fair. You can always trust us. - groups_signup_motivation3: We know you have big ideas, and we want to help. We'll share our knowledge, networks and resources. We know that isolation doesn't create change, so we'll partner with you. - groups_signup_motivation4: We meet you where you are. - groups_signup_motivation5: You might be an alliance of food hubs, producers, or distributors, and an industry body, or a local government. - groups_signup_motivation6: Whatever your role in your local food movement, we're ready to help. However you come to wonder what Open Food Network would look like or is doing in your part of the world, let's start the conversation. - groups_signup_motivation7: We make food movements make more sense. - groups_signup_motivation8: You need to activate and enable your networks, we offer a platform for conversation and action. You need real engagement. We’ll help reach all the players, all the stakeholders, all the sectors. - groups_signup_motivation9: You need resourcing. We’ll bring all our experience to bear. You need cooperation. We’ll better connect you to a global network of peers. - groups_signup_pricing: Group Account - groups_signup_studies: Case Studies - groups_signup_contact: Ready to discuss? - groups_signup_contact_text: "Get in touch to discover what OFN can do for you:" - groups_signup_detail: "Here's the detail." + groups_title: Grupper + groups_headline: Grupper / regioner + groups_search: "Søk på navn eller nøkkelord" + groups_no_groups: "Fant ingen grupper" + groups_about: "Om oss" + groups_producers: "Våre produsenter" + groups_hubs: "Våre hubs" + groups_contact_web: Kontakt + groups_contact_social: Følg + groups_contact_address: Adresse + groups_contact_email: Send oss epost + groups_contact_website: Besøk oss på nett + groups_contact_facebook: Følg oss på Facebook + groups_signup_title: Bli med som gruppe + groups_signup_headline: Bli med som gruppe + groups_signup_intro: "Vi er en fantastisk plattform for samarbeidende markedsføring, den enkleste måten for medlemmer og interessenter å nå nye markeder. Vi er non-profit, rimelig og enkel." + groups_signup_email: Send oss epost + groups_signup_motivation1: Vi forvandler matsystemer rettferdig. + groups_signup_motivation2: Det er grunnen til at vi står opp om morgenen. Vi er en global non-profit, basert på åpen kildekode. We opptrer rettferdig. Du kan alltid stole på oss. + groups_signup_motivation3: Vi vet du har gode ideer og vi ønsker å hjelpe. Vi deler vår kunnskap, våre nettverk og ressurser. Vi vet at isolasjon ikke skaper endring så vi vil samarbeide med deg. + groups_signup_motivation4: Vi møter deg der du er. + groups_signup_motivation5: Du kan være en allianse av mat-hubs, produsenter eller distributører, i industri eller lokale myndigheter. + groups_signup_motivation6: Uansett din rolle i den lokale matkjeden, vi ønsker å hjelpe. Hvis du lurer på hvordan Open Food Network vil se ut eller hva de gjør i din del av verden, la oss snakke sammen. + groups_signup_motivation7: Vi gir matkjeden mer mening. + groups_signup_motivation8: Du trenger å aktivere og tilrettelegge for dine nettverk, vi tilbyr en plattform for samtale og handling. Du trenger ekte engasjement. Vi hjelper til med å nå alle aktører, alle interessenter, alle sektorer. + groups_signup_motivation9: Du trenger flere ressurser. We kommer med all erfaring vi kan bære. Du trenger samarbeid. Vi kobler deg med et globalt nettverk med likesinnede. + groups_signup_pricing: Gruppekonto + groups_signup_studies: Bruksundersøkelser + groups_signup_contact: Klar for å snakke sammen? + groups_signup_contact_text: "Ta kontakt for å oppdage hva OFN kan gjøre for deg:" + groups_signup_detail: "Her er detaljene." - login_invalid: "Invalid email or password" + login_invalid: "Ugyldig epost eller passord" - modal_hubs: "Food Hubs" - modal_hubs_abstract: Our food hubs are the point of contact between you and the people who make your food! - modal_hubs_content1: You can search for a convenient hub by location or name. Some hubs have multiple points where you can pick-up your purchases, and some will also provide delivery options. Each food hub is a sales point with independent business operations and logistics - so variations between hubs are to be expected. - modal_hubs_content2: You can only shop at one food hub at a time. + modal_hubs: "Mat-hubs" + modal_hubs_abstract: Våre hubs er kontaktpunkt mellom deg og menneskene som lager maten din! + modal_hubs_content1: Du kan søke etter en passende hub på lokasjon eller navn. Noen hubs har flere hentepunkt hvor du kan plukke opp det du har kjøpt, og noen tilbyr også levering. Hver mat-hub er en butikk med uavhengige drift og logistikk - så det vil være forskjeller mellom huber. + modal_hubs_content2: Du kan kun handle hos en hub om gangen. - modal_groups: "Groups / Regions" - modal_groups_content1: These are the organisations and relationships between hubs which make up the Open Food Network. - modal_groups_content2: Some groups are clustered by location or council, others by non-geographic similarities. + modal_groups: "Grupper / Regioner" + modal_groups_content1: Dette er organisasjonene og hub-koblingene som utgjør Open Food Network. + modal_groups_content2: Noen grupper er klynger basert på lokalnivå eller kommunenivå, andre har ingen geografiske likheter. - modal_how: "How it works" - modal_how_shop: Shop the Open Food Network - modal_how_shop_explained: Search for a food hub near you to start shopping! You can expand each food hub to see what kinds of goodies are available, and click through to start shopping. (You can only shop one food hub at a time.) - modal_how_pickup: Pick-ups, delivery & shipping costs - modal_how_pickup_explained: Some food hubs deliver to your door, while others require you to pick-up your purchases. You can see which options are available on the homepage, and select which you'd like at the shopping and check-out pages. Delivery will cost more, and pricing differs from hub-to-hub. Each food hub is a sales point with independent business operations and logisitics - so variations between hubs are to be expected. - modal_how_more: Learn more - modal_how_more_explained: "If you want to learn more about the Open Food Network, how it works, and get involved, check out:" + modal_how: "Slik fungerer det" + modal_how_shop: Handle på Open Food Network + modal_how_shop_explained: Søk etter en mat-hub nær deg for å begynne å handle! Du kan utse detaljer for hver mat-hub for å se hvilke godbiter som er finnes, og klikk deg videre for å handle. (Du kan kun handle hos en mat-hub om gangen). + modal_how_pickup: Henting, levering og transportkostnader + modal_how_pickup_explained: Noen mat-hubs leverer på døren, mens andre krever at du henter varene du har kjøpt. Du kan se hvilke alternativ som er tilgjengelige på hjemmesiden, og velge hvilket du ønsker på handle- og betalingssidene. Levering koster mer, og prisene varierer fra hub til hub. Hver mat-hub er en forretning med uavhengig drift og logistikk - så variasjoner mellom hubs er naturlig. + modal_how_more: Finn ut mer + modal_how_more_explained: "Hvis du ønsker å lære mer om Open Food Network, hvordan det fungerer og ta del, sjekk ut:" - modal_producers: "Producers" - modal_producers_explained: "Our producers make all the delicious food you can shop for on the Open Food Network." + modal_producers: "Produsenter" + modal_producers_explained: "Våre produsenter lager all den herlige maten du kan handle på Open Food Network." ocs_choice_hub: "Hub:" - ocs_choice_oc: "Order Cycle:" - ocs_choice_text: "You have not yet picked where you will get your order from." - ocs_closed_headline: Orders are currently closed for this hub - ocs_closed_time: "The last cycle closed %{time} ago." - ocs_closed_contact: "Please contact your hub directly to see if they accept late orders, or wait until the next cycle opens." - ocs_closed_opens: "The next order cycle opens in %{time}" - ocs_closed_email: "Email: %{email}" - ocs_closed_phone: "Phone: %{phone}" - ocs_pickup_time: "Your order will be ready on %{pickup_time}" - ocs_change_date: "Change Collection Date" - ocs_change_date_notice: "(This will reset your cart)" - ocs_close_time: "ORDERS CLOSE" - ocs_when_headline: When do you want your order? - ocs_when_text: No products are displayed until you select a date. - ocs_when_closing: "Closing On" - ocs_when_choose: "Choose Order Cycle" - ocs_list: "List View" + ocs_choice_oc: "Bestillingsrunde:" + ocs_choice_text: "Du har ennå ikke valgt hvor du vil handle fra." + ocs_closed_headline: Bestillinger er for tiden stengt for denne huben + ocs_closed_time: "Den siste runden stengte for %{time} siden." + ocs_closed_contact: "Vennligst kontakt din hub direkte for å se om de godtar sene bestillinger, eller vent til neste runde åpner." + ocs_closed_opens: "Den neste bestillingsrunden åpner om %{time}" + ocs_closed_email: "Epost: %{email}" + ocs_closed_phone: "Telefon: %{phone}" + ocs_pickup_time: "Din bestilling vil være klar %{pickup_time}" + ocs_change_date: "Endre hentedato" + ocs_change_date_notice: "(Dette nullstiller kurven din)" + ocs_close_time: "BESTILLINGER STENGER" + ocs_when_headline: Når ønsker du bestillingen din? + ocs_when_text: Ingen varer vises før du velger en dato. + ocs_when_closing: "Stenger" + ocs_when_choose: "Velg Bestillingsrunde" + ocs_list: "Listevisning" - producers_about: About us - producers_buy: Shop for - producers_contact: Contact - producers_contact_phone: Call - producers_contact_social: Follow - producers_at: "products at:" - producers_filter: Filter by + producers_about: Om oss + producers_buy: Handle + producers_contact: Kontakt + producers_contact_phone: Ring + producers_contact_social: Følg + producers_at: "varer hos:" + producers_filter: Filtrer på producers_filter_type: Type - producers_title: Producers - producers_headline: Find local producers - producers_signup_title: Sign up as a producer - producers_signup_headline: Food producers, empowered. - producers_signup_motivation: Sell your food and tell your stories to diverse new markets. Save time and money on every overhead. We support innovation without the risk. We've levelled the playing field. - producers_signup_send: Join now - producers_signup_enterprise: Enterprise Accounts - producers_signup_studies: Stories from our producers. - producers_signup_cta_headline: Join now! - producers_signup_cta_action: Join now - producers_signup_detail: Here's the detail. + producers_title: Produsenter + producers_headline: Finn lokale produsenter + producers_signup_title: Bli med som produsent + producers_signup_headline: Matprodusenter, styrket. + producers_signup_motivation: Selg dine produkter og fortell dine historier til mangfoldige ny markeder. Spar tid og penger på alt du ikke ønsker å gjøre selv. Vi støtter nyskaping uten risiko. Vi jevner ut spillet. + producers_signup_send: Bli med nå + producers_signup_enterprise: Bedriftskonto + producers_signup_studies: Historier fra våre produsenter. + producers_signup_cta_headline: Bli med nå! + producers_signup_cta_action: Bli med nå + producers_signup_detail: Detaljene. - products_item: Item - products_description: Description + products_item: Vare + products_description: Beskrivelse products_variant: Variant - products_quantity: Quantity - products_availabel: Available? - products_price: Price + products_quantity: Antall + products_availabel: Tilgjengelig? + products_price: Pris - register_title: Register + register_title: Registrer - shops_title: Shops - shops_headline: Shopping, transformed. - shops_text: Food grows in cycles, farmers harvest in cycles, and we order food in cycles. If you find an order cycle closed, check back soon. - shops_signup_title: Sign up as a hub - shops_signup_headline: Food hubs, unlimited. - shops_signup_motivation: Whatever your model, we support you. However you change, we're with you. We're non-profit, independent, and open-sourced. We're the software partners you've dreamed of. - shops_signup_action: Join now - shops_signup_pricing: Enterprise Accounts - shops_signup_stories: Stories from our hubs. - shops_signup_help: We're ready to help. - shops_signup_help_text: You need a better return. You need new buyers and logistics partners. You need your story told across wholesale, retail, and the kitchen table. - shops_signup_detail: Here's the detail. + shops_title: Butikker + shops_headline: Handling på en ny måte. + shops_text: Mat gror i syklus, bønder høster i syklus, og vi bestiller mat i syklus. Hvis du møter en stengt besillingsrunde, sjekk igjen snart. + shops_signup_title: Bli med som hub + shops_signup_headline: Mat-hubs, ubegrenset. + shops_signup_motivation: Uansett modell støtter vi deg. Uansett hvordan du forandrer deg er vi med deg. Vi er non-profitt, uavhengig, og transparent. Vi er løsningspartneren du har drømt om. + shops_signup_action: Bli med nå + shops_signup_pricing: Bedriftskonto + shops_signup_stories: Historier fra våre hubs. + shops_signup_help: Vi er klar til å hjelpe. + shops_signup_help_text: Du trenger bedre resultater. Du trenger nye kunder og logistikkpartnere. Du trenger å få din historie fortalt hos grossister, i dagligvaren og rundt kjøkkenbordet. + shops_signup_detail: Detaljene. - orders_fees: Fees... - orders_edit_title: Shopping Cart - orders_edit_headline: Your shopping cart - orders_edit_time: Order ready for - orders_edit_continue: Continue shopping - orders_edit_checkout: Checkout - orders_form_empty_cart: "Empty cart" - orders_form_subtotal: Produce subtotal - orders_form_admin: Admin & handling - orders_form_subtotal: Total - orders_oc_expired_headline: Orders have closed for this order cycle - orders_oc_expired_text: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders." - orders_oc_expired_text_others_html: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders %{link}." - orders_oc_expired_text_link: "or see the other order cycles available at this hub" - orders_oc_expired_email: "Email:" - orders_oc_expired_phone: "Phone:" - orders_show_title: Order Confirmation - orders_show_time: Order ready on - orders_show_number: Order confirmation + orders_fees: Gebyrer... + orders_edit_title: Handlekurv + orders_edit_headline: Din handlekurv + orders_edit_time: Bestilling klar for + orders_edit_continue: Fortsett å handle + orders_edit_checkout: Kassen + orders_form_empty_cart: "Tøm handlekurv" + orders_form_subtotal: Vis delsum + orders_form_admin: Admin og håndtering + orders_form_subtotal: Sum + orders_oc_expired_headline: Bestillinger stengt for denne runden + orders_oc_expired_text: "Beklager, bestillinger for denne runden stengte for %{time} siden! Kontakt din hub direkte for å høre om de tar i mot sene bestillinger." + orders_oc_expired_text_others_html: "Beklager, bestillinger for denne runden stengte for %{time} siden! Kontakt din hub direkte for å høre om de tar i mot sene bestillinger %{link}." + orders_oc_expired_text_link: "eller se på de andre bestillinsrundene tilgjengelig fra denne huben" + orders_oc_expired_email: "Epost:" + orders_oc_expired_phone: "Telefon:" + orders_show_title: Ordrebekreftelse + orders_show_time: Bestilling klar for + orders_show_number: Ordrebekreftelse - products_cart_distributor_choice: "Distributor for your order:" - products_cart_distributor_change: "Your distributor for this order will be changed to %{name} if you add this product to your cart." - products_cart_distributor_is: "Your distributor for this order is %{name}." - products_distributor_error: "Please complete your order at %{link} before shopping with another distributor." - products_oc: "Order cycle for your order:" - products_oc_change: "Your order cycle for this order will be changed to %{name} if you add this product to your cart." - products_oc_is: "Your order cycle for this order is %{name}." - products_oc_error: "Please complete your order from %{link} before shopping in a different order cycle." - products_oc_current: "your current order cycle" - products_quantity: Quantity - products_max_quantity: Max quantity - products_distributor: Distributor - products_distributor_info: When you select a distributor for your order, their address and pickup times will be displayed here. + products_cart_distributor_choice: "Distributør for bestillingen:" + products_cart_distributor_change: "Din distributør for denne ordren vil bli endret til %{name} hvis du legger til dette produktet i handlekurven din." + products_cart_distributor_is: "Din distributør for denne ordren er %{name}." + products_distributor_error: "Vennligst fullfør din bestilling hos %{link} før du handler hos en annen distributør." + products_oc: "Bestillingsrunde for din bestilling:" + products_oc_change: "Din bestillingsrunde for denne bestillingen vil bli endret til %{name} hvis du legger til dette produktet i den handlekurv." + products_oc_is: "Din bestillingsrunde for denne bestillingen er %{name}." + products_oc_error: "Vennligst fullfør din bestilling hos %{link} før du handler i en annen bestillingsrunde." + products_oc_current: "din nåværende bestillingsrunde" + products_quantity: Mengde + products_max_quantity: Max mengde + products_distributor: Distributør + products_distributor_info: Når du velger en distributør for din bestilling, vil deres adresse og hentetider vises her. diff --git a/lib/open_food_network/permalink_generator.rb b/lib/open_food_network/permalink_generator.rb new file mode 100644 index 0000000000..99d923fd1e --- /dev/null +++ b/lib/open_food_network/permalink_generator.rb @@ -0,0 +1,22 @@ +module PermalinkGenerator + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def find_available_value(existing, requested) + return requested unless existing.include?(requested) + used_indices = existing.map do |p| + p.slice!(/^#{requested}/) + p.match(/^\d+$/).to_s.to_i + end + options = (1..used_indices.length + 1).to_a - used_indices + requested + options.first.to_s + end + end + + def create_unique_permalink(requested) + existing = self.class.where('id != ?', id).where("permalink LIKE ?", "#{requested}%").pluck(:permalink) + self.class.find_available_value(existing, requested) + end +end diff --git a/lib/open_food_network/rack_request_blocker.rb b/lib/open_food_network/rack_request_blocker.rb new file mode 100644 index 0000000000..71f1c450e2 --- /dev/null +++ b/lib/open_food_network/rack_request_blocker.rb @@ -0,0 +1,77 @@ +# Copied from http://blog.salsify.com/engineering/tearing-capybara-ajax-tests +# https://gist.github.com/jturkel/9317269/raw/ff7838684370fd8a468ffe1e5ce1f3e46ba39951/rack_request_blocker.rb + +require 'atomic' + +# Rack middleware that keeps track of the number of active requests and can block new requests. +class RackRequestBlocker + + @@num_active_requests = Atomic.new(0) + @@block_requests = Atomic.new(false) + + # Returns the number of requests the server is currently processing. + def self.num_active_requests + @@num_active_requests.value + end + + # Prevents the server from accepting new requests. Any new requests will return an HTTP + # 503 status. + def self.block_requests! + @@block_requests.value = true + end + + # Allows the server to accept requests again. + def self.allow_requests! + @@block_requests.value = false + end + + def initialize(app) + @app = app + end + + def call(env) + increment_active_requests + if block_requests? + block_request(env) + else + @app.call(env) + end + ensure + decrement_active_requests + end + + def self.wait_for_requests_complete + self.block_requests! + max_wait_time = 30 + polling_interval = 0.01 + wait_until = Time.now + max_wait_time.seconds + while true + return if self.num_active_requests == 0 + if Time.now > wait_until + raise "Failed waiting for completing requests, #{self.num_active_requests} running." + else + sleep(polling_interval) + end + end + ensure + self.allow_requests! + end + + private + + def block_requests? + @@block_requests.value + end + + def block_request(env) + [503, {}, []] + end + + def increment_active_requests + @@num_active_requests.update { |v| v + 1 } + end + + def decrement_active_requests + @@num_active_requests.update { |v| v - 1 } + end +end diff --git a/script/ci/includes.sh b/script/ci/includes.sh index a0599504c2..619d03153e 100644 --- a/script/ci/includes.sh +++ b/script/ci/includes.sh @@ -5,18 +5,33 @@ function load_environment { fi } +function master_merged { + if [[ `git tag -l "$BUILDKITE_BRANCH"` != '' ]]; then + echo "'$BUILDKITE_BRANCH' is a tag." + if [[ `git tag -l --contains origin/master "$BUILDKITE_BRANCH"` != '' ]]; then + echo "This tag contains the current master." + return 0 + else + echo "This tag does not contain the current master." + return 1 + fi + fi + if [[ `git branch -r --merged origin/$BUILDKITE_BRANCH` == *origin/master* ]]; then + echo "This branch already has the current master merged." + return 0 + fi + return 1 +} + function exit_unless_master_merged { - if [[ `git branch -a --merged origin/$BUILDKITE_BRANCH` != *origin/master* ]]; then + if ! master_merged; then echo "This branch does not have the current master merged. Please merge master and push again." exit 1 fi } function succeed_if_master_merged { - if [[ `git branch -a --merged origin/$BUILDKITE_BRANCH` == *origin/master* ]]; then - echo "This branch already has the current master merged." - exit 0 - fi + master_merged && exit 0 } function set_ofn_commit { diff --git a/spec/features/admin/bulk_order_management_spec.rb b/spec/features/admin/bulk_order_management_spec.rb index bf77ab415a..8d52f21b48 100644 --- a/spec/features/admin/bulk_order_management_spec.rb +++ b/spec/features/admin/bulk_order_management_spec.rb @@ -124,7 +124,7 @@ feature %q{ visit '/admin/orders/bulk_management' end - pending "displays an update button which submits pending changes" do + it "displays an update button which submits pending changes" do fill_in "quantity", :with => 2 page.should have_selector "input[name='quantity'].update-pending" page.should_not have_selector "input[name='quantity'].update-success" @@ -132,6 +132,8 @@ feature %q{ click_button "Update" page.should_not have_selector "input[name='quantity'].update-pending" page.should have_selector "input[name='quantity'].update-success" + page.should have_selector "input[name='final_weight_volume'].update-success", visible: false + page.should have_selector "input[name='price'].update-success", visible: false end end end @@ -288,9 +290,8 @@ feature %q{ visit '/admin/orders/bulk_management' end - pending "displays a select box for order cycles, which filters line items by the selected order cycle" do - order_cycle_names = ["All"] - OrderCycle.all.each{ |oc| order_cycle_names << oc.name } + it "displays a select box for order cycles, which filters line items by the selected order cycle" do + order_cycle_names = OrderCycle.pluck(:name).push "All" find("div.select2-container#s2id_order_cycle_filter").click order_cycle_names.each { |ocn| page.should have_selector "div.select2-drop-active ul.select2-results li", text: ocn } find("div.select2-container#s2id_order_cycle_filter").click @@ -440,13 +441,15 @@ feature %q{ page.should have_button "SAVE" end - pending "saves pendings changes when 'SAVE' button is clicked" do + it "saves pendings changes when 'SAVE' button is clicked" do within("tr#li_#{li2.id} td.quantity") do page.fill_in "quantity", :with => (li2.quantity + 1).to_s end fill_in "start_date_filter", :with => (Date.today - 9).strftime("%F %T") + page.should have_selector "input[name='quantity'].update-pending" click_button "SAVE" - page.should_not have_selector "input[name='quantity'].update-pending" + page.should have_no_selector "input.update-pending" + page.should have_selector "input[name='quantity'].update-success" within("tr#li_#{li2.id} td.quantity") do page.should have_field "quantity", :with => ( li2.quantity + 1 ).to_s end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 79d5c02d86..068a2184de 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -80,6 +80,11 @@ RSpec.configure do |config| config.before(:each, js: true) { DatabaseCleaner.strategy = :deletion, {except: ['spree_countries', 'spree_states']} } config.before(:each) { DatabaseCleaner.start } config.after(:each) { DatabaseCleaner.clean } + config.after(:each, js:true) do + Capybara.reset_sessions! + RackRequestBlocker.wait_for_requests_complete + DatabaseCleaner.clean + end # Geocoding config.before(:each) { Spree::Address.any_instance.stub(:geocode).and_return([1,1]) }