Autoanswer:
I made a test and observed that if you just run php artisan module:migrate YourModuleName
it just migrates all the new tables, without touching anything else.
Cool!
Autoanswer:
I made a test and observed that if you just run php artisan module:migrate YourModuleName
it just migrates all the new tables, without touching anything else.
Cool!
I have an app that is currently deployed and a module needs to have a new entity. Is there any way to run only the entity specific migration, without running the module migration as a whole?
OK, got it, thanks to @nWidart .
The test complained about tables that haven't been created. So, the function resetDatabase()
was doing nothing.
In order to put the function resetDatabase()
to work, you have to tell the module's service provider where to load the migrations from.
Thus, this gets solved by adding:
$this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');
into your module service provider's boot()
method.
I would edit some typos on the post, but Akismet is flagging it as spam... wtf...?
So the thing is I can't figure out how what I'm missing when I try to create tests for a new custom module I've created and called News. This module has been created with the Asgard's builtin module scaffolder.
My experience with unit testing is quite limited, I've been able to do some basic tests, but perhaps I feel a bit overwhelmed when the application grows. I have time for the project I'm working on, and I'd really like to run unit testing in order to improve stability as well as the rest of benefits that has this kind of development.
Said that, let's explain what's going on:
All suite of tests passes, except when it's the turn of my News
module, where I get this error:
Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1 no such table: news__news_translations (SQL: select * from "news__news_translations" where "news__news_translations"."news_id" is null and "news__news_translations"."news_id" is not null)
I've used the builtin BasePageTest
from the Page
module as a base to do mine (I'd use the Blog
module tests, but seems they're slighty different of the rest of the tests on v2) , so the code of my NewsBaseTest
is as following:
<?php
namespace Modules\News\Tests;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Database\Eloquent\Model;
use Illuminate\View\ViewServiceProvider;
use Mcamara\LaravelLocalization\Facades\LaravelLocalization;
use Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider;
use Modules\Core\Providers\CoreServiceProvider;
use Modules\News\Providers\NewsServiceProvider;
use Modules\News\Repositories\NewsRepository;
use Nwidart\Modules\LaravelModulesServiceProvider;
use Orchestra\Testbench\TestCase;
abstract class BaseNewsTest extends TestCase
{
protected $news;
public function setUp()
{
parent::setUp();
$this->resetDatabase();
$this->news = app(NewsRepository::class);
}
protected function getPackageProviders($app)
{
return [
LaravelModulesServiceProvider::class,
CoreServiceProvider::class,
ViewServiceProvider::class,
NewsServiceProvider::class,
LaravelLocalizationServiceProvider::class,
];
}
protected function getPackageAliases($app)
{
return [
'Eloquent' => Model::class,
'LaravelLocalization' => LaravelLocalization::class,
];
}
protected function getEnvironmentSetUp($app)
{
$app['path.base'] = __DIR__ . '/..';
$app['config']->set('database.default', 'sqlite');
$app['config']->set('database.connections.sqlite', array(
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
));
$app['config']->set('translatable.locales', ['en', 'fr']);
}
private function resetDatabase()
{
// Relative to the testbench app folder: vendors/orchestra/testbench/src/fixture
$migrationsPath = 'Database/Migrations';
$artisan = $this->app->make(Kernel::class);
// Makes sure the migrations table is created
$artisan->call('migrate', [
'--database' => 'sqlite',
]);
// We empty all tables
$artisan->call('migrate:reset', [
'--database' => 'sqlite',
]);
// Migrate
$artisan->call('migrate', [
'--database' => 'sqlite',
]);
$artisan->call('migrate', [
'--database' => 'sqlite',
'--path' => 'Modules/News/Database/Migrations',
]);
}
}
I'm sure I'm missing something, as to migrate all the tables in my module (both news__news
and news__news_translations
), but I assume (perhaps wrongly) that this piece of code at the end:
$artisan->call('migrate', [
'--database' => 'sqlite',
'--path' => 'Modules/News/Database/Migrations',
]);
Is this piece of code above doing the thing I'm expecting?
What I'm missing?
Sorry to resurrect this topic, but it worked perfectly for my super custom treatment of uploads in special modules, which even supports different media upload for each locale/language
@nWidart
Fantastic, many thanks, I'll try the first option
Hi, just a quick question:
I'd like to make some minor adjustments to the User
module (giving always at least the permission to view the dashboard, even if the administrator forgets to give the rights to do so).
Is there any way to extend/override the default behavior of a module? It'd be a bit overkill to duplicate the module and make my own CustomUsers just for one line of code I have to change...
Same happens with Media
module. I have minor changes to distinguish between audio & video files, but I lose all my changes at each composer-update
.
I need to have the whole module in .gitignore
in order to 'rescue' my modifications, as a workaround.
So, where'd be the best place to apply this changes without breaking the flow / modularity ?
Haha, yes! We both answered at same time...
I investigated a bit and found the solution by myself... (well, actually I found this):
https://github.com/dimsav/laravel-translatable/issues/239
I also thought the updating was doing out-of-the box.
By the way... I know it's time consuming, but would be possible to make a screencast like the one published (the testimonials one), but with translations? All details like this one, can bring confusion to newcomers...
Or make a tutorial? Well, perhaps I'd be able to do by myself, but I may be wrong on some stuff as I don't know a deep knowledge of the CMS innerings...
AutoSolved:
This is not an AsgardCMS issue / question, but from how the relations are made on the Entities.
So, for others looking for this, the TestimonialTranslation
entity should look like this:
<?php namespace Modules\Testimonials\Entities;
use Illuminate\Database\Eloquent\Model;
class TestimonialTranslation extends Model
{
public $timestamps = false;
protected $fillable = [
'testimonial_id',
'title',
'content'
];
protected $table = 'testimonials__testimonial_translations';
protected $touches = ['news']; // THIS enables 'touching' (updates timestamps) on the 'parent' model.
// And we make the relation with the model we want to touch
public function testimonial()
{
return $this->belongsTo(Testimonial::class);
}
}
And that's it!
Let's see how can I explain this.
I have a Testimonials
module. This module will have translations. And I added in the TestimonialsServiceProvider
a trigger, so when I create a new testimonial, it modifies a column in a table.
The problem is, on updating. If I modify the testimonial, only the testimonials__testimonial_translations
table is affected, but the timestamps on testimonials__testimonials
are not, so there're no events fired when the model is updated/saved. I guess that's because only the TestimonialTranslation
model is affected, and not the 'parent' Testimonial
. (But I think the parent model should be aware).
Which relation I'm missing... and where do I have to put it? on the Testimonial
entity? on the TestimonialTranslation
entity? On both?
I'm a bit lost here, because I don't see a direct relation between the model and the translation model... Actually I'm missing what exactly does the (Well it does manage the translations)Dimsav\Translatable\Translable
class is doing in the entity declaration...
So that was it!!!
Removing the rules()
method didn't work:
ReflectionException in Container.php line 556:
Method Modules\News\Http\Requests\CreateNewsRequest::rules() does not exist
but removing the content of rules()
method did!
class CreateNewsRequest extends BaseFormRequest
{
protected $translationsAttributesKey = 'news::news.validation.attributes';
public function rules()
{
return [];
}
( ... and rest of code below)
Many thanks @armababy , was losing my mind trying to find out what was going on.
Hi, I've created a Form Request in order to set up validation of a custom module form. Let's call it CreateNewsRequest
If some required fields are missing, redirection goes fine and it reloads the form with the errors on it. That's the expected result.
But if all required
fields are filled (therefore it should pass validation), seems that the CreateNewsRequest
doesn't hit the controller. I have checked it out putting a dd($request)
on the controller, and it does not trigger, as the FormRequest
executes before the controller. Seems that something is going on there, but after some time, I'm starting to get lost.
AFAIK, In the controller and in the form request, all the necessary classes are properly injected.
Code from the CreateNewsRequest
:
<?php namespace Modules\News\Http\Requests;
use Modules\Core\Internationalisation\BaseFormRequest;
class CreateNewsRequest extends BaseFormRequest
{
protected $translationsAttributesKey = 'news::news.validation.attributes';
public function rules()
{
return [
'title' => 'required',
'subtitle' => 'required',
'content' => 'required'
];
}
public function translationRules()
{
return [
'title' => 'required',
'subtitle' => 'required',
'content' => 'required',
];
}
public function authorize()
{
return true;
}
public function messages()
{
return [
'title.required' => trans('news::news.messages.title'),
'subtitle.required' => trans('news::news.messages.subtitle'),
'content.required' => trans('news::news.messages.content'),
];
}
public function translationMessages()
{
return [
'title.required' => trans('news::news.messages.title'),
'subtitle.required' => trans('news::news.messages.subtitle'),
'content.required' => trans('news::news.messages.content'),
];
}
}
Code from the Controller:
<?php namespace Modules\News\Http\Controllers\Admin;
use Modules\Core\Http\Controllers\Admin\AdminBaseController;
use Modules\News\Entities\News;
use Modules\News\Http\Requests\CreateNewsRequest;
use Modules\News\Http\Requests\UpdateNewsRequest;
use Modules\News\Repositories\NewsRepository;
use Modules\Media\Repositories\FileRepository;
use Modules\Core\Contracts\Setting;
class NewsController extends AdminBaseController
{
/**
* @var NewsRepository
*/
private $news;
/**
* @var FileRepository
*/
private $file;
/**
* @var Setting
*/
private $setting;
public function __construct(NewsRepository $news, FileRepository $file, Setting $setting)
{
parent::__construct();
$this->news = $news;
$this->file = $file;
$this->setting = $setting;
}
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index()
{
$news = $this->news->all();
return view('news::admin.news.index', compact('news'));
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create()
{
return view('news::admin.news.create');
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @return Response
*/
public function store(CreateNewsRequest $request)
{
$this->news->create($request->all());
flash(trans('core::core.messages.resource created', ['name' => trans('news::news.title.news')]));
return redirect()->route('admin.news.news.index');
}
/**
* Show the form for editing the specified resource.
*
* @param News $news
* @return Response
*/
public function edit(News $news)
{
$availableLocales = json_decode($this->setting->get('core::locales'));
foreach ($availableLocales as $locale) {
$media["image_" . $locale] = $this->file->findFileByZoneForEntity('image_' . $locale, $news);
$media["video_poster_" . $locale] = $this->file->findFileByZoneForEntity('video_poster_' . $locale, $news);
$media["video_" . $locale] = $this->file->findFileByZoneForEntity('video_' . $locale, $news);
}
return view('news::admin.news.edit', compact('news', 'media'));
}
/**
* Update the specified resource in storage.
*
* @param News $news
* @param Request $request
* @return Response
*/
public function update(News $news, UpdateNewsRequest $request)
{
$this->news->update($news, $request->all());
flash(trans('core::core.messages.resource updated', ['name' => trans('news::news.title.news')]));
return redirect()->route('admin.news.news.index');
}
/**
* Remove the specified resource from storage.
*
* @param News $news
* @return Response
*/
public function destroy(News $news)
{
$this->news->destroy($news);
flash(trans('core::core.messages.resource deleted', ['name' => trans('news::news.title.news')]));
return redirect()->route('admin.news.news.index');
}
}
And finally, code from the Resources\Lang\en\news.php
:
<?php
return [
'title' => [
'news' => 'News',
'create news' => 'Create a news article',
'edit news' => 'Edit a news article',
],
'button' => [
'create news' => 'Create news article',
],
'table' => [
'title' => 'Title',
'subtitle' => 'Subtitle',
'content' => 'Content',
'isTop' => 'Top news'
],
'form' => [
'title' => 'Title',
'subtitle' => 'Subtitle',
'content' => 'Content',
'isTop' => 'First news in feed?'
],
'messages' => [
'title' => 'A title is required',
'subtitle' => 'A subtitle is required',
'content' => 'Content is required'
],
'validation' => [
'attributes' => [
'title' => 'title',
'subtitle' => 'subtitle',
'content' => 'content'
]
],
];
The view form create-fields
section, quite straightforward:
<div class="box-body">
{!! Form::i18nInput('title', trans('news::news.form.title'), $errors, $lang) !!}
{!! Form::i18nInput('subtitle', trans('news::news.form.subtitle'), $errors, $lang) !!}
{!! Form::i18nTextarea('content', trans('news::news.form.content'), $errors, $lang) !!}
{!! Form::i18nCheckbox('is_top', trans('news::news.form.isTop'), $errors, $lang) !!}
</div>
And finally the Entities' code:
<?php
namespace Modules\News\Entities;
use Dimsav\Translatable\Translatable;
use Illuminate\Database\Eloquent\Model;
use Modules\Media\Support\Traits\MediaRelation;
class News extends Model
{
use Translatable, MediaRelation;
protected $table = 'news__news';
public $translatedAttributes = [
'news_id',
'title',
'subtitle',
'content'
];
protected $fillable = [
'news_id',
'title',
'subtitle',
'content',
'is_top',
];
}
and
<?php namespace Modules\News\Entities;
use Illuminate\Database\Eloquent\Model;
class NewsTranslation extends Model
{
public $timestamps = false;
protected $fillable = [
'news_id',
'title',
'subtitle',
'content',
'is_top'
];
protected $table = 'news__news_translations';
public function getContentAttribute($value)
{
return strip_tags($value);
}
}
I used the Page
module as a scaffold in order to write this custom module, as well as following Laravel's 5.1 documentation, and I'm running out of ideas as I'm not seeing any possible explanation... It worked (models were saved and updated) when I used the base Request instead of my FormRequest... Now seems to catch the form errors, but even without errors it does not either validate.
WTH I'm missing here?
Yes, I tried to do so following Laravel's documentation and it works that way indeed.
More than looking for a helper, I asked the question while doing it, just in case I had to do anything specific, being the app module based.
Thanks!
I'd like to know if there're some special considerations in order to create a queueable job for an specific module.
This module does the following:
PlaylistWasCreated
event is triggered and the playlist is sent to a TCP Port via sockets.Between step 2 and 3 is where I want to implement the Queuing, as I want to get the user back to the Playlists index, and not making him to wait until the stuff that does the socket thing ends (it concatenates videos and this action can take a long time).
Should I follow the Laravel's vanilla approach to create the Job class? Or should I have in account anything special in order to do so? I haven't find any requirements or information in the PingpongSky documentation about it.
Fantastic, thanks!
About that pull request, I'd certainly love to do so, but I'm a bit busy on the project I'm working on right now.
If possible, in a future, I will try to "untie" this dependency from the core modules, and letting this to be configurated from the theme, but I'll do with a vanilla installed CMS.
All right, understood.
A last question: about all those classes and JavaScript into the modules' views, then the way to go and to mantain it into a repo, affecting the less any potential composer update
would be to publish all modules' views ?
All right, so I should remove the core's macros.php
from .gitignore
and switching the class I need there, isn't it?
Alternatively, I've created a new theme over AdminLTE theme, but some stuff like the icheck
is still a bit dependant of some core files.
Hm... not sure if I get it.
The problem I'm facing is that if I change the backend theme, I'm tied to use the icheck.blue.css assets, and I'm not being able to change them unless I modify all core files that references to icheck-blue (those in views, and the asset pipeline in controllers).
Makes this any sense? I'm missing a workaround?
(The only workaround I've found so far is to edit the asgard.core.core.php
and give a faux identifier for icheck.blue.css
, and pointing it to the green.css
skin, but even with that I have to change flat-blue
class by flat-green
class).