Walther Diechmann
Joined
Activity
Yeah - turns out I was overthinking it - the browser/device apparently does the house-keeping themselves - so I took it in another direction. Scold me for littering if you must - here is the Stimulus controller that emerged from the thick of the fight
import { Controller } from "@hotwired/stimulus"
import { enter, leave } from "el-transition"
// Connects to data-controller="profile"
export default class extends Controller {
static targets = [
"buttonForm",
"enableNotifications",
"disableNotifications"
]
registration = null
connect() {
navigator.serviceWorker.register("/service-worker.js").then(
(reg) => {
this.registration = reg;
if (!this.registration) return;
this.showPromptForNotifications();
},
(err) => {
console.error("Service Worker registration failed: ", err);
}
);
}
enable(event) {
event.preventDefault()
if (!this.registration) return;
this.buttonFormTarget.ariaBusy = true;
Notification.requestPermission()
.then((permission) => {
if (permission === "granted") {
this.setupSubscription();
} else {
console.log("Notifications declined");
}
})
.catch((error) => console.log("Notifications error", error));
}
disable(event) {
event.preventDefault()
console.log("Disable notifications")
if (!this.registration) return;
let r = this.registration
let en = this.enableNotificationsTarget
let dn = this.disableNotificationsTarget
this.registration.pushManager.getSubscription().then(
async function (subscription) {
if (!subscription) return;
await fetch("/web_push_subscriptions?unsubscribe=true", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(subscription),
}).then((response) => {
if (response.ok) {
subscription.unsubscribe()
r = null
leave(dn).then(() => {
enter(en)
})
}
}).catch(function (e) {
console.log("Server unsubscription setup failed", e);
})
})
}
// Show a prompt to the user to subscribe to notifications
showPromptForNotifications() {
this.registration.pushManager.getSubscription().then(
(subscription) => {
if (subscription) {
this.setupSubscription(subscription);
} else {
enter(this.enableNotificationsTarget)
}
},
(err) => {
console.log("Error getting subscription: ", err);
}
);
}
//
// Setup the subscription with the push server - provided the user has granted permission
async setupSubscription(subscription = null) {
if (Notification.permission !== "granted" && !subscription) return;
if (!subscription) {
let vapid = new Uint8Array(
JSON.parse(document.querySelector("meta[name=web_push_public]")?.content)
);
subscription = await this.registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: vapid,
});
}
await fetch("/web_push_subscriptions", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(subscription),
}).then((response) => {
if (response.ok) {
leave(this.enableNotificationsTarget).then(() => {
enter(this.disableNotificationsTarget);
});
} else {
console.log("Subscription setup failed");
}
});
}
}
I use the web_push strategy in Noticed V2 - and it's a kicker - my +1 on that, really!
But I must be doing something wrong b/c everytime a user logs into the app hem is requested (by the service-worker) to accept receiving notifications. IMHO the user should only be facing that decision once (and then on a dashboard or profile have an unsubscribe
button)
My question boils down to: how do I persist the user's decision to subscribing to notifications on the client - as it apparently is not enough to
await fetch("/web_push_subscriptions", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(subscription),
});
thx again for a real gem Chris!
Posted in Setup MacOS 11.0 Big Sur Discussion
this is ruining my day totally :(
dyld: lazy symbol binding failed: Symbol not found: _mysql_server_init
Referenced from: /Users/walther/.asdf/installs/ruby/3.0.1/lib/ruby/gems/3.0.0/gems/mysql2-0.5.3/lib/mysql2/mysql2.bundle
Expected in: flat namespace
I've single-handedly restored the russian economy (judged by the amount of energy/gas I've spent googling, reinstalling everything but the macOS)
has anyone had success installing the arm64 mysql server using brew on Big Sur 11.6.5 (I did) followed by installing the mysql2 gem (I didn't) ?
I've collected quite a set of settings that you may browse here
Last update: getting my associations straight!
Well - like I more or less anticipated - the devil was lurking if not in the detail then in the assignment :big_smile:
Going back to the drawing board (and with a kind push from https://twitter.com/kaspth) I realized that I had been too generous with the delegated_typ'ing
class Assignment < AbstractResource
belongs_to :event, inverse_of: :assignments
belongs_to :assignable, polymorphic: true, required: true
accepts_nested_attributes_for :assignable
when in fact assignments are nothing but mere (polymorphic) mortals :smile:
Now I could finally -- hunting this down for the better part of a week (jeez I'm getting too old for this game) -- have my abstracted_away controller (which will allow me to focus on all the crazy exciting methods and just c/p the "standard" controller stuff from inherited controllers like this (and I don't think it gets any thinner)
class TasksController < EventsController
def new_resource
resource_class= Task
super
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def resource_params
params.require(:event).permit(:name, :account_id, :eventable_type, eventable_attributes: [ :duration ], assignments_attributes: [ :id, :assignable_id, :assignable_type, :assignable_role, :_destroy ] )
end
#
# implement on every controller where search makes sense
# geet's called from resource_control.rb
#
def find_resources_queried options
Task.search Task.all, params[:q]
end
end
Happy to report that all is - again - quiet on the West front (as opposed to the East front at the moment I'm afraid -- slava Ukraine btw)
Update: right about the ugliest method - where's the sugar?
I knew that no primary_key had this "wrong arguments" odour so it was a no-brainer to feed the beast some proper arguments
def create
r = resource_params.tap {|ary| ary.delete :eventable_attributes }
r = r.tap {|ary| ary.delete :assignments_attributes}
r = Event.new r
r.eventable = Task.new resource_params[:eventable_attributes]
if r.valid?
r.save
resource_params[:assignments_attributes].each do |k,a|
r.assignments << Assignment.create( event: r, assignable_type: a["assignable_type"], assignable_id: a["assignable_id"], assignable_role: a["assignable_role"])
end
end
end
so - it's a "done deal" (except it's nothing of the kind) but this was totally not what I expected from delegated_type (and I'm fully aware that I'm getting punished by my own sword)
Update: every little step -- closer and yet so distant
Working on the issue had me redo the #create
def create
r = resource_params.tap {|ary| ary.delete :eventable_attributes }
r = r.tap {|ary| ary.delete :assignments_attributes}
r = Event.new r
r.eventable = Task.new resource_params[:eventable_attributes]
if r.valid?
r.save
resource_params[:assignments_attributes].each do |k,a|
r.assignments << Assignment.create( event: r, assignable: a)
end
end
@resource= r
end
not in any way near my expectations - and it still won't let me off the hook
r.assignments << Assignment.create( event: r, assignable: a)
(telling me that there is undefined method `primary_key' for ActionController::Parameters:Class)
Update: inching my way through this - -
I decided to let the code rest while I finished Drive To Survive Season 4 which to a certain degree proved beneficial!
Tearing it all apart, I did:
def create_resource
r = resource_params.tap {|ary| ary.delete :eventable_attributes }
r = r.tap {|ary| ary.delete :assignments_attributes}
r = Event.new r, eventable: resource_params[:event][:eventable_attributes]
r.assignments.build resource_params[:event][:assignments_attributes]
end
which left me with this beauty:
hours-hours-1 | 22:58:01 web.1 | NoMethodError (undefined method `[]' for nil:NilClass):
hours-hours-1 | 22:58:01 web.1 |
hours-hours-1 | 22:58:01 web.1 | app/controllers/tasks_controller.rb:33:in `create_resource'
() which at least left me with something to chase!
Update: adding 'events' in the CLI
I "adjusted" the models somewhat (and added the eventable
module for completeness, now looking like this:
class Event < ApplicationRecord
belongs_to :calendar, optional: true
has_many :assignments, inverse_of: :event
delegated_type :eventable, types: %w[ Call Task Meeting], dependent: :destroy
accepts_nested_attributes_for :eventable, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :assignments, reject_if: :all_blank, allow_destroy: true
end
class Task < Event
include Eventable
has_many :assignments, through: :event, inverse_of: :assignable
end
module Eventable
extend ActiveSupport::Concern
included do
has_one :event, as: :eventable, touch: true, dependent: :destroy
end
end
class Assignment < ApplicationRecord
belongs_to :event, inverse_of: :assignments
delegated_type :assignable, types: %w[ Participant Asset ]
end
class TasksController < EventsController
def new_resource
@event = Event.new eventable: Task.new
@event.assignments.new( assignable: (Employee.find(params[:employee_id]) rescue nil))
end
def create
@event = Event.new resource_params
end
private
# Never trust parameters from the scary internet, only allow the white list through.
def resource_params
params.require(:event).permit(:name, :account_id, eventable_attributes: [ :id, :duration ], assignments_attributes: [ :id, :assignable_id, :assignable_type, :assignable_role, :_destroy ] )
end
end
Further, I "recalibrated" the form to now produce this kind of params:
Started POST "/tasks" for 172.25.0.1 at 2022-03-29 17:40:30 +0000
hours-hours-1 | 17:40:30 web.1 | Processing by TasksController#create as TURBO_STREAM
hours-hours-1 | 17:40:30 web.1 | Parameters: {"authenticity_token"=>"[FILTERED]", "event"=>{"account_id"=>"1", "name"=>"dfghjklæ", "eventable_attributes"=>{"duration"=>"110"}, "assignments_attributes"=>{"0"=>{"assignable_type"=>"Employee", "assignable_id"=>"54", "assignable_role"=>"owner"}}}}
(the somewhat 'funny' log is due to me running this all off a set of Docker containers)
While having the tasks_controller#create
method doing the heavy-lifting still has quite a while to go - I've managed to fiddle the CLI into persisting a task by doing this:
irb(main):041:0> event = Event.new account_id: Account.first.id, name: "test", eventable: Task.new(duration: 100)
irb(main):044:0> event.assignments << Assignment.create( event: event, assignable: Employee.last, assignable_role: "owner")
Now - if only I could make the
irb(main):031:0> Event.new params["event"]
/usr/local/bundle/gems/activemodel-7.0.0/lib/active_model/attribute_assignment.rb:51:in `_assign_attribute': unknown attribute 'eventable_attributes' for Event. (ActiveModel::UnknownAttributeError)
go away - and take the log barfing up
hours-hours-1 | 17:40:30 web.1 | NoMethodError (undefined method `constantize' for nil:NilClass):
hours-hours-1 | 17:40:30 web.1 |
hours-hours-1 | 17:40:30 web.1 | app/controllers/tasks_controller.rb:30:in `create_resource'
with it - I'd be home free :big_smile:
I have this marvelous complicated DB design that goes along these lines: events ( like calls, tasks, and meetings ) can have a number of assignees (Participants, and Assets) - like a Team Meeting can have a meeting room and the entire team of employees in team A assigned.
That one/two line 'use case' really has me in the ropes :cry:
class Event < ApplicationRecord
belongs_to :calendar, optional: true
has_many :assignments, inverse_of: :event
delegated_type :eventable, types: %w[ Call Task Meeting], dependent: :destroy
end
class Task < Event
include Eventable
has_many :assignments, through: :event, inverse_of: :assignable
accepts_nested_attributes_for :assignments
end
class Assignment < ApplicationRecord
belongs_to :event, inverse_of: :assignments
delegated_type :assignable, types: %w[ Participant Asset ]
end
# routes.rb
resources :employees do
resources :tasks
end
class TasksController < EventsController
def new
@task= Task.new( event: Event.new)
@task.assignments.new( assignable: (Employee.find(params[:employee_id]) rescue nil))
end
def create
??
end
private
def resource_params
params.require(:task).permit(:duration, event_attributes: [ :name, :account_id ], assignments_attributes: [ :id, :assignable_id, :assignable_type, :assignable_role, :_destroy ] )
end
end
I am able to get a params back from the form with
#
# Parameters: {"authenticity_token"=>"[FILTERED]",
# "task"=>{
# "event_attributes"=>{"account_id"=>"1", "name"=>"meeting"},
# "duration"=>"100",
# "assignments_attributes"=>{
# "0"=>{"assignable_type"=>"Employee", "assignable_id"=>"54", "assignable_role"=>"owner"}
# }
# }
# }
I just don't get how I persist the thing :cry: (again)
Posted in Hotwire Modal Forms Discussion
Solid lesson, Chris! I do miss one tiny issue though :(
When saving the form and it has errors all is good - but when the form is good!?
Remember - you're on the index.html.erb already and opening a modal will afford you to prepend new posts on /posts across all tabs/connected users, and all is great.
I can format.turbo_stream { render turbo_stream: turbo_stream.remove( "post_form" ) }
but that does not close the modal and will leave me with "an empty" DIV if I somehow manages to get the modal.hide() working. A "how to attach to the webhooks of Turbo really would come in handy <3
And then there is the 'edit' action on the modal too - same song more or less.