Wed Oct 16
can
method is on the user as authorization always attaches to the current logged in userThe following specifies checking the sendReceipt()
method in the OrderPolicy
(new Actions\SendReceipt)->canSee(function ($request) {
return $request->user()->can('sendReceipt', \App\Models\Order::class);
})->canRun(function ($request, $order) {
return $request->user()->can('sendReceipt', \App\Models\Order::class);
}),
Thu Oct 10
How to generate EPUB documentation for Elixir v1.6
mix archive.uninstall phx_new-1.4.10
) or conflicts happen during the build processgit clone https://github.com/phoenixframework/phoenix.git
cd phoenix
git checkout v1.4 (or next completed)
mix deps.get
mix docs --formatter epub
git clone https://github.com/elixir-lang/elixir-lang.github.com.git
git clone https://github.com/elixir-lang/elixir.git
cd elixir
nano Makefile
#==> Documentation tasks
# DOCS_FORMAT = html
DOCS_FORMAT = epub
make docs
cp doc/elixir/Elixir.epub
Tue Oct 8
IEx testing a file
import_file("lib/knockin/tasks/stack_overflow_query.ex")
data = %Knockin.Tasks.StackOverflowQuery{}
Knockin.Tasks.StackOverflowQuery.get_base_url
Sort order
Collections
Enum - Set of algorithms for enumerating over enumerables
all?
- Returns true if supplied callback applies to all valuesany?
- Returns true if supplied callback applies to a single valuechunk_every
chunk_by
map_every
each
map
min
max
filter
reduce
sort
uniq
uniq_by
Pattern matching - allows us to match simple values, data structures, and even functions
Control structures
if
and unless
- if looks for truthy, unless is the inverse and defaults to falsecase
- match against multiple patterns. _ required in the event there is no match (like default in other languages but required, unlike others). Allows for guard clausescond
- Match conditions instead of values, akin to else if from other languageswith
- (Need to look this up)Tue Oct 8
nova:publish
as Nova updates via composer. It's easier, if using composer, to run the commands on update.Wed Oct 2
Structs
%Person{}
right for the first answer? I looked at the code, not the text to answer it.Pattern Matching
assert_raise MatchError, fn ->
[a, b] = [1, 2, 3]
end
koan "The pattern can make assertions about what it expects" do
assert match?([1, _second, _third], [1, 2, 3])
end
Functions
koan "Functions can have default argument values" do
assert repeat_again("Hello ") == "Hello Hello Hello Hello Hello "
assert repeat_again("Hello ", 2) == "Hello Hello "
end
koan "You can build anonymous functions out of any elixir expression by prefixing it with &" do
three_times = &[&1, &1, &1]
assert three_times.("foo") == ["foo", "foo", "foo"]
end
koan "You can pass functions around as arguments. Place an '&' before the name and state the arity" do
assert times_five_and_then(2, &square/1) == 100
end
Enums
koan "Elements can have a lot in common" do
assert Enum.all?([1, 2, 3], &less_than_five?/1) == true
assert Enum.all?([4, 6, 8], &less_than_five?/1) == false
end
koan "You three there, follow me!" do
assert Enum.take([1, 2, 3, 4, 5], 3) == [1, 2, 3]
end
koan "Zip-up in pairs!" do
letters = [:a, :b, :c]
numbers = [1, 2, 3]
assert Enum.zip(letters, numbers) == [a: 1, b: 2, c: 3]
end
koan "When you want to find that one pesky element" do
assert Enum.find([1, 2, 3, 4], &even?/1) == 2
end
Thu Oct 3
Processes
koan "You can ask a process to introduce itself" do
information = Process.info(self())
assert information[:status] == :running
end
Agents
GenServers
koan "Let's use the remaining functions in the external API" do
Laptop.start_link("EL!73")
{_, response} = Laptop.unlock("EL!73")
assert response == "Laptop unlocked!"
Laptop.change_password("EL!73", "Elixir")
{_, response} = Laptop.unlock("EL!73")
assert response == "Incorrect password!"
{_, response} = Laptop.owner_name()
assert response == "Jack Sparrow"
end
Protocols
Comprehensions
koan "A generator specifies how to extract values from a collection" do
collection = [["Hello","World"], ["Apple", "Pie"]]
assert (for [a, b] <- collection, do: "#{a} #{b}") == ["Hello World", "Apple Pie"]
end
koan "You can use multiple generators at once" do
assert (for x <- ["little", "big"], y <- ["dogs", "cats"], do: "#{x} #{y}") == ["little dogs", "little cats", "big dogs", "big cats"]
end
Fri Sep 27
Customize download file name using record information.
/**
* Get the actions available for the resource.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function actions(Request $request)
{
$fileName = $this->getDownloadFileName($request);
return [
(new DownloadExcel)->withFilename($fileName)
->withWriterType(\Maatwebsite\Excel\Excel::CSV)
->withName('Download CSV')
->withHeadings()
->only('code')
->onlyOnIndex()
->withoutConfirmation(),
];
}
/**
* Get the filename for the DownloadExcel action.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
private function getDownloadFileName(Request $request)
{
$result = 'codes-' . time() . '.csv';
try {
$batch = $request->findModelQuery()->getParent();
$result = \Str::slug($batch->codeType->name) . "-{$batch->id}-codes.csv";
} catch (\Exception $exception) {
}
return $result;
}
Mon Aug 12 2019
selectedBucket
, if you emit an event named selectedBucketChanged
you'll see the event emitted twice. I believe it's because there are life cycle events for any prop that are emitted for reactivity that I just happened to trample on.Tue Jul 30
Laravel streaming upload from one filesystem (local/legacy) to another (spaces).
$resource = \Storage::disk('legacy')->readStream($source);
$status = \Storage::disk($this->disk)->put($destination, $resource, 'public');
$sourceSize = \Storage::disk('legacy')->size($source);
$destinationSize = \Storage::disk($this->disk)->size($destination);
Wed Jul 31
cloud
doesn't really coerce down to spaces
or s3
../artisan convert:material-assets -d spaces --no-progress --reset
Thu Aug 1
Chrome overrides to enable Vue devtools.
Sources
tab.>>
and select Overrides
.+ Select folder for overrides
.Allow
.Page
under the sources tab to show the files for the page.Save for overrides
. This places the file in a site/folder
location.{}
to pretty print the javascript.devtools
and hit enter. productionTip: !1,
devtools: !1,
performance: !1,
Mon Jul 15 2019
Json field queries
SELECT
*, `payload`->"$[0].eCommerceOrderId" AS `e_commerce_order_id`
FROM
pendingOrderStatus
WHERE
`status` = 'sent';
SELECT
id, `order`->>'$.e_commerce_order_id' AS `e_commerce_order_id`
FROM
order_log_transforms
WHERE
`order`->>'$.e_commerce_order_id' IS NOT NULL;
Wed Jul 17 2019
Importing from the source table in one database to the destination in another database.
INSERT INTO `materials-staging`.`files` (`id`, `path`, `type`, `hash`, `material_id`, `active`, `created_at`, `updated_at`, `deleted_at`)
SELECT
*
FROM
`materials_staging`.`mat_files`;
Mon Jul 8
storage/app/public/images/HELL/originalFileName.extension
. The image column saves only the name as it already appears.$this->model()->path
. At a resource level we can get the model columns used because it's defined as $this->model. Because we're on the detail view the path in this case is filled in. We don't need methods that include the models.This is one of the biggest "aha!" moments with Nova where the docs aren't very intuitive nor do they promote this in any meaningful way in the examples I've seen.
Image::make('Header Image', 'image')
->disk('public')
->store(function (Request $request, \App\Models\Bucket $model) {
$path = "images/{$model->path}";
$file = $request->image->storeAs($path, $request->image->getClientOriginalName(), 'public');
return [
'image' => $request->image->getClientOriginalName(),
];
})
->prunable()
->preview(function ($image, $disk) {
return $image ? \Storage::disk($disk)->url("images/{$this->model()->path}/$image") : null;
})
->thumbnail(function ($image, $disk) {
return $image ? \Storage::disk($disk)->url("images/{$this->model()->path}/$image") : null;
}),
Tue Jul 9
getPathAttribute()
essentially creates our path value in the same way from our slug and it's only used during the create model upload.This is sleight of hand at its finest, the real path column is what we're looking for but we're hacking in functionality to work at the create form level, bypassing the need to restrict this to only during edits.
public function getPathAttribute()
{
$path = (array_has($this->attributes, 'path') && $this->attributes['path']) ? $this->attributes['path'] : null;
// We only care to calculate the path in the same manner as our creating method when the model is first used
if ($path === null) {
$slug = (array_has($this->attributes, 'slug') && $this->attributes['slug']) ? static::stripSpecialCharacters($this->attributes['slug']) : null;
return $slug;
}
return $path;
}
protected static function boot()
{
parent::boot();
static::created(function(Bucket $bucket) {
$bucket->path = static::stripSpecialCharacters($bucket->slug);
$bucket->save();
});
}
Wed Jul 10
Filter the specific keys to look for:
> keys horizon:queue*
1) "horizon:queues:orders:transfer:reserved"
2) "horizon:queue:orders:transform"
3) "horizon:queue:orders:transfer"
4) "horizon:queues:orders:transfer"
Get the type of our keys:
> type "horizon:queues:orders:transfer:reserved"
zset
> type "horizon:queue:orders:transform"
hash
> type "horizon:queue:orders:transfer"
hash
> type "horizon:queues:orders:transfer"
list
> type "horizon:job:App\\Jobs\\PendingOrderTransformJob"
hash
> type "horizon:job:App\\Jobs\\PendingOrderTransferJob"
hash
Look at the values for the specific keys:
> zrange "horizon:queues:orders:transfer:reserved" 0 2 WITHSCORES
1) "{\"type\":\"job\",\"timeout\":null,\"tags\":[\"App\\\\Model\\\\PendingOrderStatus:1258\"],\"id\":\"2520\",\"data\":{\"command\":\"O:32:\\\"App\\\\Jobs\\\\PendingOrderTransferJob\\\":8:{s:9:\\\"\\u0000*\\u0000status\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":3:{s:5:\\\"class\\\";s:28:\\\"App\\\\Model\\\\PendingOrderStatus\\\";s:2:\\\"id\\\";i:1258;s:10:\\\"connection\\\";s:5:\\\"mysql\\\";}s:6:\\\"\\u0000*\\u0000job\\\";N;s:10:\\\"connection\\\";s:13:\\\"redis:horizon\\\";s:5:\\\"queue\\\";s:15:\\\"orders:transfer\\\";s:15:\\\"chainConnection\\\";N;s:10:\\\"chainQueue\\\";N;s:5:\\\"delay\\\";N;s:7:\\\"chained\\\";a:0:{}}\",\"commandName\":\"App\\\\Jobs\\\\PendingOrderTransferJob\"},\"displayName\":\"App\\\\Jobs\\\\PendingOrderTransferJob\",\"timeoutAt\":1562789478,\"pushedAt\":\"1562787678.5943\",\"maxTries\":null,\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"attempts\":3}"
2) "1562788306"
> hgetall "horizon:queue:orders:transfer"
1) "throughput"
2) "52"
3) "runtime"
4) "5623.0248076923099"
> lrange "horizon:queues:orders:transfer" 0 99
...
47) "{\"type\":\"job\",\"timeout\":null,\"tags\":[\"App\\\\Model\\\\PendingOrderStatus:1280\"],\"id\":\"2564\",\"data\":{\"command\":\"O:32:\\\"App\\\\Jobs\\\\PendingOrderTransferJob\\\":8:{s:9:\\\"\\u0000*\\u0000status\\\";O:45:\\\"Illuminate\\\\Contracts\\\\Database\\\\ModelIdentifier\\\":3:{s:5:\\\"class\\\";s:28:\\\"App\\\\Model\\\\PendingOrderStatus\\\";s:2:\\\"id\\\";i:1280;s:10:\\\"connection\\\";s:5:\\\"mysql\\\";}s:6:\\\"\\u0000*\\u0000job\\\";N;s:10:\\\"connection\\\";s:13:\\\"redis:horizon\\\";s:5:\\\"queue\\\";s:15:\\\"orders:transfer\\\";s:15:\\\"chainConnection\\\";N;s:10:\\\"chainQueue\\\";N;s:5:\\\"delay\\\";N;s:7:\\\"chained\\\";a:0:{}}\",\"commandName\":\"App\\\\Jobs\\\\PendingOrderTransferJob\"},\"displayName\":\"App\\\\Jobs\\\\PendingOrderTransferJob\",\"timeoutAt\":1562789480,\"pushedAt\":\"1562787680.4057\",\"maxTries\":null,\"job\":\"Illuminate\\\\Queue\\\\CallQueuedHandler@call\",\"attempts\":3}"
> hgetall "horizon:job:App\\Jobs\\PendingOrderTransferJob"
1) "throughput"
2) "7"
3) "runtime"
4) "7738.0300000000007"
Dump our entire key list (the -n
option specifies the database):
> redis-cli -n 3 keys "*" > horizon.keys
horizon:snapshot:job:App\\Jobs\\PendingOrderTransformJob
horizon:supervisor:orange-business-rules-swap-f6S1:supervisor-order-transform
horizon:job:App\\Jobs\\PendingOrderTransformJob
horizon:failed_jobs
horizon:snapshot:job:App\\Jobs\\PendingOrderTransferJob
horizon:masters
horizon:job_id
horizon:queues:orders:transfer:reserved
horizon:snapshot:queue:orders:transform
horizon:supervisor:orange-business-rules-swap-f6S1:supervisor-default
horizon:supervisor:orange-business-rules-swap-f6S1:supervisor-order-transfer
horizon:supervisors
horizon:recent_failed_jobs
horizon:master:orange-business-rules-swap-f6S1
horizon:job:App\\Jobs\\PendingOrderTransferJob
horizon:measured_jobs
horizon:queue:orders:transform
horizon:measured_queues
horizon:recent_jobs
horizon:queue:orders:transfer
horizon:last_snapshot_at
horizon:monitor:time-to-clear
horizon:snapshot:queue:orders:transfer
horizon:metrics:snapshot
horizon:queues:orders:transfer
horizon:id
or horizon:failed*
.Thu Jul 11
Do not use Redis::throtle()
with the retryUntil()
method.
Redis::throttle($key)->allow(1)->every(60)->then(function () {
$this->execute();
}, function () {
// Could not obtain lock...
return $this->release(10);
});
/**
* Determine the time at which the job should timeout.
*
* @return \DateTime
*/
public function retryUntil()
{
return now()->addMinutes(60);
}
Fri Jul 12
Setting a redis override for our custom systemd configuration
sudo systemctl edit redis-server
The command opens nano to an override file in /etc/systemd/system where we paste the following:
[Service]
ReadWriteDirectories=-/mnt/orange-business-rules/var/lib/redis
Thu Jul 11
Maps
Map.fetch(@person, :age)
returns {:ok, value}MapSets
MapSet.size(new_set)
returns an integer for the size (think array length in JavaScript)MapSet.member?(@set, 3)
returns a booleanMapSet.new(@set, fn x -> 3 * x end)
creates a new set using the function results of 3 * (each value in the set).MapSet.to_list(@set)
looks for brackets not braces. I confused a list with a tuple.Tue Jul 2
This wasn't quite as easy to grok without attaching a debugger but this makes complete sense now.
/**
* Perform the action on the given models.
*
* @param \Laravel\Nova\Fields\ActionFields $fields
* @param \Illuminate\Support\Collection $models
* @return mixed
*/
public function handle(ActionFields $fields, Collection $models)
{
$count = 1;
foreach ($models as $model) {
$count = $this->generateCodes($model->id, $model->code_type_id, $fields->number);
}
return Action::message("$count codes were successfully created.");
}
Sat Jun 29
.gitignore
for VSCode integration and to remove /config/*.secret.exs
. It's unclear if this was present in the earlier version of Phoenix and I weirdly need to add it. It's also possible that I just followed along with the up and running guide.Wed June 26
How Laravel prevents your scheduled jobs from overlapping - Diving Laravel
obr-sage-sync
scheduler was broken in production due to network outages affecting Digital Ocean droplets. The sync:customers
command would not execute via the scheduler because withoutOverlap()
detected the command was still executing.Fri Jun 28
Started over with Phoenix 1.4.8 using asdf global packages for the following:
elixir 1.9.0 (set by /Users/jbrayton/.tool-versions)
erlang 22.0.4 (set by /Users/jbrayton/.tool-versions)
nodejs 12.5.0 (set by /Users/jbrayton/.tool-versions)
Essentially, I changed the shim for node to:
#!/usr/bin/env bash
# asdf-plugin: nodejs 10.15.3
# asdf-plugin: nodejs 8.11.4
# asdf-plugin: nodejs 12.5.0
exec $(brew --prefix asdf)/bin/asdf exec "npm" "$@"
/usr/local/opt/asdf/bin/asdf
and seems to work for the other packages. It could be that I was trying to do a local install before a global.