Safe Haskell | Safe-Inferred |
---|---|
Language | Haskell2010 |
Graphula is a compact interface for generating data and linking its dependencies. You can use this interface to generate fixtures for automated testing.
{- config/models School name Text deriving Generic Teacher schoolId SchoolId name Text deriving Generic Course schoolId SchoolId teacherId TeacherId name Text deriving Generic -} instance Arbitrary School where -- ... instance Arbitrary Teacher where -- ... instance Arbitrary Course where -- ... instanceHasDependencies
School instanceHasDependencies
Teacher where type Dependencies Teacher = Only SchoolId instanceHasDependencies
Course where type Dependencies Course = (SchoolId, CourseId)runGraphulaT
runDB $ do school <-node
@School () mempty teacher <-node
@Teacher (onlyKey
school) $ edit $ t -> t { teacherName = "Alice" } course <-node
@Course (keys
(school, teacher)) $ensure
$ not . courseIsArchived
Synopsis
- class HasDependencies a where
- type Dependencies a
- type KeySource a :: KeySourceType
- dependsOn :: a -> Dependencies a -> a
- newtype Only a = Only {
- fromOnly :: a
- only :: a -> Only a
- node :: forall a m. (MonadGraphula m, Logging m a, Arbitrary a, HasDependencies a, GenerateKey a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a) => Dependencies a -> NodeOptions a -> m (Entity a)
- edit :: (a -> a) -> NodeOptions a
- ensure :: (a -> Bool) -> NodeOptions a
- data GraphulaT n m a
- runGraphulaT :: MonadUnliftIO m => Maybe Int -> (forall b. ReaderT SqlBackend n b -> m b) -> GraphulaT n m a -> m a
- data GenerationFailure
- data KeySourceType
- nodeKeyed :: forall a m. (MonadGraphula m, Logging m a, Arbitrary a, HasDependencies a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a) => Key a -> Dependencies a -> NodeOptions a -> m (Entity a)
- data GraphulaLoggedT m a
- runGraphulaLoggedT :: MonadUnliftIO m => GraphulaLoggedT m a -> m a
- runGraphulaLoggedWithFileT :: MonadUnliftIO m => FilePath -> GraphulaLoggedT m a -> m a
- data GraphulaIdempotentT m a
- runGraphulaIdempotentT :: MonadUnliftIO m => GraphulaIdempotentT m a -> m a
- type family GraphulaContext (m :: Type -> Type) (ts :: [Type]) :: Constraint where ...
- type GraphulaNode m a = (HasDependencies a, Logging m a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a, Arbitrary a)
- type MonadGraphula m = (Monad m, MonadIO m, MonadGraphulaBackend m, MonadGraphulaFrontend m)
- class MonadGraphulaBackend m where
- class MonadGraphulaFrontend m where
- insert :: (PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m, GraphulaSafeToInsert a) => Maybe (Key a) -> a -> m (Maybe (Entity a))
- insertKeyed :: (PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m) => Key a -> a -> m (Maybe (Entity a))
- remove :: (PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m) => Key a -> m ()
- data NodeOptions a
- class (GenerateKeyInternal (KeySource a) a, KeyConstraint (KeySource a) a, InsertWithPossiblyRequiredKey (KeySourceTypeInternalM (KeySource a)), InsertConstraint (KeySourceTypeInternalM (KeySource a)) a) => GenerateKey a
- class NoConstraint a
Basic usage
Model requirements
class HasDependencies a where Source #
Nothing
type Dependencies a Source #
A data type declaring the model's dependencies
Models with no dependencies can declare an empty instance,
instance HasDependencies
School
Models with one dependency must use the Only
1-tuple constructor,
instance HasDependencies
Teacher where
type Dependencies Teacher = Only SchoolId
Models with multiple dependencies use tuple syntax,
instance HasDependencies
Course where
type Dependencies Course = (SchoolId, TeacherId)
type Dependencies _a = ()
type KeySource a :: KeySourceType Source #
Specify the method for resolving a node's key
This can be
'SourceDefault -- automatically generate keys from the database 'SourceArbitrary -- automatically generate keys using'SourceExternal -- explicitly pass a key using
Arbitrary
nodeKeyed
Most types will use SourceDefault
or SourceArbitrary
. Only use
SourceExternal
if the key for a value is always defined externally.
type KeySource _a = 'SourceDefault
dependsOn :: a -> Dependencies a -> a Source #
Assign values from the Dependencies
collection to a value
This must be an idempotent operation. Law:
(\x d -> x `dependsOn` d `dependsOn` d) = dependsOn
The default, Generic
-based implementation will assign values by the order
of the fields in the model's type.
default dependsOn :: (HasEot a, HasEot (Dependencies a), GHasDependencies (Proxy a) (Proxy (Dependencies a)) (Eot a) (Eot (Dependencies a))) => a -> Dependencies a -> a Source #
For entities that only have singular Dependencies
Instances
Foldable Only Source # | |
Defined in Graphula.Dependencies fold :: Monoid m => Only m -> m # foldMap :: Monoid m => (a -> m) -> Only a -> m # foldMap' :: Monoid m => (a -> m) -> Only a -> m # foldr :: (a -> b -> b) -> b -> Only a -> b # foldr' :: (a -> b -> b) -> b -> Only a -> b # foldl :: (b -> a -> b) -> b -> Only a -> b # foldl' :: (b -> a -> b) -> b -> Only a -> b # foldr1 :: (a -> a -> a) -> Only a -> a # foldl1 :: (a -> a -> a) -> Only a -> a # elem :: Eq a => a -> Only a -> Bool # maximum :: Ord a => Only a -> a # | |
Traversable Only Source # | |
Functor Only Source # | |
Generic (Only a) Source # | |
Show a => Show (Only a) Source # | |
Eq a => Eq (Only a) Source # | |
Ord a => Ord (Only a) Source # | |
type Rep (Only a) Source # | |
Defined in Graphula.Dependencies | |
type Keys (Only (Entity a)) Source # | |
Defining the graph
node :: forall a m. (MonadGraphula m, Logging m a, Arbitrary a, HasDependencies a, GenerateKey a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a) => Dependencies a -> NodeOptions a -> m (Entity a) Source #
Generate a node with a default (Arbitrary or database-provided) key
a <- node @A () mempty
edit :: (a -> a) -> NodeOptions a Source #
Modify the node after it's been generated
a <- node @A () $ edit $ \a -> a { someField = True }
ensure :: (a -> Bool) -> NodeOptions a Source #
Require a node to satisfy the specified predicate
a <- node @A () $ ensure $ (== True) . someField
N.B. ensuring a condition that is infrequently met can be innefficient.
Running the graph
Instances
:: MonadUnliftIO m | |
=> Maybe Int | Optional seed |
-> (forall b. ReaderT SqlBackend n b -> m b) | Database runner |
-> GraphulaT n m a | |
-> m a |
data GenerationFailure Source #
GenerationFailureMaxAttemptsToConstrain TypeRep | Could not satisfy constraints defined using |
GenerationFailureMaxAttemptsToInsert TypeRep | Could not satisfy database constraints on |
Instances
Exception GenerationFailure Source # | |
Defined in Graphula.Node | |
Show GenerationFailure Source # | |
Defined in Graphula.Node showsPrec :: Int -> GenerationFailure -> ShowS # show :: GenerationFailure -> String # showList :: [GenerationFailure] -> ShowS # | |
Eq GenerationFailure Source # | |
Defined in Graphula.Node (==) :: GenerationFailure -> GenerationFailure -> Bool # (/=) :: GenerationFailure -> GenerationFailure -> Bool # |
Advanced usage
Non-serial keys
data KeySourceType Source #
SourceDefault | Generate keys using the database's |
SourceArbitrary | |
SourceExternal | Always explicitly pass an external key See |
nodeKeyed :: forall a m. (MonadGraphula m, Logging m a, Arbitrary a, HasDependencies a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a) => Key a -> Dependencies a -> NodeOptions a -> m (Entity a) Source #
Generate a node with an explictly-given key
let someKey = UUID.fromString "..." a <- nodeKeyed @A someKey () mempty
Running with logging
data GraphulaLoggedT m a Source #
Instances
runGraphulaLoggedT :: MonadUnliftIO m => GraphulaLoggedT m a -> m a Source #
Run the graph while logging to a temporary file
runGraphulaLoggedWithFileT :: MonadUnliftIO m => FilePath -> GraphulaLoggedT m a -> m a Source #
runGraphulaLoggedT
, but to the specified file
Running idempotently
data GraphulaIdempotentT m a Source #
Instances
runGraphulaIdempotentT :: MonadUnliftIO m => GraphulaIdempotentT m a -> m a Source #
Useful synonymns
When declaring your own functions that call node
, these synonyms can help
with the constraint soup.
genSchoolWithTeacher :: GraphulaContext m '[School, Teacher] -> m (Entity Teacher) genSchoolWithTeacher = do school <- node @School () mempty node @Teacher (onlyKey school) mempty
type family GraphulaContext (m :: Type -> Type) (ts :: [Type]) :: Constraint where ... Source #
A constraint over lists of nodes for MonadGraphula
, and GraphulaNode
.
Helpful for defining utility functions over many nodes.
mkABC :: (GraphulaContext m '[A, B, C]) => m (Node m C)
mkABC = do
a <- node A () mempty
b <- node
B (only a) mempty
node @C (a, b) $ edit $ n ->
n { cc = "spanish" }
GraphulaContext m '[] = MonadGraphula m | |
GraphulaContext m (t ': ts) = (GraphulaNode m t, GraphulaContext m ts) |
type GraphulaNode m a = (HasDependencies a, Logging m a, PersistEntityBackend a ~ SqlBackend, PersistEntity a, Typeable a, Arbitrary a) Source #
Lower-level details
These exports are likely to be removed from this module in a future version. If you are using them, consider importing from their own modules.
type MonadGraphula m = (Monad m, MonadIO m, MonadGraphulaBackend m, MonadGraphulaFrontend m) Source #
class MonadGraphulaBackend m where Source #
type Logging m :: Type -> Constraint Source #
Instances
(MonadGraphulaBackend m, MonadIO m) => MonadGraphulaBackend (GraphulaLoggedT m) Source # | |
Defined in Graphula.Logged type Logging (GraphulaLoggedT m) :: Type -> Constraint Source # askGen :: GraphulaLoggedT m (IORef QCGen) Source # logNode :: Logging (GraphulaLoggedT m) a => a -> GraphulaLoggedT m () Source # | |
MonadIO m => MonadGraphulaBackend (GraphulaT n m) Source # | |
class MonadGraphulaFrontend m where Source #
insert :: (PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m, GraphulaSafeToInsert a) => Maybe (Key a) -> a -> m (Maybe (Entity a)) Source #
insertKeyed :: (PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m) => Key a -> a -> m (Maybe (Entity a)) Source #
remove :: (PersistEntityBackend a ~ SqlBackend, PersistEntity a, Monad m) => Key a -> m () Source #
Instances
data NodeOptions a Source #
Options for generating an individual node
NodeOptions
can be created and combined with the Monoidal operations (<>)
and mempty
.
a1 <- node @A () mempty a2 <- node @A () $ edit $ \a -> a { someField = True } a3 <- node @A () $ ensure $ (== True) . someField
Instances
Monoid (NodeOptions a) Source # | |
Defined in Graphula.Node mempty :: NodeOptions a # mappend :: NodeOptions a -> NodeOptions a -> NodeOptions a # mconcat :: [NodeOptions a] -> NodeOptions a # | |
Semigroup (NodeOptions a) Source # | |
Defined in Graphula.Node (<>) :: NodeOptions a -> NodeOptions a -> NodeOptions a # sconcat :: NonEmpty (NodeOptions a) -> NodeOptions a # stimes :: Integral b => b -> NodeOptions a -> NodeOptions a # | |
Generic (NodeOptions a) Source # | |
Defined in Graphula.Node type Rep (NodeOptions a) :: Type -> Type # from :: NodeOptions a -> Rep (NodeOptions a) x # to :: Rep (NodeOptions a) x -> NodeOptions a # | |
type Rep (NodeOptions a) Source # | |
Defined in Graphula.Node |
class (GenerateKeyInternal (KeySource a) a, KeyConstraint (KeySource a) a, InsertWithPossiblyRequiredKey (KeySourceTypeInternalM (KeySource a)), InsertConstraint (KeySourceTypeInternalM (KeySource a)) a) => GenerateKey a Source #
Abstract constraint that some a
can generate a key
This is part of ensuring better error messages.
Instances
(GenerateKeyInternal (KeySource a) a, KeyConstraint (KeySource a) a, InsertWithPossiblyRequiredKey (KeySourceTypeInternalM (KeySource a)), InsertConstraint (KeySourceTypeInternalM (KeySource a)) a) => GenerateKey a Source # | |
Defined in Graphula.Dependencies |
class NoConstraint a Source #
Instances
NoConstraint a Source # | |
Defined in Graphula.NoConstraint |