app in a model

200 views
Skip to first unread message

Eugene Toropov

unread,
Apr 17, 2015, 10:38:06 AM4/17/15
to mojol...@googlegroups.com
Greetings,

In the following example how will you make MyApp::Model::Users have access to app object (which is basically $self) that is necessary to use app->db and app->config?


package MyApp;
use Mojo::Base 'Mojolicious';

use MyApp::Model::Users;

sub startup {
  my $self = shift;

  $self->secrets(['Mojolicious rocks']);
  $self->helper(users => sub { state $users = MyApp::Model::Users->new });

  my $r = $self->routes;

  $r->any('/' => sub {
    my $c = shift;

    my $user = $c->param('user') || '';
    my $pass = $c->param('pass') || '';
    return $c->render unless $c->users->check($user, $pass);

    $c->session(user => $user);
    $c->flash(message => 'Thanks for logging in.');
    $c->redirect_to('protected');
  } => 'index');

  my $logged_in = $r->under(sub {
    my $c = shift;
    return 1 if $c->session('user');
    $c->redirect_to('index');
    return undef;
  });
  $logged_in->get('/protected');

  $r->get('/logout' => sub {
    my $c = shift;
    $c->session(expires => 1);
    $c->redirect_to('index');
  });
}

1;
Cheers
Eugene

Jan Henning Thorsen

unread,
Apr 20, 2015, 3:07:27 AM4/20/15
to mojol...@googlegroups.com
You don't. Passing $app or $c to a model is not a good idea. Reason for this is that it makes it hard to reuse the models elsewhere. What you can do, is passing data from $app when you construct your models, but I would strongly advice against passing $app.

Eugene Toropov

unread,
Apr 20, 2015, 3:17:27 AM4/20/15
to mojol...@googlegroups.com
Hi Jan,

Thanks for your reply. Don’t you think that passing app->db to every model is not a good idea neither because model must know itself where data are and how to fetch it and pass to controller? Also it’s simply inconvenient to always have one (or even 2 - app->db and app->redis for example) arguments passed to every model, no?

Cheers
Eugene

--
You received this message because you are subscribed to the Google Groups "Mojolicious" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.
To post to this group, send email to mojol...@googlegroups.com.
Visit this group at http://groups.google.com/group/mojolicious.
For more options, visit https://groups.google.com/d/optout.

Jan Henning Thorsen

unread,
Apr 20, 2015, 4:18:43 AM4/20/15
to mojol...@googlegroups.com
I pass on $db to every instance of a model in MCT: https://github.com/mojoconf/MCT/blob/master/lib/MCT.pm#L33

Just make a helper where the passing of $db (or any other common argument) is "invisible".

The nice thing is that the model will need to know less. It will then be easier to use for "other things" outside of your Mojo app. To flip the question around. Which version ofof the code below makes most sense?

  my $app = Mojolicious->new;
  $db = Mojo::Pg->new(app => $app);
  $db = Mojo::Pg->new(app => $app->config->{db}{dsn});

(I hope the version where $app is passed to new() looks weird...)

To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious+unsubscribe@googlegroups.com.

Eugene Toropov

unread,
Apr 20, 2015, 4:31:45 AM4/20/15
to mojol...@googlegroups.com
Hi Jan,

Having to pass "db => shift->model->db” in every model helper doesn’t seem like invisible way ;) It would be really invisible if you had “use DB” inside a model package and then “my $db = $DB::dbh” :) but then you’d have to somehow pass app->config to DB. Also if you kept something in app->config that model needs - would you pass it as another parameter to every model you had? What if then you had app->cache (redis/memcached) ? Another parameter to pass to every model again? I feel it doesn’t seem good but can’t find any other way in Mojo to do it so thought someone had found the solution...

Cheers
Eugene

To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.

Jan Henning Thorsen

