acts_as_holdable 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ab95215af6bfe8d8811807c536ca35ca837a7c83d030c087986fd4d78fb37716
4
+ data.tar.gz: 548d98f061d57a4c7e12cf3a71cefa9808c0109e793c124d06747a52dfacd073
5
+ SHA512:
6
+ metadata.gz: f43dd7e5ff2f853233b100bcc814615faad4ade99b49b5a4107e3a65c7caf1a73e2d99111ba786f7ea144098db761e92c7f345ba19a3655ae8a5fa6730bb0641
7
+ data.tar.gz: 54f10fd2c304f946f6bcab51aff8fced9c88842e44873e47611a9dbc44ad5de98b4d0479a1ddc094fa6565d7fb0cd06dbcfdf4092b4a5449a45535ebe50d6bed
data/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # ActsAsHoldable
2
+ ActsAsHoldable allows resources to be held by users. It:
3
+
4
+ * Is a MVC solution based on Rails engines
5
+
6
+ ## Installation
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'acts_as_holdable'
11
+ ```
12
+
13
+ And then execute:
14
+ ```bash
15
+ $ bundle
16
+ ```
17
+
18
+ Or install it yourself as:
19
+ ```bash
20
+ $ gem install acts_as_holdable
21
+ ```
22
+
23
+ ## Migrations
24
+
25
+ ```bash
26
+ bundle exec rake acts_as_holdable_engine:install:migrations
27
+ ```
28
+
29
+ ## Holdables, Holders and Holdings
30
+
31
+ To set-up a Holdable model, use acts_as_holdable. A Holdable model is enabled to be held.
32
+
33
+ ```ruby
34
+ class Ticket < ApplicationRecord
35
+ acts_as_holdable
36
+ end
37
+ ```
38
+
39
+ To set-up a Holder model, use acts_as_holder. Only Holders can create holdings.
40
+
41
+ ```ruby
42
+ class User < ApplicationRecord
43
+ acts_as_holder
44
+ end
45
+ ```
46
+
47
+ From this time on, a User can hold a Ticket with
48
+
49
+ ```ruby
50
+ @user.hold! @ticket
51
+ ```
52
+
53
+ A User can unhold a Ticket with
54
+
55
+ ```ruby
56
+ @holding = @user.hold! @ticket
57
+ @user.unhold! @holding
58
+ ```
59
+
60
+ You can access holdings both from the Holdable and the Holder
61
+
62
+ ```ruby
63
+ @ticket.holdings # return all holdings created on this ticket
64
+ @user.holdings # return all holdings made by this user
65
+ ```
66
+
67
+ ## Configuring Options
68
+
69
+ There are a number available options to make your models behave differently. They are all configurable in the Holdable model, passing a hash to `acts_as_holdable`
70
+
71
+ Available options (with values) are:
72
+
73
+ * `:on_hand_type`: Specifies how the `amount` of a holding (e.g. number of tickets from an event) affects the future availability of the holdable. Allowed values are:
74
+ * `:none`
75
+ * `:open`
76
+
77
+ ### No constraints
78
+
79
+ The model accepts holdings without any constraint. This means every holder can create an infinite number of holdings on it and no capacity or time checks are performed.
80
+
81
+ Creating a holding on this model means holding it forever and without care for other existing holdings. In other words, the number of holdings do not affect the availability of this holdable. (e.g. pre-ordering a product that will be released soon)
82
+
83
+ ### Capacity constraints
84
+
85
+ The option `on_hand_type` may be used to set a constraint over the `amount` attribute of the holding
86
+
87
+ #### No capacity constraints - `on_hand_type: :none`
88
+
89
+ The model is holdable without capacity constraints.
90
+
91
+ ```ruby
92
+ class Product < ActiveRecord::Base
93
+ # As `on_hand_type: :none` is a default and can be ommited
94
+ acts_as_holdable on_hand_type: :none
95
+ end
96
+ ```
97
+
98
+ #### Open capacity - `on_hand_type: :open`
99
+
100
+ > WARNING - **migration needed!** - with this option the model must have an attribute `on_hand: :integer`
101
+
102
+ The model is holdable until its `capacity` is reached. (e.g. an event't tickets types)
103
+
104
+ **Configuration**
105
+
106
+ ```ruby
107
+ class Event < ActiveRecord::Base
108
+ acts_as_holdable on_hand_type: :open
109
+ end
110
+ ```
111
+
112
+ **Creating a new holdable**
113
+
114
+ Each instance of the model must define its capacity.
115
+
116
+ ```ruby
117
+ @ticket = Ticket.new(...)
118
+ @ticket.on_hand = 30 # This event allows 30 tickets
119
+ @ticket.save!
120
+ ```
121
+
122
+ **Holding**
123
+
124
+ ```ruby
125
+ # Holding a model with `on_hand_type: :open` requires `:amount`
126
+
127
+ @user1.hold! @ticket, amount: 5 # holding tickets for 5 people, OK
128
+ @user2.hold! @ticket, amount: 20 # holding tickets for other 20 people, OK
129
+ @user3.hold! @ticket, amount: 10 # overholding! raise ActsAsHoldable::AvailabilityError
130
+ ```
131
+
132
+ ## Holding resources on a span of time
133
+
134
+ Holdings can be created to be 'living' within a span of time, if the holding doesn't gets confirmed within the time span, it will be destroyed.
135
+
136
+
137
+ ```ruby
138
+ @holding = @user.hold_for(@ticket, duration: 10.minutes, amount: 1)
139
+ ```
140
+
141
+ A User can confirm a holding with
142
+
143
+ ```ruby
144
+ @user.confirm_holding!(@holding)
145
+ ```
146
+
147
+
148
+ ## Contributing
149
+ Contribution directions go here.
150
+
151
+ ## License
152
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
153
+
154
+ ### Acknowledgements
155
+
156
+ To speed-up the initialization process of this project, the structure of this repository was strongly influenced by ActsAsBookable by Tandù srl.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'ActsAsHoldable'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
25
+
26
+ require 'rake/testtask'
27
+
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'test'
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = false
32
+ end
33
+
34
+ task default: :test
File without changes
@@ -0,0 +1,10 @@
1
+ en:
2
+ acts_as_holdable:
3
+ errors:
4
+ messages:
5
+ holding:
6
+ must_be_holdable: "cannot hold a %{model} as it\'s not holdable"
7
+ must_be_holder: "a %{model} cannot hold a resource, because %{model} is not a holder"
8
+ availability:
9
+ amount_gt_on_hand: "amount cannot be greater than %{model} on_hand"
10
+ already_held: "the %{model} is full"
@@ -0,0 +1,10 @@
1
+ es-MX:
2
+ acts_as_holdable:
3
+ errors:
4
+ messages:
5
+ holding:
6
+ must_be_holdable: "No se puede apartar %{model} ya que no es apartable"
7
+ must_be_holder: "%{model} no puede apartar un recurso, porque %{model} no es un agente de apartados"
8
+ availability:
9
+ amount_gt_on_hand: "cantidad no puede ser mayor que la capacidad de %{model}"
10
+ already_held: "%{model} está totalmente apartado"
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,12 @@
1
+ class ActsAsHoldableHoldings < ActiveRecord::Migration[5.2]
2
+ def change
3
+ create_table :acts_as_holdable_holdings, force: true do |t|
4
+ t.references :holdable, polymorphic: true, index: { name: "index_acts_as_holdable_holdings_holdable" }
5
+ t.references :holder, polymorphic: true, index: { name: "index_acts_as_holdable_holdings_holder" }
6
+ t.column :amount, :integer
7
+ t.column :job_pid, :string
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_record'
4
+ require 'acts_as_holdable/engine'
5
+ require 'active_job'
6
+ require 'sidekiq-status'
7
+
8
+ module ActsAsHoldable
9
+ extend ActiveSupport::Autoload
10
+
11
+ autoload :T
12
+ autoload :Holdable
13
+ autoload :Holder
14
+ autoload :Holding
15
+ autoload :DbUtils
16
+
17
+ autoload_under 'holdable' do
18
+ autoload :Core
19
+ end
20
+
21
+ class InitializationError < StandardError
22
+ def initialize(model, message)
23
+ super "Error initializing acts_as_holdable on #{model} - " + message
24
+ end
25
+ end
26
+
27
+ class OptionsInvalid < StandardError
28
+ def initialize(model, message)
29
+ super "Error validating options for #{model} - " + message
30
+ end
31
+ end
32
+
33
+ class AvailabilityError < StandardError
34
+ end
35
+ end
36
+
37
+ ActiveSupport.on_load(:active_record) do
38
+ extend ActsAsHoldable::Holdable
39
+ include ActsAsHoldable::Holder
40
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsHoldable
4
+ module DBUtils
5
+ class << self
6
+ def connection
7
+ ActsAsHoldable::Holding.connection
8
+ end
9
+
10
+ def using_postgresql?
11
+ connection && connection.adapter_name == 'PostgreSQL'
12
+ end
13
+
14
+ def using_mysql?
15
+ connection && connection.adapter_name == 'Mysql2'
16
+ end
17
+
18
+ def using_sqlite?
19
+ connection && connection.adapter_name == 'SQLite'
20
+ end
21
+
22
+ def like_operator
23
+ using_postgresql? ? 'ILIKE' : 'LIKE'
24
+ end
25
+
26
+ # escape _ and % characters in strings, since these are wildcards in SQL.
27
+ def escape_like(str)
28
+ str.gsub(/[!%_]/) { |x| '!' + x }
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsHoldable
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsHoldable
4
+ module Holdable
5
+ def holdable?
6
+ false
7
+ end
8
+
9
+ def acts_as_holdable(options = {})
10
+ holdable(options)
11
+ end
12
+
13
+ private
14
+
15
+ # rubocop:disable Metrics/MethodLength
16
+ def holdable(options)
17
+ if holdable?
18
+ self.holding_opts = options
19
+ else
20
+ class_attribute :holding_opts
21
+ self.holding_opts = options
22
+
23
+ class_eval do
24
+ has_many :holdings, as: :holdable, dependent: :destroy, class_name: '::ActsAsHoldable::Holding'
25
+
26
+ validates :on_hand, presence: true, if: :on_hand_required?,
27
+ numericality: { only_integer: true,
28
+ greater_than_or_equal_to: 0 }
29
+
30
+ def self.holdable?
31
+ true
32
+ end
33
+
34
+ def on_hand_required?
35
+ holding_opts && holding_opts[:on_hand_type] != :none
36
+ end
37
+ end
38
+ end
39
+
40
+ include Core
41
+ end
42
+ # rubocop:enable Metrics/MethodLength
43
+ end
44
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsHoldable
4
+ module Holdable
5
+ module Core
6
+ def self.included(base)
7
+ base.extend ActsAsHoldable::Holdable::Core::ClassMethods
8
+ base.include ActsAsHoldable::Holdable::Core::InstanceMethods
9
+
10
+ base.initialize_acts_as_holdable_core
11
+ end
12
+
13
+ module ClassMethods
14
+ def initialize_acts_as_holdable_core
15
+ set_options
16
+ end
17
+
18
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
19
+ def validate_holding_options!(options)
20
+ unpermitted_params = []
21
+ required_params = {}
22
+
23
+ case holding_opts[:on_hand_type]
24
+ when :closed
25
+ required_params[:amount] = [Integer]
26
+ when :open
27
+ required_params[:amount] = [Integer]
28
+ when :none
29
+ unpermitted_params << :amount
30
+ end
31
+
32
+ # Actual validation
33
+ unpermitted_params = unpermitted_params.select { |p| options.key?(p) }
34
+ .map { |p| "'#{p}'" }
35
+
36
+ wrong_types = required_params
37
+ .select { |k, v| options.key?(k) && v.select { |type| options[k].is_a?(type) }.length.zero? }
38
+ .map { |k, v| "'#{k}' must be a '#{v.join(' or ')}' but '#{options[k].class}' found" }
39
+
40
+ required_params = required_params.reject { |k, _v| options.key?(k) }
41
+ .map { |k, _v| "'#{k}'" }
42
+
43
+ # Raise OptionsInvalid if some invalid parameters were found
44
+ if (unpermitted_params.length + required_params.length + wrong_types.length).positive?
45
+ message = ''
46
+ message << " unpermitted parameters: #{unpermitted_params.join(',')}." unless unpermitted_params.empty?
47
+ message << " missing parameters: #{required_params.join(',')}." unless required_params.empty?
48
+ message << " parameters type mismatch: #{wrong_types.join(',')}" unless wrong_types.empty?
49
+ raise ActsAsHoldable::OptionsInvalid.new(self, message)
50
+ end
51
+ true
52
+ end
53
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
54
+
55
+ private
56
+
57
+ # rubocop:disable Metrics/MethodLength
58
+ def set_options
59
+ holding_opts[:preset] = :ticket
60
+
61
+ # Validates options
62
+ permitted_options = {
63
+ on_hand_type: %i[open closed none],
64
+ preset: [:ticket]
65
+ }
66
+
67
+ holding_opts.each_pair do |key, val|
68
+ # rubocop:disable Style/GuardClause
69
+ if !permitted_options.key? key
70
+ raise ActsAsHoldable::InitializationError.new(self, "#{key} is not a valid option")
71
+ elsif !permitted_options[key].include? val
72
+ raise ActsAsHoldable::InitializationError.new(self, "#{val} is not a valid value for #{key}. Allowed values: #{permitted_options[key]}")
73
+ end
74
+ # rubocop:enable Style/GuardClause
75
+ end
76
+
77
+ defaults = case holding_opts[:preset]
78
+ when :ticket
79
+ {
80
+ on_hand_type: :open
81
+ }
82
+ else
83
+ {
84
+ on_hand_type: :none
85
+ }
86
+ end
87
+
88
+ # Merge options with defaults
89
+ holding_opts.reverse_merge!(defaults)
90
+ end
91
+ # rubocop:enable Metrics/MethodLength
92
+ end
93
+
94
+ module InstanceMethods
95
+ def check_availability!(opts)
96
+ # validates options
97
+ validate_holding_options!(opts)
98
+
99
+ if holding_opts[:on_hand_type] != :none
100
+ # Amount > on_hand
101
+ if opts[:amount] > on_hand
102
+ raise ActsAsHoldable::AvailabilityError,
103
+ ActsAsHoldable::T.er('.availability.amount_gt_on_hand', model: self.class.to_s)
104
+ end
105
+ end
106
+ true
107
+ end
108
+
109
+ def check_availability(opts)
110
+ check_availability!(opts)
111
+ rescue ActsAsHoldable::AvailabilityError
112
+ false
113
+ end
114
+
115
+ def hold!(holder, opts)
116
+ holder.hold(self, opts)
117
+ end
118
+
119
+ def validate_holding_options!(opts)
120
+ self.class.validate_holding_options!(opts)
121
+ end
122
+
123
+ def holder?
124
+ self.class.holder?
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jobs/unhold_job'
4
+ module ActsAsHoldable
5
+ module Holder
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def acts_as_holder(_opts = {})
12
+ class_eval do
13
+ has_many :holdings, as: :holder, dependent: :destroy, class_name: '::ActsAsHoldable::Holding'
14
+ end
15
+
16
+ include ActsAsHoldable::Holder::InstanceMethods
17
+ extend ActsAsHoldable::Holder::SingletonMethods
18
+ end
19
+
20
+ def holder?
21
+ false
22
+ end
23
+ end
24
+
25
+ module InstanceMethods
26
+ def hold!(holdable, opts = {})
27
+ # validates availability
28
+ holdable.check_availability!(opts) if holdable.class.holdable?
29
+
30
+ holding_params = opts.merge(holder: self, holdable: holdable)
31
+ holding = ActsAsHoldable::Holding.create!(holding_params)
32
+
33
+ holdable.reload
34
+ holding
35
+ end
36
+
37
+ def unhold!(holding)
38
+ # deletes current holding
39
+ ActsAsHoldable::Holding.destroy(holding.id)
40
+ reload
41
+ true
42
+ end
43
+
44
+ def hold_for(holdable, opts = {})
45
+ # Clean params for Bookings
46
+ holding_opts = opts.except(:duration)
47
+
48
+ # Creates the Booking
49
+ holding = hold!(holdable, holding_opts)
50
+
51
+ # Sets a Job to delete the Booking
52
+ job = UnholdJob.set(wait: opts[:duration]).perform_later(holding.id)
53
+ holding.update(job_pid: job.provider_job_id)
54
+ holding
55
+ end
56
+
57
+ def confirm_holding!(holding)
58
+ return false unless holding.job_pid
59
+ Sidekiq::Status.unschedule(holding.job_pid)
60
+ holding.update(job_pid: nil)
61
+ holding
62
+ end
63
+
64
+ def holder?
65
+ self.class.holder?
66
+ end
67
+ end
68
+
69
+ module SingletonMethods
70
+ def holder?
71
+ true
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsHoldable
4
+ # Holding model. Store in host database holdings made by holders on holdables
5
+ class Holding < ::ActiveRecord::Base
6
+ self.table_name = 'acts_as_holdable_holdings'
7
+
8
+ belongs_to :holdable, polymorphic: true
9
+ belongs_to :holder, polymorphic: true
10
+
11
+ validates_presence_of :holdable
12
+ validates_presence_of :holder
13
+
14
+ validate :holdable_is_holdable,
15
+ :holder_is_holder
16
+
17
+ validates :amount, presence: true, numericality: { only_integer: true,
18
+ greater_than_or_equal_to: 0 }
19
+
20
+ private
21
+
22
+ # Validation method. Check if the holded model is actually holdable
23
+ def holdable_is_holdable
24
+ return unless holdable.present? && !holdable.class.holdable?
25
+ errors.add(:holdable, T.er('holding.must_be_holdable',
26
+ model: holdable.class.to_s))
27
+ end
28
+
29
+ # Validation method. Check if the holder model is actually a holder
30
+ def holder_is_holder
31
+ return unless holder.present? && !holder.class.holder?
32
+ errors.add(:holder, T.er('holder.must_be_holder',
33
+ model: holder.class.to_s))
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsHoldable
4
+ module T
5
+ def self.t(message, opts = {})
6
+ I18n.t('.acts_as_holdable.' + message, opts)
7
+ end
8
+
9
+ def self.er(message, opts = {})
10
+ t('errors.messages.' + message, opts)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActsAsHoldable
4
+ VERSION = '0.1.1'
5
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class UnholdJob < ActiveJob::Base
4
+ discard_on ActiveRecord::RecordNotFound
5
+ queue_as :default
6
+
7
+ def perform(holding_id)
8
+ ActsAsHoldable::Holding.destroy(holding_id) if booking_exists?(holding_id)
9
+ end
10
+
11
+ private
12
+
13
+ def booking_exists?(holding_id)
14
+ ActsAsHoldable::Holding.find_by(id: holding_id)
15
+ end
16
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # desc "Explaining what the task does"
4
+ # task :acts_as_holdable do
5
+ # # Task goes here
6
+ # end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # desc "Explaining what the task does"
4
+ # task :acts_as_holdable do
5
+ # # Task goes here
6
+ # end
metadata ADDED
@@ -0,0 +1,203 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: acts_as_holdable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Nora Gabriela Alvarado
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: sidekiq
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sidekiq-status
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bump
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: combustion
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: coveralls
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: sqlite3
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: Description of ActsAsHoldable.
154
+ email:
155
+ - noragmora@gmail.com
156
+ executables: []
157
+ extensions: []
158
+ extra_rdoc_files: []
159
+ files:
160
+ - README.md
161
+ - Rakefile
162
+ - app/assets/config/acts_as_booked_manifest.js
163
+ - config/locales/en.yml
164
+ - config/locales/es.yml
165
+ - config/routes.rb
166
+ - db/migrate/20180530211641_acts_as_holdable_holdings.rb
167
+ - lib/acts_as_holdable.rb
168
+ - lib/acts_as_holdable/db_utils.rb
169
+ - lib/acts_as_holdable/engine.rb
170
+ - lib/acts_as_holdable/holdable.rb
171
+ - lib/acts_as_holdable/holdable/core.rb
172
+ - lib/acts_as_holdable/holder.rb
173
+ - lib/acts_as_holdable/holding.rb
174
+ - lib/acts_as_holdable/t.rb
175
+ - lib/acts_as_holdable/version.rb
176
+ - lib/jobs/unhold_job.rb
177
+ - lib/tasks/acts_as_booked_tasks.rake
178
+ - lib/tasks/acts_as_holdable_tasks.rake
179
+ homepage: https://gitlab.com/aromaron/acts_as_holdable
180
+ licenses:
181
+ - MIT
182
+ metadata: {}
183
+ post_install_message:
184
+ rdoc_options: []
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements: []
198
+ rubyforge_project:
199
+ rubygems_version: 2.7.6
200
+ signing_key:
201
+ specification_version: 4
202
+ summary: Summary of ActsAsHoldable.
203
+ test_files: []