Class: Pass

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
Extendable
Defined in:
app/models/pass.rb

Constant Summary collapse

EXPIRED_ERROR =
"Sorry! This pass has expired."
OUT_OF_TICKETS =
"Sorry! There are no tickets remaning on this pass."
OVER_EVENT_LIMIT =
"Sorry! This pass has already been redeemed for this event."
EVENT_NOT_ELIGIBLE =
"This pass is not eligible for some of the events in your cart."
SHOW_NOT_ELIGIBLE =
"This pass is not eligible for some of the shows in your cart."
TICKET_TYPE_NOT_ELIGIBLE =
"This pass is not eligible for some of the ticket types in your cart"
ORG_ERROR =
"Sorry! This pass is not eligible for some of the tickets in your cart"

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Extendable

#adjust_expiration_to

Class Method Details

.for(pass_type) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
# File 'app/models/pass.rb', line 23

def self.for(pass_type)
  new.tap do |pass|
    pass.pass_type  = pass_type
    pass.organization     = pass_type.organization
    pass.price            = pass_type.price
    pass.sold_price       = pass.price
    pass.tickets_allowed  = pass_type.tickets_allowed
    pass.pass_code        = new_pass_code
    pass.starts_at        = pass_type.starts_at
    pass.ends_at          = pass_type.ends_at
  end
end

.new_pass_code(size = 8) ⇒ Object



83
84
85
86
87
# File 'app/models/pass.rb', line 83

def self.new_pass_code(size=8)
  # Avoid confusable characters like 1/L/I and 0/O
  charset = %w{ 2 3 4 6 7 9 A C D E F G H J K M N P Q R T V W X Y Z}
  (0...size).map{ charset.to_a[rand(charset.size)] }.join
end

.to_sentence(passes) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
# File 'app/models/pass.rb', line 93

def self.to_sentence(passes)
  if passes.any?
    pass_types = passes.collect(&:pass_type).uniq
    if pass_types.length > 1
      "multiple passes"
    else
      ActionController::Base.helpers.pluralize(passes.length, "#{passes.first.pass_type.passerize}")
    end
  else
    "No passes"
  end
end

Instance Method Details

#adjust_ends_atObject



36
37
38
# File 'app/models/pass.rb', line 36

def adjust_ends_at
  self.ends_at = self.ends_at.end_of_day unless self.ends_at.blank?
end

#alive?Boolean

Returns:

  • (Boolean)


63
64
65
# File 'app/models/pass.rb', line 63

def alive?
  (tickets_allowed > tickets_purchased) && (starts_at < DateTime.now) && (ends_at > DateTime.now)
end

#applies_to?(thing) ⇒ Boolean

Returns:

  • (Boolean)


89
90
91
# File 'app/models/pass.rb', line 89

def applies_to?(thing)
  thing.applies_to_pass? self
end

#apply_pass_to_cart(cart) ⇒ Object



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'app/models/pass.rb', line 117

