contracts.ruby
contracts.ruby copied to clipboard
Contracts and order of includes in the app
I have a contract like this
Contract Maybe[String], Maybe[String] => User
def self.create_user
validate_password(password, password_confirmation)
hashed_password = DB["select crypt( ? , gen_salt('bf'));", password].first[:crypt]
u = User.new(email: email)
DB.transaction do
u.save
u.add_password_hash(hashed_password: hashed_password)
end
end
When the app includes the module this code is in I get an error message that says
uninitialized constant Svc::User (NameError)
So I tried ::User instead but that doesn't either. So it looks like I need to load all other classes first before I can use contracts that use them.
Is there a way to get around this?
Does it work without the contract? I would expect the behavior of uninitialized constant
to be consistent here since the code doesn't get executed until the method is called.
Whoops, accidentally closed.
Yes it does work if I take the contract out.
I guess it makes sense that the contract can't know about a type that hasn't been defined yet but this means I have to be very careful about where I include my contracts. It's a bit annoying for custom classes. Either that or I only return simple types which doesn't seem all that satisfactory either.
I ran into this as well. I worked around it by stubbing any classes that are referenced in contracts before you actually load those classes. Then you don't have to worry about load order.
Example:
# ---------------
# lib/mygem.rb
# ---------------
module MyGem
class ClassOne; end
class ClassTwo; end
end
require "contracts"
require_relative "mygem/classone"
require_relative "mygem/classtwo"
module MyGem
def self.some_method
# Other stuff can go here if you need it.
end
end
# ---------------
# lib/mygem/classone.rb
# ---------------
module MyGem
class ClassOne
include ::Contracts::Core
Contract MyGem::ClassTwo => String
def something_that_calls_classtwo(classtwo_obj)
# Do something with classtwo_obj
end
end
end
# ---------------
# lib/mygem/classtwo.rb
# ---------------
module MyGem
class ClassTwo
include ::Contracts::Core
Contract MyGem::ClassOne => String
def something_that_calls_classone(classone_obj)
# Do something with classone_obj
end
end
end