diff --git a/README.md b/README.md index b617baf..6a4c235 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ -# Promocode +# Promocode Generator -TODO: Write a gem description +Generates save and unique (when used with AR) codes which can be handed to customers for promotional campaigns. ## Installation Add this line to your application's Gemfile: - gem 'promocode' + gem 'promocode_generator' And then execute: @@ -14,11 +14,26 @@ And then execute: Or install it yourself as: - $ gem install promocode + $ gem install promocode_generator ## Usage -TODO: Write usage instructions here +Without Rails: + + Use PromocodeGenerator.generate(length) to produce a promotional code of the desired length. + +With Rails: + + In your model, use the promocode_attribute method to automatically generate a promotional code of length 8 in the before_save hook: + + ```ruby + class Campaign < ActiveRecord::Base + promocode_attribute :code + end + ``` + + Basic uniqueness validation comes by default so that codes are generated until a non-existing code is found in the db. Provide the :reject_if option to customize: + `promocode_attribute :code, :reject_if => Proc.new { |code| ModelA.where(:code => code).any? }` ## Contributing diff --git a/Rakefile b/Rakefile index 2995527..1b41e31 100644 --- a/Rakefile +++ b/Rakefile @@ -1 +1,5 @@ require "bundler/gem_tasks" +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) +task default: :spec diff --git a/lib/promocode/version.rb b/lib/promocode/version.rb deleted file mode 100644 index 03dba59..0000000 --- a/lib/promocode/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Promocode - VERSION = "0.0.1" -end diff --git a/lib/promocode_generator.rb b/lib/promocode_generator.rb index 364951e..70192e5 100644 --- a/lib/promocode_generator.rb +++ b/lib/promocode_generator.rb @@ -1,5 +1,11 @@ -require "promocode/version" +require "promocode_generator/version" +require "promocode_generator/model_additions" -module Promocode - # Your code goes here... +module PromocodeGenerator + # Generates an 8 character random code of uppercase letters and numbers + # It explicitly excludes the letters I, L, O and the number 0 to avoid + # confusion + def self.generate(length = 8) + [('A'..'Z').to_a - ['I', 'L', 'O'], ('1'..'9').to_a].flatten.shuffle[0,length].join + end end diff --git a/lib/promocode_generator/model_additions.rb b/lib/promocode_generator/model_additions.rb new file mode 100644 index 0000000..347f13a --- /dev/null +++ b/lib/promocode_generator/model_additions.rb @@ -0,0 +1,14 @@ +module PromocodeGenerator + module ModelAdditions + def promocode_attribute(attribute, options = {}) + options[:reject_if] ||= Proc.new { |code| self.where(attribute => code).any? } + + before_save do + # Make sure not to generate the same code twice! + begin + self.code = PromocodeGenerator.generate + end while options[:reject_if].call(code) + end + end + end +end diff --git a/lib/promocode_generator/version.rb b/lib/promocode_generator/version.rb new file mode 100644 index 0000000..dd97fb6 --- /dev/null +++ b/lib/promocode_generator/version.rb @@ -0,0 +1,3 @@ +module PromocodeGenerator + VERSION = "0.0.1" +end diff --git a/promocode_generator.gemspec b/promocode_generator.gemspec index e952b39..31b62a4 100644 --- a/promocode_generator.gemspec +++ b/promocode_generator.gemspec @@ -21,4 +21,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "rake" spec.add_development_dependency "rspec" + spec.add_development_dependency "supermodel" end diff --git a/spec/promocode_generator/model_additions_spec.rb b/spec/promocode_generator/model_additions_spec.rb new file mode 100644 index 0000000..1d955d4 --- /dev/null +++ b/spec/promocode_generator/model_additions_spec.rb @@ -0,0 +1,40 @@ +require 'spec_helper' + +# Dummy reject condition that can be stubbed in the tests below +module RejectCondition + def self.check(code) + false + end +end + +class Campaign < SuperModel::Base + include ActiveModel::Validations::Callbacks + extend PromocodeGenerator::ModelAdditions + promocode_attribute :code, :reject_if => Proc.new { |code| RejectCondition.check(code) } +end + +describe PromocodeGenerator::ModelAdditions do + let(:campaign) { Campaign.new(:name => "foo") } + + context "when the model is saved" do + it "generates a code" do + expect(PromocodeGenerator).to receive(:generate).once.and_return('ABCDEFG') + campaign.save! + end + + it "assigns the code to the model" do + campaign.save! + expect(campaign.code).not_to be_nil + end + end + + context "when the reject_if block returns false" do + before { RejectCondition.stub(:check).and_return(true, false) } + after { RejectCondition.unstub(:check) } + + it "generates a new code" do + expect(PromocodeGenerator).to receive(:generate).exactly(2).times + campaign.save! + end + end +end diff --git a/spec/promocode_generator_spec.rb b/spec/promocode_generator_spec.rb new file mode 100644 index 0000000..7448448 --- /dev/null +++ b/spec/promocode_generator_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe PromocodeGenerator do + describe ".generate" do + it "generates a code of the given length" do + expect(PromocodeGenerator.generate(10).length).to eq(10) + end + + it "generates a code with default length" do + expect(PromocodeGenerator.generate.length).to be > 0 + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c6664f1..1d935e5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1 +1,2 @@ -require 'promocode' +require 'promocode_generator' +require 'supermodel'