Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Wynn Netherland committed Jul 10, 2008
0 parents commit f5d79f7
Show file tree
Hide file tree
Showing 16 changed files with 324 additions and 0 deletions.
20 changes: 20 additions & 0 deletions MIT-LICENSE
@@ -0,0 +1,20 @@
Copyright (c) 2007 [name of plugin creator]

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 changes: 38 additions & 0 deletions README
@@ -0,0 +1,38 @@
= ActsAsRedeemable


Adds redemption capability to a model for items like coupons, invitation codes, etc.

Each redeemable gets a unique code upon creation that can be sent in an email or printed as a coupon code.


== Usage

=== Optionally generate the model

script/generate redeemable Coupon
rake db:migrate


=== Make your ActiveRecord model act as redeemable.

class Coupon < ActiveRecord::Base
acts_as_redeemable :valid_for => 30.days, :code_length => 8 # optional expiration, code length
end

=== Create a new instance

c = Coupon.new
c.user_id = 1 # The user who created the coupon
c.save
c.code
# "4D9110A3"
c.created_at
# Fri Feb 15 14:56:37 -0600 2008
c.expires_on
# Fri Mar 16 14:56:37 -0600 2008




Copyright (c) 2008 Squeejee, released under the MIT license
32 changes: 32 additions & 0 deletions README.textile
@@ -0,0 +1,32 @@
h2. ActsAsRedeemable

Adds redemption capability to a model for items like coupons, invitation codes, etc. Each redeemable gets a unique code upon creation that can be sent in an email or printed as a coupon code.


h2. Usage

h3. Optionally generate the model

script/generate redeemable Coupon
rake db:migrate


h3. Make your ActiveRecord model act as redeemable.

class Coupon < ActiveRecord::Base
acts_as_redeemable :valid_for => 30.days, :code_length => 8 # optional expiration, code length
end

h3. Create a new instance

c = Coupon.new
c.user_id = 1 # The user who created the coupon
c.save
c.code
# "4D9110A3"
c.created_at
# Fri Feb 15 14:56:37 -0600 2008
c.expires_on
# Fri Mar 16 14:56:37 -0600 2008</code></pre>

Copyright (c) 2008 Squeejee, released under the MIT license
22 changes: 22 additions & 0 deletions Rakefile
@@ -0,0 +1,22 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'

desc 'Default: run unit tests.'
task :default => :test

desc 'Test the acts_as_redeemable plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end

desc 'Generate documentation for the acts_as_redeemable plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'ActsAsRedeemable'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end
2 changes: 2 additions & 0 deletions init.rb
@@ -0,0 +1,2 @@
require 'acts_as_redeemable'
ActiveRecord::Base.send(:include, Squeejee::Acts::Redeemable)
1 change: 1 addition & 0 deletions install.rb
@@ -0,0 +1 @@
# Install hook code here
102 changes: 102 additions & 0 deletions lib/acts_as_redeemable.rb
@@ -0,0 +1,102 @@
require 'md5'
module Squeejee #:nodoc:
module Acts #:nodoc:
module Redeemable #:nodoc:
def self.included(base)
base.extend(ClassMethods)
end
# This act provides the capabilities for redeeming and expiring models. Useful for things like
# coupons, invitations, and special offers.
#
# Coupon example:
#
# class Coupon < ActiveRecord::Base
# acts_as_redeemable :valid_for => 30.days, :code_length => 8 # optional expiration, code length
# end
#
#
# c = Coupon.new
# c.user_id = 1 # The user who created the coupon
# c.save
# c.code
#
# # "4D9110A3"
module ClassMethods
# Configuration options are:
#
# * +valid_for+ - specifies the duration until redeemable expire. Default is no expiration
# * +code_length+ - set the length of the generated unique code. Default is six alphanumeric characters
# * example: <tt>acts_as_redeemable :valid_for => 30.days, :code_length => 8</tt>
def acts_as_redeemable(options = {})
unless redeemable? # don't let AR call this twice
cattr_accessor :valid_for
cattr_accessor :code_length
before_create :setup_new
self.valid_for = options[:valid_for] unless options[:valid_for].nil?
self.code_length = (options[:code_length].nil? ? 6 : options[:code_length])
end
include InstanceMethods

# Generates an alphanumeric code using an MD5 hash
# * +code_length+ - number of characters to return
def generate_code(code_length=6)
chars = ("a".."z").to_a + ("1".."9").to_a
new_code = Array.new(code_length, '').collect{chars[rand(chars.size)]}.join
Digest::MD5.hexdigest(new_code)[0..(code_length-1)].upcase
end

# Generates unique code based on +generate_code+ method
def generate_unique_code
begin
new_code = generate_code(self.code_length)
end until !active_code?(new_code)
new_code
end

# Checks the database to ensure the specified code is not taken
def active_code?(code)
find :first, :conditions => {:code => code}
end

