boba 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/boba/active_record/attribute_service.rb +38 -0
- data/lib/boba/active_record/reflection_service.rb +69 -0
- data/lib/boba/version.rb +1 -1
- data/lib/tapioca/dsl/compilers/active_record_associations_persisted.rb +5 -28
- data/lib/tapioca/dsl/compilers/active_record_columns_persisted.rb +11 -27
- data/lib/tapioca/dsl/compilers/attr_json.rb +132 -0
- data/lib/tapioca/dsl/compilers/paperclip.rb +77 -0
- data/lib/tapioca/dsl/compilers/state_machines_extended.rb +5 -0
- metadata +11 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7db0fba3021c2e619b8ba2a5e8d7a19173a4b66fc63f16f72ca6e3bc140ed5b
|
4
|
+
data.tar.gz: c655306579accf7b2d76bdd5e4643a16926dddb2d1bf9cf48a1a065134f11c6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f24939c06e8e727a21c0c30d9d881493ef7de1acb88fe292384c457c73df059ae68a24c3a3a615873da40ea4d646e0fd9cb62379dc6c026ca553b47a6655ff3
|
7
|
+
data.tar.gz: 3c0f1b1931937e93a274513e47954d67605203edc9fceee6552a1aac8121af7a71bc7469e11be1a63c222fe81ba1e6b3759d415d1cc0a49bf881fb009518c24c
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Boba
|
5
|
+
module ActiveRecord
|
6
|
+
module AttributeService
|
7
|
+
class << self
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(constant: T.class_of(::ActiveRecord::Base), attribute: String).returns(T::Boolean) }
|
11
|
+
def has_unconditional_presence_validator?(constant, attribute)
|
12
|
+
return false unless constant.respond_to?(:validators_on)
|
13
|
+
|
14
|
+
constant.validators_on(attribute).any? do |validator|
|
15
|
+
next false unless validator.is_a?(::ActiveRecord::Validations::PresenceValidator)
|
16
|
+
|
17
|
+
!validator.options.key?(:if) && !validator.options.key?(:unless) && !validator.options.key?(:on)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
sig { params(constant: T.class_of(::ActiveRecord::Base), column_name: String).returns(T::Boolean) }
|
22
|
+
def has_non_null_database_constraint?(constant, column_name)
|
23
|
+
column = constant.columns_hash[column_name]
|
24
|
+
return false if column.nil?
|
25
|
+
|
26
|
+
!column.null
|
27
|
+
rescue StandardError
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
sig { params(constant: T.class_of(::ActiveRecord::Base), column_name: String).returns(T::Boolean) }
|
32
|
+
def virtual_attribute?(constant, column_name)
|
33
|
+
constant.columns_hash[column_name].nil?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative("attribute_service")
|
5
|
+
|
6
|
+
module Boba
|
7
|
+
module ActiveRecord
|
8
|
+
module ReflectionService
|
9
|
+
class << self
|
10
|
+
extend T::Sig
|
11
|
+
|
12
|
+
ReflectionType = T.type_alias do
|
13
|
+
T.any(::ActiveRecord::Reflection::ThroughReflection, ::ActiveRecord::Reflection::AssociationReflection)
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
17
|
+
def has_one_and_required_reflection?(reflection)
|
18
|
+
return false unless reflection.has_one?
|
19
|
+
return true if !!reflection.options[:required]
|
20
|
+
return true if reflection_required_by_database_constraint?(reflection)
|
21
|
+
|
22
|
+
reflection_required_by_validation?(reflection)
|
23
|
+
end
|
24
|
+
|
25
|
+
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
26
|
+
def belongs_to_and_non_optional_reflection?(reflection)
|
27
|
+
return false unless reflection.belongs_to?
|
28
|
+
|
29
|
+
optional = if reflection.options.key?(:required)
|
30
|
+
!reflection.options[:required]
|
31
|
+
else
|
32
|
+
reflection.options[:optional]
|
33
|
+
end
|
34
|
+
return !optional unless optional.nil?
|
35
|
+
return true if reflection_required_by_database_constraint?(reflection)
|
36
|
+
return true if reflection_required_by_validation?(reflection)
|
37
|
+
|
38
|
+
# nothing defined, so fall back to the default active record config
|
39
|
+
!!reflection.active_record.belongs_to_required_by_default
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
# check for non-nullable database constraint on the foreign key
|
45
|
+
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
46
|
+
def reflection_required_by_database_constraint?(reflection)
|
47
|
+
Boba::ActiveRecord::AttributeService.has_non_null_database_constraint?(
|
48
|
+
reflection.active_record,
|
49
|
+
reflection.foreign_key,
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
# check for presence validator on the foreign key or on the association
|
54
|
+
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
55
|
+
def reflection_required_by_validation?(reflection)
|
56
|
+
return true if Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
|
57
|
+
reflection.active_record,
|
58
|
+
reflection.foreign_key,
|
59
|
+
)
|
60
|
+
|
61
|
+
Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
|
62
|
+
reflection.active_record,
|
63
|
+
reflection.name,
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/boba/version.rb
CHANGED
@@ -5,6 +5,8 @@ require "tapioca/dsl/compilers/active_record_associations"
|
|
5
5
|
|
6
6
|
return unless defined?(Tapioca::Dsl::Compilers::ActiveRecordAssociations)
|
7
7
|
|
8
|
+
require "boba/active_record/reflection_service"
|
9
|
+
|
8
10
|
module Tapioca
|
9
11
|
module Dsl
|
10
12
|
module Compilers
|
@@ -188,39 +190,14 @@ module Tapioca
|
|
188
190
|
association_class = type_for(reflection)
|
189
191
|
return as_nilable_type(association_class) unless association_type_option.persisted?
|
190
192
|
|
191
|
-
if has_one_and_required_reflection?(reflection)
|
193
|
+
if Boba::ActiveRecord::ReflectionService.has_one_and_required_reflection?(reflection)
|
194
|
+
association_class
|
195
|
+
elsif Boba::ActiveRecord::ReflectionService.belongs_to_and_non_optional_reflection?(reflection)
|
192
196
|
association_class
|
193
197
|
else
|
194
198
|
as_nilable_type(association_class)
|
195
199
|
end
|
196
200
|
end
|
197
|
-
|
198
|
-
# Note - one can do more here. If the association's attribute has an unconditional presence validation, it
|
199
|
-
# should also be considered required.
|
200
|
-
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
201
|
-
def has_one_and_required_reflection?(reflection)
|
202
|
-
reflection.has_one? && !!reflection.options[:required]
|
203
|
-
end
|
204
|
-
|
205
|
-
# Note - one can do more here. If the FK defining the belongs_to association is non-nullable at the DB level, or
|
206
|
-
# if the association's attribute has an unconditional presence validation, it should also be considered
|
207
|
-
# non-optional.
|
208
|
-
sig { params(reflection: ReflectionType).returns(T::Boolean) }
|
209
|
-
def belongs_to_and_non_optional_reflection?(reflection)
|
210
|
-
return false unless reflection.belongs_to?
|
211
|
-
|
212
|
-
optional = if reflection.options.key?(:required)
|
213
|
-
!reflection.options[:required]
|
214
|
-
else
|
215
|
-
reflection.options[:optional]
|
216
|
-
end
|
217
|
-
|
218
|
-
if optional.nil?
|
219
|
-
!!reflection.active_record.belongs_to_required_by_default
|
220
|
-
else
|
221
|
-
!optional
|
222
|
-
end
|
223
|
-
end
|
224
201
|
end
|
225
202
|
end
|
226
203
|
end
|
@@ -5,6 +5,7 @@ require "tapioca/dsl/compilers/active_record_columns"
|
|
5
5
|
|
6
6
|
return unless defined?(Tapioca::Dsl::Compilers::ActiveRecordColumns)
|
7
7
|
|
8
|
+
require "boba/active_record/attribute_service"
|
8
9
|
require "tapioca/dsl/helpers/active_record_column_type_helper"
|
9
10
|
|
10
11
|
module Tapioca
|
@@ -152,8 +153,14 @@ module Tapioca
|
|
152
153
|
def column_type_for(column_name)
|
153
154
|
return ["T.untyped", "T.untyped"] if column_type_option.untyped?
|
154
155
|
|
155
|
-
nilable_column = !has_non_null_database_constraint?(
|
156
|
-
|
156
|
+
nilable_column = !Boba::ActiveRecord::AttributeService.has_non_null_database_constraint?(
|
157
|
+
@constant,
|
158
|
+
column_name,
|
159
|
+
)
|
160
|
+
nilable_column &&= !Boba::ActiveRecord::AttributeService.has_unconditional_presence_validator?(
|
161
|
+
@constant,
|
162
|
+
column_name,
|
163
|
+
)
|
157
164
|
|
158
165
|
column_type = @constant.attribute_types[column_name]
|
159
166
|
getter_type = column_type_helper.send(
|
@@ -169,7 +176,8 @@ module Tapioca
|
|
169
176
|
getter_type
|
170
177
|
end
|
171
178
|
|
172
|
-
|
179
|
+
virtual_attribute = Boba::ActiveRecord::AttributeService.virtual_attribute?(@constant, column_name)
|
180
|
+
if column_type_option.persisted? && (virtual_attribute || !nilable_column)
|
173
181
|
[getter_type, setter_type]
|
174
182
|
else
|
175
183
|
getter_type = as_nilable_type(getter_type) unless column_type_helper.send(
|
@@ -180,30 +188,6 @@ module Tapioca
|
|
180
188
|
end
|
181
189
|
end
|
182
190
|
|
183
|
-
sig { params(column_name: String).returns(T::Boolean) }
|
184
|
-
def virtual_attribute?(column_name)
|
185
|
-
@constant.columns_hash[column_name].nil?
|
186
|
-
end
|
187
|
-
|
188
|
-
sig { params(column_name: String).returns(T::Boolean) }
|
189
|
-
def has_non_null_database_constraint?(column_name)
|
190
|
-
column = @constant.columns_hash[column_name]
|
191
|
-
return false if column.nil?
|
192
|
-
|
193
|
-
!column.null
|
194
|
-
end
|
195
|
-
|
196
|
-
sig { params(column_name: String).returns(T::Boolean) }
|
197
|
-
def has_unconditional_presence_validator?(column_name)
|
198
|
-
return false unless @constant.respond_to?(:validators_on)
|
199
|
-
|
200
|
-
@constant.validators_on(column_name).any? do |validator|
|
201
|
-
next false unless validator.is_a?(ActiveRecord::Validations::PresenceValidator)
|
202
|
-
|
203
|
-
!validator.options.key?(:if) && !validator.options.key?(:unless) && !validator.options.key?(:on)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
191
|
sig do
|
208
192
|
params(
|
209
193
|
klass: RBI::Scope,
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# typed: true
|
3
|
+
|
4
|
+
return if !defined?(AttrJson::Record)
|
5
|
+
|
6
|
+
module Tapioca
|
7
|
+
module Dsl
|
8
|
+
module Compilers
|
9
|
+
# `Tapioca::Dsl::Compilers::AttrJson` decorates RBI files for classes that use the `AttrJson` gem.
|
10
|
+
# https://github.com/jrochkind/attr_json
|
11
|
+
#
|
12
|
+
# For example, with the following ActiveRecord model:
|
13
|
+
# ~~~rb
|
14
|
+
# class Product < ActiveRecord::Base
|
15
|
+
# include AttrJson::Record
|
16
|
+
#
|
17
|
+
# attr_json :price_cents, :integer
|
18
|
+
# end
|
19
|
+
# ~~~
|
20
|
+
#
|
21
|
+
# This compiler will generate the following RBI:
|
22
|
+
# ~~~rbi
|
23
|
+
# class Product
|
24
|
+
# include AttrJsonGeneratedMethods
|
25
|
+
# extend AttrJson::Record::ClassMethods
|
26
|
+
#
|
27
|
+
# module AttrJsonGeneratedMethods
|
28
|
+
# sig { returns(::Integer) }
|
29
|
+
# def price_cents; end
|
30
|
+
#
|
31
|
+
# sig { params(value: Integer).returns(::Integer) }
|
32
|
+
# def price_cents=(value); end
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
# ~~~
|
36
|
+
class AttrJson < Tapioca::Dsl::Compiler
|
37
|
+
extend T::Sig
|
38
|
+
|
39
|
+
# Class methods module is already defined in the gem rbi, so just reference it here.
|
40
|
+
ClassMethodsModuleName = "AttrJson::Record::ClassMethods"
|
41
|
+
InstanceMethodModuleName = "AttrJsonGeneratedMethods"
|
42
|
+
ConstantType = type_member {{ fixed: T.any(T.class_of(::AttrJson::Record), T.class_of(::AttrJson::Model)) }}
|
43
|
+
|
44
|
+
class << self
|
45
|
+
extend T::Sig
|
46
|
+
|
47
|
+
sig { override.returns(T::Enumerable[Module]) }
|
48
|
+
def gather_constants
|
49
|
+
all_classes.select { |constant| constant < ::AttrJson::Record || constant < ::AttrJson::Model }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
sig { override.void }
|
54
|
+
def decorate
|
55
|
+
rbi_class = root.create_path(constant)
|
56
|
+
instance_module = RBI::Module.new(InstanceMethodModuleName)
|
57
|
+
|
58
|
+
decorate_attributes(instance_module)
|
59
|
+
|
60
|
+
rbi_class << instance_module
|
61
|
+
rbi_class.create_include(InstanceMethodModuleName)
|
62
|
+
rbi_class.create_extend(ClassMethodsModuleName) if constant < ::AttrJson::Record
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def decorate_attributes(rbi_scope)
|
68
|
+
T.unsafe(constant).attr_json_registry
|
69
|
+
.definitions
|
70
|
+
.sort_by(&:name) # this is annoying, but we need to sort to force consistent ordering or the rbi checks fail
|
71
|
+
.each do |definition|
|
72
|
+
_, type, options = definition.original_args
|
73
|
+
attribute_name = definition.name
|
74
|
+
type_name = sorbet_type(type, array: !!options[:array], nilable: !!options[:nil])
|
75
|
+
|
76
|
+
# Model: attr_json(:other_model_id, :string)
|
77
|
+
# => other_model_id
|
78
|
+
# => other_model_id=
|
79
|
+
rbi_scope.create_method(attribute_name, return_type: type_name)
|
80
|
+
rbi_scope.create_method(
|
81
|
+
"#{attribute_name}=",
|
82
|
+
parameters: [create_param("value", type: type_name)],
|
83
|
+
return_type: type_name,
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def symbol_type(type_name)
|
89
|
+
return type_name if type_name.is_a?(Symbol)
|
90
|
+
return type_name.to_sym if type_name.is_a?(String)
|
91
|
+
|
92
|
+
type_name.type
|
93
|
+
end
|
94
|
+
|
95
|
+
def sorbet_type(type_name, array: false, nilable: false)
|
96
|
+
sorbet_type = if type_name.respond_to?(:model)
|
97
|
+
type_name.model
|
98
|
+
else
|
99
|
+
case symbol_type(type_name)
|
100
|
+
when :string, :immutable_string, :text, :uuid, :binary
|
101
|
+
"String"
|
102
|
+
when :boolean
|
103
|
+
"T::Boolean"
|
104
|
+
when :integer, :big_integer
|
105
|
+
"Integer"
|
106
|
+
when :float
|
107
|
+
"Float"
|
108
|
+
when :decimal
|
109
|
+
"BigDecimal"
|
110
|
+
when :time, :datetime
|
111
|
+
"Time"
|
112
|
+
when :date
|
113
|
+
"Date"
|
114
|
+
when :money
|
115
|
+
"Money"
|
116
|
+
when :json
|
117
|
+
"T.untyped"
|
118
|
+
else
|
119
|
+
"T.untyped"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
sorbet_type = "::#{sorbet_type}"
|
124
|
+
sorbet_type = "T::Array[#{sorbet_type}]" if array
|
125
|
+
sorbet_type = "T.nilable(#{sorbet_type})" if nilable # todo: improve this
|
126
|
+
|
127
|
+
sorbet_type
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
return unless defined?(Paperclip)
|
5
|
+
|
6
|
+
module Tapioca
|
7
|
+
module Dsl
|
8
|
+
module Compilers
|
9
|
+
# `Tapioca::Dsl::Compilers::Paperclip` decorates RBI files for classes that use the `has_attached_file` method
|
10
|
+
# provided by the `paperclip` gem.
|
11
|
+
# https://github.com/thoughtbot/paperclip
|
12
|
+
#
|
13
|
+
# For example, with the following ActiveRecord model:
|
14
|
+
# ~~~rb
|
15
|
+
# class Product < ActiveRecord::Base
|
16
|
+
# has_attached_file(:marketing_image)
|
17
|
+
# end
|
18
|
+
# ~~~
|
19
|
+
#
|
20
|
+
# This compiler will generate the following RBI:
|
21
|
+
# ~~~rbi
|
22
|
+
# class Product
|
23
|
+
# include PaperclipGeneratedMethods
|
24
|
+
#
|
25
|
+
# module PaperclipGeneratedMethods
|
26
|
+
# sig { returns(::Paperclip::Attachment) }
|
27
|
+
# def marketing_image; end
|
28
|
+
#
|
29
|
+
# sig { params(value: T.untyped).void }
|
30
|
+
# def marketing_image=(value); end
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# ~~~
|
34
|
+
class Paperclip < Tapioca::Dsl::Compiler
|
35
|
+
extend T::Sig
|
36
|
+
include RBIHelper
|
37
|
+
|
38
|
+
InstanceModuleName = "PaperclipGeneratedMethods"
|
39
|
+
ConstantType = type_member { { fixed: T.class_of(::Paperclip::Glue) } }
|
40
|
+
|
41
|
+
class << self
|
42
|
+
extend T::Sig
|
43
|
+
|
44
|
+
sig { override.returns(T::Enumerable[Module]) }
|
45
|
+
def gather_constants
|
46
|
+
all_classes.select { |c| c < ::Paperclip::Glue }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
sig { override.void }
|
51
|
+
def decorate
|
52
|
+
attachments = ::Paperclip::AttachmentRegistry.names_for(constant)
|
53
|
+
return if attachments.empty?
|
54
|
+
|
55
|
+
root.create_path(constant) do |klass|
|
56
|
+
instance_module = RBI::Module.new(InstanceModuleName)
|
57
|
+
|
58
|
+
attachments.each do |attachment_name|
|
59
|
+
# Model: has_attached_file(:marketing_image)
|
60
|
+
# => marketing_image
|
61
|
+
# => marketing_image=
|
62
|
+
instance_module.create_method(attachment_name, return_type: "::Paperclip::Attachment")
|
63
|
+
instance_module.create_method(
|
64
|
+
"#{attachment_name}=",
|
65
|
+
parameters: [create_param("value", type: "T.untyped")],
|
66
|
+
return_type: nil,
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
klass << instance_module
|
71
|
+
klass.create_include(InstanceModuleName)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -21,6 +21,11 @@ module Tapioca
|
|
21
21
|
def decorate
|
22
22
|
return if constant.state_machines.empty?
|
23
23
|
|
24
|
+
# This is a hack to make sure the instance methods are defined on the constant. Somehow the constant is being
|
25
|
+
# loaded but the actual `state_machine` call is not being executed, so the instance methods don't exist yet.
|
26
|
+
# Instantiating an empty class fixes it.
|
27
|
+
constant.try(:new)
|
28
|
+
|
24
29
|
super()
|
25
30
|
|
26
31
|
root.create_path(T.unsafe(constant)) do |klass|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boba
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Angellist
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-11-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sorbet-static-and-runtime
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.16.
|
33
|
+
version: 0.16.4
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.16.
|
40
|
+
version: 0.16.4
|
41
41
|
description:
|
42
42
|
email:
|
43
43
|
- alex.stathis@angellist.com
|
@@ -48,20 +48,24 @@ files:
|
|
48
48
|
- LICENSE
|
49
49
|
- README.md
|
50
50
|
- lib/boba.rb
|
51
|
+
- lib/boba/active_record/attribute_service.rb
|
52
|
+
- lib/boba/active_record/reflection_service.rb
|
51
53
|
- lib/boba/relations_railtie.rb
|
52
54
|
- lib/boba/version.rb
|
53
55
|
- lib/tapioca/dsl/compilers/active_record_associations_persisted.rb
|
54
56
|
- lib/tapioca/dsl/compilers/active_record_columns_persisted.rb
|
57
|
+
- lib/tapioca/dsl/compilers/attr_json.rb
|
55
58
|
- lib/tapioca/dsl/compilers/money_rails.rb
|
59
|
+
- lib/tapioca/dsl/compilers/paperclip.rb
|
56
60
|
- lib/tapioca/dsl/compilers/state_machines_extended.rb
|
57
61
|
homepage: https://github.com/angellist/boba
|
58
62
|
licenses:
|
59
63
|
- MIT
|
60
64
|
metadata:
|
61
65
|
bug_tracker_uri: https://github.com/angellist/boba/issues
|
62
|
-
changelog_uri: https://github.com/angellist/boba/blob/0.0.
|
66
|
+
changelog_uri: https://github.com/angellist/boba/blob/0.0.9/History.md
|
63
67
|
homepage_uri: https://github.com/angellist/boba
|
64
|
-
source_code_uri: https://github.com/angellist/boba/tree/0.0.
|
68
|
+
source_code_uri: https://github.com/angellist/boba/tree/0.0.9
|
65
69
|
rubygems_mfa_required: 'true'
|
66
70
|
post_install_message:
|
67
71
|
rdoc_options: []
|
@@ -78,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
82
|
- !ruby/object:Gem::Version
|
79
83
|
version: '0'
|
80
84
|
requirements: []
|
81
|
-
rubygems_version: 3.5.
|
85
|
+
rubygems_version: 3.5.22
|
82
86
|
signing_key:
|
83
87
|
specification_version: 4
|
84
88
|
summary: Custom Tapioca compilers
|