File Coverage

lib/Mojolicious/Plugin/BModel.pm
Criterion Covered Total %
statement 49 102 48.0
branch 9 34 26.4
condition 5 12 41.6
subroutine 14 22 63.6
pod 1 3 33.3
total 78 173 45.0


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::BModel;
2              
3 3     3   1101037 use 5.010;
  3         11  
4 3     3   23 use strict;
  3         6  
  3         148  
5 3     3   45 use warnings;
  3         7  
  3         288  
6 3     3   24 use Carp qw/ carp croak /;
  3         12  
  3         310  
7 3     3   19 use File::Find qw/ find /;
  3         6  
  3         194  
8 3     3   20 use List::Util qw/ first /;
  3         19  
  3         270  
9 3     3   21 use File::Spec;
  3         6  
  3         110  
10              
11 3     3   1807 use Mojo::Loader;
  3         486845  
  3         193  
12 3     3   25 use Mojo::Base 'Mojolicious::Plugin';
  3         6  
  3         15  
13              
14             our $VERSION = '0.1';
15              
16             my $DEFAULT_CREATE_DIR = 1;
17             my $DEFAULT_MODEL_DIR = 'Model'; # directory of poject for the Model-modules
18             my %MODULES = ();
19             my $BASE_MODEL = 'Mojolicious::BModel::Base';
20              
21             sub register {
22 0     0 1 0 my ( $self, $app, $conf ) = @_;
23              
24 0         0 my $app_name = ref $app; # name of calling Mojo app
25              
26             # namespace path to Models dir. By default it is the name of application, but it can be redefine.
27             # For example, namespace can be MyPath::ToModel or any other
28 0 0       0 my $namespace = $conf->{namespace} ? $conf->{namespace} : $app_name;
29 0 0       0 if ( ! $self->_check_namespace( $namespace ) ) {
30 0         0 croak "Wrong format of namespace $namespace. Exit";
31             }
32              
33 0         0 my $namespace_path = $self->_convert_namespace_to_path( $namespace );
34              
35             # work with considering operation system
36 0         0 my $path_to_model = File::Spec->catfile( $app->home->child('lib')->to_string, $namespace_path, $DEFAULT_MODEL_DIR );
37 0         0 my $dir_exists = $self->_check_model_dir( $path_to_model );
38 0 0       0 my $to_create_dir = exists $conf->{create_dir} ? $conf->{create_dir} : $DEFAULT_CREATE_DIR;
39              
40 0 0 0     0 if ( ! ( $dir_exists && $to_create_dir ) ) {
    0 0        
41 0         0 carp "Directory " . File::Spec->catfile( $namespace_path, $DEFAULT_MODEL_DIR ) . " does not exist";
42 0         0 return 1;
43             }
44             elsif ( ! $dir_exists && $to_create_dir ) {
45 0 0       0 mkdir $path_to_model or croak "Could not create directory $path_to_model : $!";
46             }
47              
48 0         0 $self->load_models( $path_to_model, $namespace, $app );
49              
50             $app->helper(
51             model => sub {
52 0     0   0 my ( $self, $model_name ) = @_;
53 0 0       0 croak "Unknown model $model_name" if ! exists $MODULES{ $model_name };
54 0         0 return $MODULES{ $model_name };
55             }
56 0         0 );
57              
58 0         0 return 1;
59             }
60              
61             # check a path to the 'Model' dir
62             sub _check_model_dir {
63 3     3   8428 my ( $self, $path_to_model ) = @_;
64              
65 3 100 100     62 return 1 if -e $path_to_model && -d $path_to_model;
66 2         9 return;
67             }
68              
69             # check for validation of namespace
70             sub _check_namespace {
71 0     0   0 my ( $self, $namespace ) = @_;
72              
73 0         0 my @splitted_namespace = $self->_separate_namespace_name( $namespace );
74 0 0       0 return if ! scalar @splitted_namespace;
75              
76 0     0   0 my $found_wrong_name = first { $_ !~ m/[a-zA-Z0-9]/ } @splitted_namespace;
  0         0  
77 0 0       0 return if $found_wrong_name;
78              
79 0         0 return 1;
80             }
81              
82             # convert name like 'Aaaa::Bbbbb::Cccc' to 'Aaaa/Bbbbb/Cccc' for Unix-like OS or 'Aaaa\Bbbbb\Cccc' for Windows
83             sub _convert_namespace_to_path {
84 0     0   0 my ( $self, $namespace ) = @_;
85              
86 0         0 my @splitted_namespace = $self->_separate_namespace_name( $namespace );
87 0 0       0 my $path = scalar @splitted_namespace == 1 ? $splitted_namespace[0] : File::Spec->catfile( @splitted_namespace );
88              
89 0         0 return $path;
90             }
91              
92             sub _separate_namespace_name {
93 0     0   0 my ( $self, $namespace ) = @_;
94              
95 0         0 my @splitted_name = split( /\:\:/, $namespace );
96              
97 0         0 return @splitted_name;
98             }
99              
100             sub _convert_model_dirs_array {
101 6     6   40 my ( $self, @dirs ) = @_;
102              
103 6         9 my $canonical_name;
104              
105 6         16 for my $dir ( @dirs ) {
106 15 100       54 next if $dir =~ m/^\s*$/;
107 9 100       29 $canonical_name = ( defined $canonical_name ) ? $canonical_name . '::' . $dir : $dir;
108             }
109              
110 6         18 return $canonical_name;
111             }
112              
113             sub find_models {
114 1     1 0 4175 my ( $self, $path_to_model, $model_path ) = @_;
115              
116 1         4 my @model_dirs = ( $model_path );
117              
118             # find all subdirs in the directory of model
119             find(
120             sub {
121 7 100 66 7   401 return if ! -d $File::Find::name || $File::Find::name eq $path_to_model;
122 6         16 my $dir_name = $File::Find::name;
123 6         91 $dir_name =~ s/$path_to_model//; # remove path to model from directory
124              
125             # split dir path name and convert it into name like 'aaa::bbb::ccc'
126 6         65 my @dirs = File::Spec->splitdir( $dir_name );
127 6         22 my $canonical_dir_name = $self->_convert_model_dirs_array( @dirs );
128 6 50       24 if ( ! $canonical_dir_name ) {
129 0         0 carp "Cannot parse dir name $dir_name";
130 0         0 return;
131             }
132 6         809 push @model_dirs, $model_path . '::' . $canonical_dir_name;
133             },
134 1         110 ( $path_to_model )
135             );
136              
137 1         13 return \@model_dirs;
138             }
139              
140             # recursive search and download modules with models
141             sub load_models {
142 0     0 0   my ( $self, $path_to_model, $namespace, $app ) = @_;
143              
144 0           my $model_path = $namespace . '::' . $DEFAULT_MODEL_DIR;
145 0           my @model_dirs = @{ $self->find_models( $path_to_model, $model_path ) };
  0            
146              
147 0           my $base_load_err = Mojo::Loader::load_class( $BASE_MODEL );
148 0 0         croak "Loading base model $BASE_MODEL failed: $base_load_err" if ref $base_load_err;
149             {
150 3     3   7579 no strict 'refs';
  3         7  
  3         923  
  0            
151 0     0     *{ "$BASE_MODEL\::app" } = sub { $app };
  0            
  0            
152             }
153              
154             # load modules from every dirs and subdirs of model
155 0           for my $dir ( @model_dirs ) {
156 0           my @model_packages = Mojo::Loader::find_modules( $dir );
157 0           for my $pm ( @model_packages ) {
158 0           my $load_err = Mojo::Loader::load_class( $pm );
159 0 0         croak "Loading '$pm' failed: $load_err" if ref $load_err;
160 0           my ( $basename ) = $pm =~ /$model_path\::(.*)/;
161 0           $MODULES{ $basename } = $pm->new;
162             }
163             }
164              
165 0           return 1;
166             }
167              
168             1;
169              
170             __END__