unread,
Apr 20, 2015, 4:49:02 AM4/20/15
to mojol...@googlegroups.com
It's so funny how this questions comes up over and over again. I even have to battle it myself from time to time, even when I know how stupid (Sorry for using "stupid", but I think I'm allowed as it reflects back on myself), limiting, hard to get around later, ... it would be to pass some $app-like object around to lower level models.

Also, I probably know how your mind is set on your statement, and how you probably just want to get confirmation instead of the answer I'm giving you. Yes... Me and many before you have been in the same state of mind.

So... I was hoping I could help you with my Mojo::Pg->new() example above, but it seems like I have failed.

I wish you the best of luck finding the right solution.

I also apologize if I'm stepping on yours or anyone else's toes. That is not my intention.

Eugene Toropov

unread,
Apr 20, 2015, 5:10:20 AM4/20/15
to mojol...@googlegroups.com
Right, well, I hope Sebastian will have a moment to reply at some point if it comes up so often and so many people were "in the same state of mind" :) Or he did already? If so - would be great if someone could provide a link. Thank you.

Cheers
Eugene

To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.

Jan Henning Thorsen

unread,
Apr 20, 2015, 5:50:35 AM4/20/15
to mojol...@googlegroups.com
"Many people" != "Mojolicious people". This is a very generic problem, where a higher level object pass itself to a lower level object. What I mean is that you build stuff from small components built on top of each other, and then you have this high level object on top which "orchestrate" the objects, passing on just enough information to make each of them work.

I will try to come up with a new example. Which of these two lines of code makes most sense to you?

  $user = $c->model->users->find({id => $c->param("user_id")});
  $user = $c->model->users->find($c);


Btw... I'm drifting. Your original question is very specific and not explaining why you want to pass $app to the model. Can you try to explain why you need to pass on $app to the model? Do you have an example usecase/piece of code where that makes most sense?

Eugene Toropov

unread,
Apr 20, 2015, 6:27:28 AM4/20/15
to mojol...@googlegroups.com
Hi Jan,

My case is as follows: we have a number of partners application must communicate with. Every partner is basically a separate package with a number of common methods (download, import, search, book, etc). Right now I’m implementing download method (to run via "app partner download <partner_id>" command) for a number of partners. It basically gets data from partner (FTP or SOAP or other kind of XML/JSON/custom format web service) and saves in database so it needs app->db handler and a number of config parameters from app->config. 

Your example is about something different. You don’t tell model where and how it must search data you want. You simply provide input parameter (but not app->db) and obviously 2nd option doesn’t make sense.

Cheers
Eugene

To unsubscribe from this group and stop receiving emails from it, send an email to mojolicious...@googlegroups.com.

Jason Galea

unread,
Apr 20, 2015, 7:40:01 AM4/20/15
to mojol...@googlegroups.com
The best example to consider here is testing. When you write your tests for MyApp::Model::Users do you want to have to have $c as it exists in Mojolicious (or a complex mock of it) or would you prefer to just have to give it what it needs to work?

$app = Mock::App->new(db => $db, config => Mock::Config->new({}));
my $users = MyApp::Model::Users->new(app => $app);

..blah blah..

or rather

$users = MyApp::Model::Users( db => $db, partner_id => 4321, other_config => 'options' );

If you have more models, then you do something like..

$model = MyApp::Model->new(db => $db, other_config => 'options');
$users = $model->users( partner_id => 4321 );

or 

$model = MyApp::Model->new(db => $db,  partner_id => 4321, other_config => 'options' );
$users = $model->users();

The main point though is to keep your package separate from Mojolicious (or any other module/framework) to keep it simple, testable, and portable.



Eugene Toropov

unread,
Apr 20, 2015, 9:32:18 AM4/20/15
to mojol...@googlegroups.com
Hi Jason,

I don’t want to provide neither app or controller nor db. All your examples have to pass db which is not a good practice I think. But if I need to provide app->db and app->config and app->cache and something else from app then easier to provide just app and find everything else in it.

Cheers
Eugene
Reply all
Reply to author
Forward
0 new messages