end

def redeemable? #:nodoc:
self.included_modules.include?(InstanceMethods)
end
end

module InstanceMethods

# Marks the redeemable redeemed by the given user id
# * +redeemed_by_id+ - id of redeeming user
def redeem!(redeemed_by_id)
unless self.redeemed? or self.expired?
self.update_attributes({:redeemed_by_id => redeemed_by_id, :recipient_id => redeemed_by_id, :redeemed_at => Time.now})
self.after_redeem
end
end

# Returns whether or not the redeemable has been redeemed
def redeemed?
self.redeemed_at?
end

# Returns whether or not the redeemable has expired
def expired?
self.expires_on? and self.expires_on < Time.now
end

def setup_new #:nodoc:
self.code = self.class.generate_unique_code
unless self.class.valid_for.nil? or self.expires_on?
self.expires_on = self.created_at + self.class.valid_for
end
end

# Callback for business logic to implement after redemption
def after_redeem() end

end
end
end
end
20 changes: 20 additions & 0 deletions lib/generators/redeemable/redeemable_generator.rb
@@ -0,0 +1,20 @@
class RedeemableGenerator < Rails::Generator::NamedBase #:nodoc:
def manifest
record do |m|
m.class_collisions class_name

m.template "app/models/model_template.rb", "app/models/#{file_name}.rb"

unless options[:skip_migration]
m.directory 'db/migrate'
m.migration_template 'db/migration.rb', 'db/migrate', :assigns => {
:migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
}, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
end

m.template "test/fixtures/model.yml", "test/fixtures/#{table_name}.yml"
m.template "test/unit/model_test.rb", "test/unit/#{file_name}.rb"

end
end
end
7 changes: 7 additions & 0 deletions lib/generators/redeemable/templates/USAGE
@@ -0,0 +1,7 @@
script/generate redeemable (ModelName)

Example:

script/generate redeemable Coupon

This will create a Coupon model
@@ -0,0 +1,7 @@
class <%= class_name %> < ActiveRecord::Base
acts_as_redeemable
belongs_to :user
belongs_to :redeemed_by, :class_name => "User", :foreign_key => "redeemed_by_id"
validates_presence_of :user_id
end
19 changes: 19 additions & 0 deletions lib/generators/redeemable/templates/db/migration.rb
@@ -0,0 +1,19 @@
class <%= migration_name %> < ActiveRecord::Migration
def self.up
create_table :<%= table_name %> do |t|

t.column :user_id, :integer
t.column :code, :string

t.column :created_at, :datetime
t.column :redeemed_at, :datetime
t.column :redeemed_by_id, :integer

t.column :expires_on => :datetime
end
end

def self.down
drop_table :<%= table_name %>
end
end
5 changes: 5 additions & 0 deletions lib/generators/redeemable/templates/test/fixtures/model.yml
@@ -0,0 +1,5 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
one:
id: 1
two:
id: 2
10 changes: 10 additions & 0 deletions lib/generators/redeemable/templates/test/unit/model_test.rb
@@ -0,0 +1,10 @@
require File.dirname(__FILE__) + '/../test_helper'

class <%= class_name %>Test < Test::Unit::TestCase
fixtures :<%= file_name %>

# Replace this with your real tests.
def test_truth
assert true
end
end
22 changes: 22 additions & 0 deletions test/acts_as_redeemable_test.rb
@@ -0,0 +1,22 @@
require 'test/unit'
require File.expand_path(File.join(File.dirname(__FILE__), '../../../../config/environment.rb'))
require File.dirname(__FILE__) + '/../lib/acts_as_redeemable'
load(File.dirname(__FILE__) + "/schema.rb")

class FreeTodayCoupon < ActiveRecord::Base
acts_as_redeemable

end

class ActsAsRedeemableTest < Test::Unit::TestCase
# Replace this with your real tests.
def test_should_generate_six_digit_code
assert_equal FreeTodayCoupon.generate_unique_code.length, 6
end

def test_should_mark_redeemed
f = FreeTodayCoupon.create(:user_id => 1)
f.redeem!(2)
assert f.redeemed?
end
end
15 changes: 15 additions & 0 deletions test/schema.rb
@@ -0,0 +1,15 @@
ActiveRecord::Schema.define(:version => 1) do

create_table :free_today_coupons, :force => true do |t|

t.column :user_id, :integer
t.column :code, :string

t.column :created_at, :datetime
t.column :redeemed_at, :datetime
t.column :redeemed_by_id, :integer

end


end
2 changes: 2 additions & 0 deletions uninstall.rb
@@ -0,0 +1,2 @@
require 'acts_as_redeemable'
ActiveRecord::Base.send(:include, Squeejee::Acts::Redeemable)

0 comments on commit f5d79f7

Please sign in to comment.