It turns out, all that was required to resolve the error was to quit and restart the running ember server command. Apparently the live reload server doesn’t include newly installed addons―thanks to a GitHub comment for the hint!
]]>Unlike when using the @section Blade directives, there can be as many @push('name') directives with the same name as you need. Using the @stack('name') directive in place of @yield , will then pop them all out at once in a single location in your template (in reverse order).
Suppose you’re using jQuery and that you have two partial views that each need a bit of JavaScript to run on page load:
<!-- From first partial --> <div class="must-init"> <p>Sample</p> </div> <!-- From second partial --> <div class="needs-setup"> <p>Example</p> </div> <script> $(function () { // Required by first partial $('.must-init').initialise(); // Required by second partial $('.needs-setup').setup(); )}; </script>
Adding the scripts adjacent to the views is problematic, because likely jQuery won’t be loaded when they are executed. You could try to always remember to yield two different sections in the main layout view, but that is rather error prone.
By stacking the script sections in your views, the same output that you’d create above if you weren’t using partials is easy to achieve. First, @push the script needed by each view onto the stack:
<!-- From first partial --> <div class="must-init"> <p>Sample</p> </div> @push('readyscripts') // Required by first partial $('.must-init').initialise(); @endpush
<!-- From second partial --> <div class="needs-setup"> <p>Example</p> </div> @push('readyscripts') // Required by second partial $('.needs-setup').setup(); @endpush
You can’t use @yield to insert the pushed scripts into another template, since they weren’t defined in @section directives. Instead, simply use @stack in otherwise the same way:
<script> $(function () { @stack('readyscripts') }); </script>
One potentially important caveat to keep in mind is that since this is indeed a stack, the last section that you push onto it will be the first to appear in your rendered output. So instead of obtaining an exact formulation of the code we initially wanted, in this example we’d end up with something like this:
<!-- From first partial --> <div class="must-init"> <p>Sample</p> </div> <!-- From second partial --> <div class="needs-setup"> <p>Example</p> </div> <script> $(function () { // Required by *second* partial $('.needs-setup').setup(); // Required by *first* partial $('.must-init').initialise(); )}; </script>
]]>
In the simple version, the new enum type contains (at least) all of the same labels as the old one. For instance, as with these two:
CREATE TYPE old_enum AS ENUM ('a', 'b', 'c', 'd'); CREATE TYPE new_enum AS ENUM ('a', 'b', 'c', 'd', 'e');
If a table has an old_enum column and we want to turn it into a new_enum one, with no default value in place on the column, we can use the following command:
ALTER TABLE table_name ALTER COLUMN column_name SET DATA TYPE new_enum USING column_name::text::new_enum;
The USING expression casts the current value of column_name to text, and then to new_enum. This works because every allowed value of the first enum type exists in the second.
This case is not significantly more difficult to deal with. If there is a default value on the column, we simply remove it before altering the enum type of the column, and then add a new one when we’re done:
ALTER TABLE table_name ALTER COLUMN column_name DROP DEFAULT, ALTER COLUMN column_name SET DATA TYPE new_enum USING column_name::text::new_enum, ALTER COLUMN column_name SET DEFAULT 'a';
A more complicated scenario arises when not all of the old labels appear in the new enum. I’ll assume that there is a mapping from the old ones to the new, at least for every label that is known to appear in a row of the table. If there isn’t, then the conversion is probably not a good idea in the first place.
Consider now an even newer type,
CREATE TYPE newer_enum AS ENUM ('alpha', 'beta', 'c', 'd', 'e');
We still want to convert from the old_enum type, but now we also want to map the label ‘a’ to ‘alpha’, and ‘b’ to ‘beta’, while leaving ‘c’ and ‘d’ alone. This can be accomplished by manually applying each required change via a CASE statement in the USING expression:
ALTER TABLE table_name ALTER COLUMN column_name DROP DEFAULT, ALTER COLUMN column_name SET DATA TYPE newer_enum USING ( CASE column_name::text WHEN 'a' THEN 'alpha' WHEN 'b' THEN 'beta' ELSE column_name::text END CASE )::newer_enum, ALTER COLUMN column_name SET DEFAULT 'alpha';
For each record, this statement returns ‘alpha’ or ‘beta’ if the column contains ‘a’ or ‘b’, respectively, or otherwise returns the current value in the column cast as text. The returned value in all cases is then cast to the newer enum type.
]]>{ "widgets": [ { "id": 1, "foo_ids": [ 1 ] }, { "id": 3, "foo_ids": [ ] }, { "id": 2, "foo_ids": [ 1, 2, 3 ] } ] }
To easily and automatically add this capability to any model I wanted, I created a new class extending Cake’s Table , which I called “ApiTable”:
class ApiTable extends Table { public function findForApi (...) { ... } public function formatWithJoinedIds (...) { ... } }
The formatWithJoinedIds() method adds an array called model_ids for each model, from an input list, that is associated with this one through a ‘BelongsToMany’ or ‘HasMany’ join, without including the contents of the associated models. It is intended to be called from the findApi() method, which simply performs a query containing the passed-in desired list of models and formats the results.
The custom finder is straightforward:
public function findForApi (Query $query, array $includeIdsFor) { $query ->contain($includeIdsFor) ->formatResults([$this, 'formatWithJoinedIds']); return $query; }
The array $includeIdsFor should contain strings identifying the associated properties whose joined ids should be included. For instance, if a Widget has many associated Foos, Bars, and Bazzes, the ids of the foos and bars would be included in the output by passing ['Foos', 'Bars'].
The result formatter, used to add the calculated fields containing the id arrays, is a little longer but not much more complicated:
public function formatWithJoinedIds ($results, $keepJoinedData) { // Array of all associations this model has many models of $joins = array_merge( $this->associations()->type('BelongsToMany'), $this->associations()->type('HasMany'), [] ); // Map each result row to itself, with the array id fields // added, and optionally foreign model data removed return $results->map( function ($row) use ($joins, $keepJoinedData) { foreach ($joins as $join) { $property = $join->property(); if (isset($row[$property])) { // Start a new id array for this property $ids = []; foreach ($row[$property] as $joinedToRow) { $ids[] = $joinedToRow['id']; } // Turn e.g. 'foos' into 'foo_ids', and add this // as a new property of the row $row[Inflector::singularize($property) . '_ids'] = $ids; if (!$keepJoinedData) { // Optionally remove the associated data unset($row[$property]); } } } return $row; } ); }
This routine generates an array of all properties of the current model that point to many of another model. It then iterates over these properties, and for each one iterates over all of the foreign models to collect an array of their ids. The resulting array is added as a new property to the row, and the foreign models themselves are unset if they are not to be included in the output.
]]>