def apply_pass_to_cart(cart)
  Rails.logger.debug ("PASSES Applying pass [#{self.id}] to cart [#{cart.id}]")
  cart.prepare_for_pass!

  #the number of tickets remining for this pass. We go straight to the DB here
  #to avoid a race condition on pass.tickets_remaining
  tickets_remaining_on_pass = self.tickets_allowed - tickets_purchased
  Rails.logger.debug ("PASSES [#{tickets_remaining_on_pass}] tickets remaining on this pass")

  self.errors.add(:base, EXPIRED_ERROR)  and return if self.expired?
  self.errors.add(:base, OUT_OF_TICKETS) and return if tickets_remaining_on_pass < 1

  #The list of tickets w'eve applied this pass too
  tickets_applied = []

  #The list of tickets we rejected for being over the limit
  tickets_rejected = []

  over_event_limit = false
  pass_errors = Set.new

  transaction do

    cart.applied_pass = self

    cart.tickets.each do |ticket|
      (pass_errors << EVENT_NOT_ELIGIBLE       and next) unless self.applies_to? (ticket.show.event)
      (pass_errors << SHOW_NOT_ELIGIBLE        and next) unless self.applies_to? (ticket.show)
      (pass_errors << TICKET_TYPE_NOT_ELIGIBLE and next) unless self.applies_to? (ticket.ticket_type)
      (pass_errors << ORG_ERROR                and next) unless (self.organization == ticket.organization)

      event = ticket.show.event
      tickets_remaining_for_this_event = tickets_remaining_for(event)
      Rails.logger.debug ("PASSES [#{tickets_remaining_for_this_event}] tickets remaining to event [#{event.id}]")
      over_event_limit = true if tickets_remaining_for_this_event == 0

      tickets_remaining = [tickets_remaining_on_pass, tickets_remaining_for_this_event].min
      Rails.logger.debug ("PASSES [#{tickets_remaining}] tickets remaining after considering pass and event")

      if tickets_remaining > 0
        Rails.logger.debug ("PASSES adding pass [#{self.id}] to ticket [#{ticket.id}]")
        ticket.pass = self
        ticket.cart_price = 0
        ticket.save
        tickets_applied << ticket
        tickets_remaining_on_pass = tickets_remaining_on_pass - 1
        Rails.logger.debug ("PASSES Now only [#{tickets_remaining_on_pass}] tickets remaining on this pass")
      else
        Rails.logger.debug ("PASSES rejecting ticket [#{ticket.id}]")
        tickets_rejected << tickets 
      end
    end

    if tickets_rejected.length > 0 
      str = ""
      if tickets_applied.length == 0
        str += "There are no tickets"
      else
        str += "Only #{ActionController::Base.helpers.pluralize(tickets_applied.length, 'ticket')}"
        str += tickets_applied.length > 1 ? " were" : " was"
      end

      str += " remaining on this pass"
      str += " for this event" if over_event_limit

      if tickets_applied.length > 0
        str += ". We've applied your pass to #{ActionController::Base.helpers.pluralize(tickets_applied.length, 'ticket')} and left the others in your cart."
      end

      pass_errors << str
    end

    pass_errors.each { |err| self.errors.add(:base, err) }

    FeeCalculator.apply(FeeStrategy.new).to(cart)
    cart.save
  end


end

#cart_priceObject



44
45
46
# File 'app/models/pass.rb', line 44

def cart_price
  price
end

#exchangeable?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'app/models/pass.rb', line 75

def exchangeable?
  false
end

#expire!Object



40
41
42
# File 'app/models/pass.rb', line 40

def expire!
  update_column(:ends_at, DateTime.now)
end

#expired?Boolean

Returns:

  • (Boolean)


67
68
69
# File 'app/models/pass.rb', line 67

def expired?
  ends_at < Time.now
end

#realized_feeObject



59
60
61
# File 'app/models/pass.rb', line 59

def realized_fee
  self.pass_type.hide_fee? ? self.price * PassType::SERVICE_FEE : 0
end

#refundable?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'app/models/pass.rb', line 71

def refundable?
  true
end

#returnable?Boolean

Returns:

  • (Boolean)


79
80
81
# File 'app/models/pass.rb', line 79

def returnable?
  false
end

#tickets_purchasedObject

We intentionally go to the DB here to avoid race conditions when applying a pass to the cart.

Method could cause problems in very large N+1 situations. Where up-to-date accuracy isn’t needed, Pass.includes(:tickets).tickets.length might be better



55
56
57
# File 'app/models/pass.rb', line 55

def tickets_purchased
  Ticket.where(:pass_id => self.id).count
end

#tickets_remaining_for(event) ⇒ Object



106
107
108
109
110
111
112
113
114
115
# File 'app/models/pass.rb', line 106

def tickets_remaining_for(event)
  @ept = EventsPassType.active
                .where(:organization_id => self.organization_id)
                .where(:event_id => event.id)
                .where(:pass_type_id => self.pass_type_id).first

  return 0                    if @ept.nil?
  return self.tickets_allowed if @ept.limit_per_pass.nil?
  return @ept.limit_per_pass - event.tickets.where(:pass_id => self.id).count
end