Ricardo Garcia

Adds rspec for gem TDD

1 source 'https://rubygems.org' 1 source 'https://rubygems.org'
2 2
3 -# Specify your gem's dependencies in var.gemspec 3 +group :development do
4 + gem "sqlite3"
5 +end
6 +
4 gemspec 7 gemspec
......
1 +require 'rspec/core/rake_task'
1 require 'bundler/gem_tasks' 2 require 'bundler/gem_tasks'
3 +
4 +# Default directory to look in is `/specs`
5 +# # Run with `rake spec`
6 +RSpec::Core::RakeTask.new(:spec) do |task|
7 + task.rspec_opts = ['--color', '--format d']
8 + task.verbose = false
9 +end
10 +
11 +task default: :spec
......
1 #!/usr/bin/env ruby 1 #!/usr/bin/env ruby
2 2
3 require 'bundler/setup' 3 require 'bundler/setup'
4 +require 'active_record'
5 +require 'active_support'
6 +require_relative '../spec/mocks/var_database_mock.rb'
7 +require_relative '../spec/mocks/var_models_mock.rb'
4 require 'var' 8 require 'var'
5 9
6 -# You can add fixtures and/or initialization code here to make experimenting 10 +ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
7 -# with your gem easier. You can also use a different console, if you like. 11 +ActiveRecord::Schema.verbose = false
12 +VarDatabaseMock.setup_db
8 13
9 -# (If you use this, don't forget to add pry to your Gemfile!) 14 +require "pry"
10 -# require "pry" 15 +Pry.start
11 -# Pry.start
12 -
13 -require 'irb'
14 -IRB.start
......
1 +module ActsAsChargeable
2 + extend ActiveSupport::Concern
3 + # Class Methods
4 + module ClassMethods
5 + def acts_as_chargeable(keys = {})
6 + include ChargeableInstanceMethods
7 + cattr_accessor :sync_attributes
8 + self.sync_attributes = keys
9 + Var.add_var_class(self)
10 + end
11 + end
12 + # Instance Methods
13 + module ChargeableInstanceMethods
14 + def charge_with(service, options)
15 + unless instance_support?(service)
16 + error_message = "#{self.class} doesn't support" \
17 + " charges with #{service}"
18 + return { error_message: error_message }
19 + end
20 + send("charge_with_#{service}", options)
21 + rescue Exception => exception
22 + { error_message: exception.message }
23 + end
24 +
25 + def charge_with_conekta(options)
26 + charge = conekta_charge(options)
27 + amount = charge.amount / 100.0
28 + fee = charge.fee / 100.0
29 + update_columns(var_status: charge.status, var_id: charge.id,
30 + var_service: 'conekta', var_fee: fee,
31 + var_paid_amount: amount,
32 + var_payment_method: charge.payment_method.object)
33 + update_conekta_barcode(charge) if options[:conekta_type] == 'oxxo'
34 + if var_payed?
35 + time = Time.zone.now
36 + update_columns(var_payment_at: time)
37 + end
38 + charge
39 + rescue Conekta::ParameterValidationError, Conekta::ProcessingError,
40 + Conekta::Error => e
41 + update_columns(var_status: 'failed')
42 + { error_message: e.message }
43 + end
44 +
45 + def conekta_charge(options)
46 + @charge ||= Conekta::Charge.create({
47 + description: sync(:conekta, 'description'),
48 + amount: sync(:conekta, 'amount'), currency: 'MXN',
49 + reference_id: sync(:conekta, 'reference_id'),
50 + details: {
51 + name: sync(:conekta, 'name'), email: sync(:conekta, 'email'),
52 + line_items: [{
53 + description: sync(:conekta, 'description'), quantity: 1,
54 + unit_price: sync(:conekta, 'amount'), name: sync(:conekta, 'name')
55 + }] }
56 + }.merge(conekta_type_of_charge(options)))
57 + end
58 +
59 + def conekta_type_of_charge(options)
60 + if options[:conekta_type] == 'card'
61 + { card: options[:card_token] }
62 + elsif options[:conekta_type] == 'oxxo'
63 + { cash: { type: 'oxxo',
64 + expires_at: (Time.zone.today + 3.days).strftime('%Y-%m-%d') }
65 + }
66 + end
67 + end
68 +
69 + def update_conekta_barcode(charge)
70 + method = charge.payment_method
71 + update_columns(var_barcode: method.barcode,
72 + var_barcode_url: method.barcode_url,
73 + var_payment_expires_at: Time.at(method.expires_at))
74 + end
75 +
76 + def manual_charge
77 + update_columns(var_service: 'manual', var_status: 'paid',
78 + var_payment_at: Time.zone.now)
79 + # TODO: Create a new table with transaction
80 + end
81 +
82 + def manual_discharge
83 + update_columns(var_service: 'manual', var_status: 'pending',
84 + var_payment_at: nil)
85 + # TODO: Create a new table with transaction
86 + end
87 +
88 + # def charge_with_paypal(options)
89 + # if(!options.include? :card)
90 + # error_message = "Paypal needs a card sent as a third paramater"
91 + # return { error_message: error_message}
92 + # end
93 + # @payment = PayPal::SDK::REST::Payment.new({
94 + # intent: "sale",
95 + # payer: {
96 + # payer_info: {
97 + # email: self.sync(:paypal, 'email')},
98 + # payment_method: "credit_card",
99 + # funding_instruments: [{
100 + # credit_card: {
101 + # type: options[:card][:type],
102 + # number: options[:card][:number],
103 + # expire_month: options[:card][:expire_month],
104 + # expire_year: options[:card][:expire_year],
105 + # cvv2: options[:card][:cvv2]}}]},
106 + # transactions: [{
107 + # item_list: {
108 + # items: [{
109 + # name: self.sync(:paypal, 'name'),
110 + # sku: self.sync(:paypal, 'sku'),
111 + # price: self.sync(:paypal, 'price'),
112 + # currency: "MXN",
113 + # quantity: 1 }]},
114 + # amount: {
115 + # total: self.sync(:paypal, 'price'),
116 + # currency: "MXN" },
117 + # description: self.sync(:paypal, 'description') }]})
118 + # end
119 +
120 + def find_charge
121 + return { error_message: 'Not charged yet' } unless var_service
122 + send("find_#{var_service}_charge")
123 + rescue Exception => exception
124 + { error_message: exception.message }
125 + end
126 +
127 + def charged?(service)
128 + charge = find_charge(service)
129 + charge.any? && !charge.include?(:error_message)
130 + end
131 +
132 + def find_conekta_charge
133 + Conekta::Charge.find(var_id)
134 + end
135 +
136 + def instance_support?(service)
137 + sync_attributes.include?(service)
138 + end
139 +
140 + def sync(service, key)
141 + service_attributes = send("#{service}_attributes")
142 + return send(key) unless service_attributes.include? key.to_sym
143 + send(service_attributes[key.to_sym])
144 + end
145 +
146 + def conekta_attributes
147 + sync_attributes[:conekta] || {}
148 + end
149 +
150 + def paypal_attributes
151 + sync_attributes[:paypal] || {}
152 + end
153 +
154 + def var_payed?
155 + var_status == 'paid'
156 + end
157 +
158 + def cancel_oxxo_payment
159 + return { error_message: 'Already paid' } if var_payed?
160 + return { error_message: 'Not charged yet' } unless var_service
161 + return { error_message: 'Not charged with oxxo' } unless var_barcode
162 + charge = find_charge
163 + if charge.status == 'paid'
164 + update_columns(var_status: 'paid',
165 + var_payment_at: Time.zone.at(charge.paid_at))
166 + return { error_message: 'Already paid' }
167 + end
168 + clean_var_variables
169 + end
170 +
171 + def var_expired_by(time)
172 + return false unless var_payment_expires_at
173 + var_payment_expires_at + time < Time.zone.now && !var_payed?
174 + end
175 +
176 + private
177 +
178 + def clean_var_variables
179 + if update_columns(var_status: nil, var_barcode: nil, var_barcode_url: nil,
180 + var_id: nil, var_service: nil, var_payment_at: nil,
181 + var_payment_expires_at: nil)
182 + { object: code }
183 + else
184 + { error_message: 'Something went wrong' }
185 + end
186 + end
187 + end
188 +end
...@@ -7,9 +7,9 @@ class AddVarStatusTo<%=@model_name.pluralize.camelize%> < ActiveRecord::Migratio ...@@ -7,9 +7,9 @@ class AddVarStatusTo<%=@model_name.pluralize.camelize%> < ActiveRecord::Migratio
7 add_column :<%=@model_name.pluralize.underscore%>, :var_payment_expires_at, :datetime 7 add_column :<%=@model_name.pluralize.underscore%>, :var_payment_expires_at, :datetime
8 add_column :<%=@model_name.pluralize.underscore%>, :var_id, :string 8 add_column :<%=@model_name.pluralize.underscore%>, :var_id, :string
9 add_column :<%=@model_name.pluralize.underscore%>, :var_service, :string 9 add_column :<%=@model_name.pluralize.underscore%>, :var_service, :string
10 - add_column :<%=model_name.pluralize.underscore%>, :var_fee, :float 10 + add_column :<%=@model_name.pluralize.underscore%>, :var_fee, :float
11 - add_column :<%=model_name.pluralize.underscore%>, :var_paid_amount, :float 11 + add_column :<%=@model_name.pluralize.underscore%>, :var_paid_amount, :float
12 - add_column :<%=model_name.pluralize.underscore%>, :var_payment_at, :datetime 12 + add_column :<%=@model_name.pluralize.underscore%>, :var_payment_at, :datetime
13 - add_column :<%=model_name.pluralize.underscore%>, :var_payment_method, :string 13 + add_column :<%=@model_name.pluralize.underscore%>, :var_payment_method, :string
14 end 14 end
15 end 15 end
......
...@@ -8,6 +8,6 @@ class VarModelGenerator < Rails::Generators::NamedBase ...@@ -8,6 +8,6 @@ class VarModelGenerator < Rails::Generators::NamedBase
8 d = Time.now.strftime('%Y%m%d%H%M%S') 8 d = Time.now.strftime('%Y%m%d%H%M%S')
9 @model_name = name 9 @model_name = name
10 template 'var_model.rb', 10 template 'var_model.rb',
11 - "db/migrate/#{d}_add_var_status_to_#{name.pluralize.underscore}.rb" 11 + "db/migrate/#{d}_add_var_attributes_to_#{name.pluralize.underscore}.rb"
12 end 12 end
13 end 13 end
......
1 require 'var/version' 1 require 'var/version'
2 require 'conekta' 2 require 'conekta'
3 require 'paypal-sdk-rest' 3 require 'paypal-sdk-rest'
4 +require 'acts_as_chargeable'
4 5
5 # Main Module 6 # Main Module
6 module Var 7 module Var
7 - # TODO: add paypal 8 +
8 VALID_SERVICES = [:conekta] 9 VALID_SERVICES = [:conekta]
9 @@var_classes = [] 10 @@var_classes = []
10 11
11 - def self.valid_services 12 + class << self
13 +
14 + def valid_services
12 VALID_SERVICES 15 VALID_SERVICES
13 end 16 end
14 17
15 - def self.var_classes 18 + def var_classes
16 @@var_classes 19 @@var_classes
17 end 20 end
18 21
19 - def self.add_var_class(class_name) 22 + def add_var_class(class_name)
20 @@var_classes << class_name unless @@var_classes.include? class_name 23 @@var_classes << class_name unless @@var_classes.include? class_name
21 end 24 end
22 25
23 - def self.create_charge(service, object, options = {}) 26 + def create_charge(service, object, options = {})
24 return { error_message: 'Service is not supported' } unless VALID_SERVICES.include? service 27 return { error_message: 'Service is not supported' } unless VALID_SERVICES.include? service
25 return { error_message: "#{object.class} doesn't support charges" } unless object.respond_to?(:charge_with) 28 return { error_message: "#{object.class} doesn't support charges" } unless object.respond_to?(:charge_with)
26 charge = object.charge_with(service, options) 29 charge = object.charge_with(service, options)
27 charge 30 charge
28 end 31 end
29 32
30 - def self.conekta_webhook(params) 33 + def conekta_webhook(params)
31 payment = params[:data][:object] 34 payment = params[:data][:object]
32 object = Var.find_charge payment[:id] 35 object = Var.find_charge payment[:id]
33 object.update_columns(var_status: payment[:status]) 36 object.update_columns(var_status: payment[:status])
...@@ -35,7 +38,7 @@ module Var ...@@ -35,7 +38,7 @@ module Var
35 time = Time.strptime payment[:paid_at].to_s, '%s' 38 time = Time.strptime payment[:paid_at].to_s, '%s'
36 amount = payment[:amount].to_f / 100.0 39 amount = payment[:amount].to_f / 100.0
37 fee = payment[:fee].to_f / 100.0 40 fee = payment[:fee].to_f / 100.0
38 - object.update({var_fee: fee, var_paid_amount: amount, var_payment_method: payment[:payment_method][:object], var_payment_at: time}) 41 + object.update(var_fee: fee, var_paid_amount: amount, var_payment_method: payment[:payment_method][:object], var_payment_at: time)
39 end 42 end
40 object 43 object
41 rescue Exception => exception 44 rescue Exception => exception
...@@ -43,200 +46,12 @@ module Var ...@@ -43,200 +46,12 @@ module Var
43 false 46 false
44 end 47 end
45 48
46 - def self.find_charge(id) 49 + def find_charge(id)
47 Rails.application.eager_load! if Rails.env.development? 50 Rails.application.eager_load! if Rails.env.development?
48 @@var_classes.map do |class_name| 51 @@var_classes.map do |class_name|
49 class_name.where(var_id: id) 52 class_name.where(var_id: id)
50 end.flatten.first 53 end.flatten.first
51 end 54 end
52 -end
53 -# Module for models
54 -module ActsAsChargeable
55 - extend ActiveSupport::Concern
56 - # Class Methods
57 - module ClassMethods
58 - def acts_as_chargeable(keys = {})
59 - include ChargeableInstanceMethods
60 - cattr_accessor :sync_attributes
61 - self.sync_attributes = keys
62 - Var.add_var_class(self)
63 - end
64 - end
65 - # Instance Methods
66 - module ChargeableInstanceMethods
67 - def charge_with(service, options)
68 - unless instance_support?(service)
69 - error_message = "#{self.class} doesn't support" \
70 - " charges with #{service}"
71 - return { error_message: error_message }
72 - end
73 - send("charge_with_#{service}", options)
74 - rescue Exception => exception
75 - { error_message: exception.message }
76 - end
77 -
78 - def charge_with_conekta(options)
79 - charge = conekta_charge(options)
80 - amount = charge.amount / 100.0
81 - fee = charge.fee / 100.0
82 - update_columns(var_status: charge.status, var_id: charge.id,
83 - var_service: 'conekta', var_fee: fee,
84 - var_paid_amount: amount,
85 - var_payment_method: charge.payment_method.object)
86 - update_conekta_barcode(charge) if options[:conekta_type] == 'oxxo'
87 - if var_payed?
88 - time = Time.zone.now
89 - update_columns(var_payment_at: time)
90 - end
91 - charge
92 - rescue Conekta::ParameterValidationError, Conekta::ProcessingError,
93 - Conekta::Error => e
94 - update_columns(var_status: 'failed')
95 - { error_message: e.message }
96 - end
97 -
98 - def conekta_charge(options)
99 - @charge ||= Conekta::Charge.create({
100 - description: sync(:conekta, 'description'),
101 - amount: sync(:conekta, 'amount'), currency: 'MXN',
102 - reference_id: sync(:conekta, 'reference_id'),
103 - details: {
104 - name: sync(:conekta, 'name'), email: sync(:conekta, 'email'),
105 - line_items: [{
106 - description: sync(:conekta, 'description'), quantity: 1,
107 - unit_price: sync(:conekta, 'amount'), name: sync(:conekta, 'name')
108 - }] }
109 - }.merge(conekta_type_of_charge(options)))
110 - end
111 -
112 - def conekta_type_of_charge(options)
113 - if options[:conekta_type] == 'card'
114 - { card: options[:card_token] }
115 - elsif options[:conekta_type] == 'oxxo'
116 - { cash: { type: 'oxxo',
117 - expires_at: (Time.zone.today + 3.days).strftime('%Y-%m-%d') }
118 - }
119 - end
120 - end
121 -
122 - def update_conekta_barcode(charge)
123 - method = charge.payment_method
124 - update_columns(var_barcode: method.barcode,
125 - var_barcode_url: method.barcode_url,
126 - var_payment_expires_at: Time.at(method.expires_at))
127 - end
128 -
129 - def manual_charge
130 - update_columns(var_service: 'manual', var_status: 'paid',
131 - var_payment_at: Time.zone.now)
132 - # TODO: Create a new table with transaction
133 - end
134 -
135 - def manual_discharge
136 - update_columns(var_service: 'manual', var_status: 'pending',
137 - var_payment_at: nil)
138 - # TODO: Create a new table with transaction
139 - end
140 -
141 - # def charge_with_paypal(options)
142 - # if(!options.include? :card)
143 - # error_message = "Paypal needs a card sent as a third paramater"
144 - # return { error_message: error_message}
145 - # end
146 - # @payment = PayPal::SDK::REST::Payment.new({
147 - # intent: "sale",
148 - # payer: {
149 - # payer_info: {
150 - # email: self.sync(:paypal, 'email')},
151 - # payment_method: "credit_card",
152 - # funding_instruments: [{
153 - # credit_card: {
154 - # type: options[:card][:type],
155 - # number: options[:card][:number],
156 - # expire_month: options[:card][:expire_month],
157 - # expire_year: options[:card][:expire_year],
158 - # cvv2: options[:card][:cvv2]}}]},
159 - # transactions: [{
160 - # item_list: {
161 - # items: [{
162 - # name: self.sync(:paypal, 'name'),
163 - # sku: self.sync(:paypal, 'sku'),
164 - # price: self.sync(:paypal, 'price'),
165 - # currency: "MXN",
166 - # quantity: 1 }]},
167 - # amount: {
168 - # total: self.sync(:paypal, 'price'),
169 - # currency: "MXN" },
170 - # description: self.sync(:paypal, 'description') }]})
171 - # end
172 -
173 - def find_charge
174 - return { error_message: 'Not charged yet' } unless var_service
175 - send("find_#{var_service}_charge")
176 - rescue Exception => exception
177 - { error_message: exception.message }
178 - end
179 -
180 - def charged?(service)
181 - charge = find_charge(service)
182 - charge.any? && !charge.include?(:error_message)
183 - end
184 -
185 - def find_conekta_charge
186 - Conekta::Charge.find(var_id)
187 - end
188 -
189 - def instance_support?(service)
190 - sync_attributes.include?(service)
191 - end
192 -
193 - def sync(service, key)
194 - service_attributes = send("#{service}_attributes")
195 - return send(key) unless service_attributes.include? key.to_sym
196 - send(service_attributes[key.to_sym])
197 - end
198 -
199 - def conekta_attributes
200 - sync_attributes[:conekta] || {}
201 - end
202 -
203 - def paypal_attributes
204 - sync_attributes[:paypal] || {}
205 - end
206 -
207 - def var_payed?
208 - var_status == 'paid'
209 - end
210 -
211 - def cancel_oxxo_payment
212 - return { error_message: 'Already paid' } if var_payed?
213 - return { error_message: 'Not charged yet' } unless var_service
214 - return { error_message: 'Not charged with oxxo' } unless var_barcode
215 - charge = find_charge
216 - if charge.status == 'paid'
217 - update_columns(var_status: 'paid',
218 - var_payment_at: Time.zone.at(charge.paid_at))
219 - return { error_message: 'Already paid' }
220 - end
221 - clean_var_variables
222 - end
223 -
224 - def var_expired_by(time)
225 - return false unless var_payment_expires_at
226 - return var_payment_expires_at + time < Time.zone.now && !var_payed?
227 - end
228 -
229 - private
230 -
231 - def clean_var_variables
232 - if update_columns(var_status: nil, var_barcode: nil, var_barcode_url: nil,
233 - var_id: nil, var_service: nil, var_payment_at: nil,
234 - var_payment_expires_at: nil)
235 - { object: code }
236 - else
237 - { error_message: 'Something went wrong' }
238 - end
239 - end
240 end 55 end
241 end 56 end
242 57
......
1 # Var Version 1 # Var Version
2 module Var 2 module Var
3 - VERSION = '0.2.22' 3 + VERSION = '0.3.0'
4 end 4 end
......
1 +class VarDatabaseMock
2 + class << self
3 + def setup_db
4 + ActiveRecord::Schema.define(version: 1) do
5 + create_table :products do |t|
6 + t.string :code
7 +
8 + t.string :var_status, default: 'pending'
9 + t.string :var_barcode_url
10 + t.string :var_barcode
11 + t.datetime :var_payment
12 + t.datetime :var_payment_expires_at
13 + t.string :var_id
14 + t.string :var_service
15 + t.float :var_fee
16 + t.float :var_paid_amount
17 + t.datetime :var_payment_at
18 + t.string :var_payment_method
19 +
20 + t.timestamps null: false
21 + end
22 + end
23 + end
24 + end
25 +end
1 +class Product < ActiveRecord::Base
2 +end
1 +require 'pry'
2 +require 'active_record'
3 +require 'active_support'
4 +require 'mocks/var_database_mock'
5 +require 'mocks/var_models_mock'
6 +require 'Var'
7 +
8 +ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
9 +ActiveRecord::Schema.verbose = false
10 +VarDatabaseMock.setup_db
1 +require 'spec_helper'
2 +
3 +describe Var do
4 + context 'as a class' do
5 + it { is_expected.to respond_to(:valid_services) }
6 + it { is_expected.to respond_to(:var_classes) }
7 + it { is_expected.to respond_to(:add_var_class) }
8 + it { is_expected.to respond_to(:create_charge) }
9 + it { is_expected.to respond_to(:conekta_webhook) }
10 + it { is_expected.to respond_to(:find_charge) }
11 +
12 + it 'returns conekta as a valid service' do
13 + expect(Var.valid_services).to eq([:conekta])
14 + end
15 + end
16 +end
1 -# coding: utf-8
2 lib = File.expand_path('../lib', __FILE__) 1 lib = File.expand_path('../lib', __FILE__)
3 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 2 $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4 require 'var/version' 3 require 'var/version'
...@@ -6,12 +5,12 @@ require 'var/version' ...@@ -6,12 +5,12 @@ require 'var/version'
6 Gem::Specification.new do |spec| 5 Gem::Specification.new do |spec|
7 spec.name = 'var' 6 spec.name = 'var'
8 spec.version = Var::VERSION 7 spec.version = Var::VERSION
9 - spec.authors = ['abrahamrq'] 8 + spec.authors = ['abrahamrq', 'chelord', 'rgp']
10 spec.email = ['abraham.rq03@gmail.com'] 9 spec.email = ['abraham.rq03@gmail.com']
11 10
12 - spec.summary = 'summary of var' 11 + spec.summary = 'Payment gateway for Conekta, Paypal'
13 - spec.description = 'description of var' 12 + spec.description = 'Just another Payment Gateway for Conekta and Paypal and stuff for rails apps'
14 - spec.homepage = 'https://github.com/abrahamrq' 13 + spec.homepage = 'http://git.ukko.mx/gems/var'
15 14
16 # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or 15 # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
17 # delete this section to allow pushing this gem to any host. 16 # delete this section to allow pushing this gem to any host.
...@@ -28,6 +27,16 @@ Gem::Specification.new do |spec| ...@@ -28,6 +27,16 @@ Gem::Specification.new do |spec|
28 27
29 spec.add_dependency 'conekta', '~> 0.5' 28 spec.add_dependency 'conekta', '~> 0.5'
30 spec.add_dependency 'paypal-sdk-rest' 29 spec.add_dependency 'paypal-sdk-rest'
30 + spec.add_dependency 'activerecord', '>= 4.0', '< 5.1'
31 + spec.add_dependency "activesupport"
31 spec.add_development_dependency 'bundler', '~> 1.10.a' 32 spec.add_development_dependency 'bundler', '~> 1.10.a'
32 spec.add_development_dependency 'rake', '~> 10.0' 33 spec.add_development_dependency 'rake', '~> 10.0'
34 +
35 + spec.add_development_dependency "rspec"
36 + spec.add_development_dependency "rspec-nc"
37 + spec.add_development_dependency "guard"
38 + spec.add_development_dependency "guard-rspec"
39 + spec.add_development_dependency "pry"
40 + spec.add_development_dependency "pry-remote"
41 + spec.add_development_dependency "pry-nav"
33 end 42 end
......