implement a booking lock for users in a scheduling app - sql

I've built a scheduling web app where users can book a conference for a given date and time slot on that date.
Now I need to implement a booking lock, which means that the admin can apply this lock and users can no longer book from that time on.
My current design includes a model booking_lock that only has one attribute of "status" with values of "On" and "Off".
The model would have only one record in the database at any given time as its sole responsibility would be to provide the data to check if booking has been locked.
---------------------EDIT-----------------------------------
This is not meant to lock individual records (conferences). To do this I use the attribute of user_id. If that is 0, then the conference is available for booking.
What I want to do now is disable (lock) the booking entirely before the user hits the database.
For example:
When a user logs on they see a notification saying the booking is locked. Also, they wouldn't see a respective button to book a conference and if by chance they would navigate to the respective URL they would be redirected to home page with the same notice as above. I've got all this covered.
But I need a general switch to be turned on and off and be the basis for this kind of check. Hence the booking_lock model.
Note: I use hanami-model.
---------------------EDIT-----------------------------------
My question is twofold:
Is there a better way of implementing such a lock?
How to ensure there is only one record in the db for such lock?

1. Is there a better way of implementing such a lock?
It seems that your requirement is to have an application-wide setting, which is best done with
An environment variable
A value stored in database
Environment variables:
pros: system already in place, simple usage, can easily be configured statically and changed dynamically
cons: might be difficult to maintain with a multi-tenant architecture (multiple servers running the same app)
DB persistence
pros: scales easily with a multi-tenant architecture
cons: can seem a bit of an overkill for one setting BUT can easily be abstracted into a Key/Value store for multiple settings.
Example:
Create an AppSettings model with two columns: key & value.
Create and save an AppSetting with key "lock_booking_for_parents" and change the value through an admin interface.
Access this AppSetting throughout the app to know if you should lock booking or not.
Also, if you are worried about querying your database everytime you need to know if booking is enabled/disabled, you can easily implement caching.
2. How to ensure there is only one record in the db for such lock?
Environment variable
Not applicable
DB persistence
You can create a helper class to access your settings with default behavior, and use first_or_create method to ensure the record is unique.
class AppSettingsHelper
def self.booking_enable?
app_setting_record = AppSettings.find_by_key("lock_booking_for_parents")
if app_setting_record
return app_setting_record.value
else
## default behavior of your app when no record has been created yet
return true
end
end
def self.enable_booking
AppSettings.where(:key => "lock_booking_for_parents").first_or_create({value: false})
end
def self.disable_booking
AppSettings.where(:key => "lock_booking_for_parents").first_or_create({value: true})
end
end

You could try the ActiveRecord pessimistic locking method, or something like it. Set a field on your record that indicates a lock and try it:
booking_lock = UUID.new # Or any sufficiently unique string
Booking.update_all({ id: #booking.id, booking_lock: booking_lock }, { booking_lock: nil })
booked = Booking.find_by(id: #booking.id, booking_lock: booking_lock)
If you get the record back you successfully locked it. If not you need to try again with a different booking, it got nabbed before you could claim it.

Related

Multiple users accessing a linked table occasionally see a message "Cannot update. Database or object is read-only"

We have a split MS Access database. When users log on, they are connected/linked to two separate Access database (one for the specific project they are working on and one for record locking (and other global settings)). The "locking" database is the one I need to find a solution for.
One of the tables "tblTS_RecordLocking", simply stores a list of user names and the recordID of the record they are editing. This never has more than 50 records - usually being closer to 5-10. But before a user can do anything on a record, it opens "tblTS_RecordLocking" to see if the record is in use (so it happens a lot):
Set recIOC = CurrentDb.OpenRecordset("SELECT tblTSS_RecordLocking.* FROM tblTSS_RecordLocking WHERE (((tblTSS_RecordLocking.ProjectID_Lock)=1111) AND ((tblTSS_RecordLocking.RecordID_Lock)=123456));", , dbReadOnly)
If it's in use, the user simply gets a message and the form/record remains locked. If not in use, it will close the recordset and re-open it so that the user name is updated with the Record ID:
Set recIOC = CurrentDb.OpenRecordset("SELECT tblTSS_RecordLocking.* FROM tblTSS_RecordLocking WHERE (((tblTSS_RecordLocking.UserName_Lock)='John Smith'));")
If recIOC.EOF = True Then
recIOC.AddNew
recIOC.Fields![UserName_Lock] = "John Smith"
Else
recIOC.Edit
End If
recIOC.Fields![RecordID_Lock] = 123456
recIOC.Fields![ProjectID_Lock] = 111
recIOC.Update
recIOC.Close: set recIOC=Nothing
As soon as this finishes, everything realting to the second database is closed down (and the .laccdb file disappears).
So here's the problem. On very rare occasions, a user can get a message:
3027 - Cannot update. Database or object is read-only.
On even rarer occasions, it can flag the db as corrrupt needing to be compressed and re-indexed.
I really want to know the most reliable way to do the check/update. The process may run several hundred times in a day, and whilst I only see the issue once every few weeks, and for the most part handle it cleanly (on the front-end), I'm sure there is a better, more reliable way.
I agree with mamadsp that moving to SQL is the best option and am already in the process of doing this. However, whilst I was not able to create a fix for this issue, I was able to find a work-around that has yet to fail.
Instead of having a single lock table in the global database. I found that creating a lock table in the project database solved the problem. The main benefit of this is that there are much fewer activities on the table. So, not perfect - but it is stable.

ABAP Program to notify Users X amount of days before user account will be disabled

I'm currently learning ABAP and trying to make an enhancement but have broken down in confusion on how to go about building on top of existing code. I have a program that runs periodically via a background job that disables user accounts X amount of days (in this case 90 days of inactive usage based on USR02~TRDAT).
I want to add an enhancement to notify the User via their email address (result of usr02~bname to match usr21~bname to pass the usr21~persnumber and usr21~addrnumber to adr6 which will point to the adr6~smtp_addr of the user, providing the usr02~bname -> adr6~smtp_addr relationship) based on their last logon date being 30, 15, 7, 5, 3, and 1 day away from the 90 day inactivity threshold with a link to the SAP system to help them reactivate the account with ease.
I'm beginning to think that an enhancement might not be a good idea but rather create a new program and schedule the background job daily. Any guidance or information would be greatly appreciated...
Extract
CLASS cl_inactive_users_reader DEFINITION.
PUBLIC SECTION.
TYPES:
BEGIN OF ts_inactive_user,
user_name TYPE syst_uname,
days_of_inactivity TYPE int1,
END OF ts_inactive_user.
TYPES tt_inactive_users TYPE STANDARD TABLE OF ts_inactive_user WITH EMPTY KEY.
CLASS-METHODS read_inactive_users
IMPORTING
min_days_of_inactivity TYPE int1
RETURNING
VALUE(result) TYPE tt_inactive_users.
ENDCLASS.
Then refactor
REPORT block_inactive_users.
DATA(inactive_users) = cl_inactive_users_readers=>read_inactive_users( 90 ).
LOOP AT inactive_users INTO DATA(inactive_user).
" block user
ENDLOOP.
And add
REPORT warn_inactive_users.
DATA(inactive_users) = cl_inactive_users_readers=>read_inactive_users( 60 ).
LOOP AT inactive_users INTO DATA(inactive_user).
CASE inactive_user-days_of_inactivity.
" choose urgency
ENDCASE.
" send e-mail
ENDLOOP.
and run both reports daily.
Don't create a big ball of mud by squeezing new features into existing code.
From SAP wiki:
The enhancement concept allows you to add your own functionality to SAP's standard business applications without having to modify the original applications. To modify the standard SAP behavior as per customer requirements, we can use enhancement framework.
As per your description, it doesn't sound like a use case for an enhancement. It isn't an intervention in an existing process. The original process and your new requirement are two different processes with some mutual logical part - selection of days of inactivity of users. The two shouldn't rely on each other.
Structurally I think it is best to have a separate program for computing which e-mails need to be sent and when, and a separate program for actually sending them.
I would copy your original program to a new one, and modify it a little bit so that instead of disabling a user, it records into some table for each user: 1) an e-mail 2) a date when to send 3) how many days left (30, 15, 7, etc) 4) status if the e-mail was sent or not. Initially you can even have multiple such jobs for each period (30, 15, 7 etc) and pass it as a parameter (which you use inside instead of 90).
This program you run daily as a job and it populates that table with e-mail "tasks" of what needs to be sent today. It just adds new lines, so lines from yesterday should stay in there.
The 2nd program should just read that table and send actual e-mails and update the statuses. You run that program daily as well.
This way you have:
overview: just check the table to see what's going on
control: if the e-mailer dies or hangs, you can restart it and it will continue where it left off; with statuses you avoid sending duplicate mails
you can make sure that you don't send outdated e-mails if in your mailer script you ignore all tasks older than say 2 days
I want to clarify your confusion about the use of enhancements:
You would want to use enhancements in terms of 'something' happens or is going to happen in the system and you would want to change this standard way.
That something, let's call it event or process could be for example an order is placed, a certain user is logging onto the system or a material has been or is going to be changed.
The change could be notifying another system of an order or checking the logged on user with additional checks for example his GUI version and warn him/her if not up-to-date.
Ask yourself, what process on the system does the execution of your program or code depend on. Does anything need to happen before the program is executed? No, only elapsing time.
Even if you had found an enhancement, you would want to use. If this process using the enhancement would not be run in 90 days, your mails would not be sent, because the enhancement would never been called.
edit: That being said, supposing you mean by enhancement 'building on your existing program' instead of 'creating a new one' would be absolutely not the right terminology for enhancement in the sap universe.
I would extend the functionality of your existing program, since you already compute how many days are left and you would have only one job to maintain.

Web2py manage user registration key pending status

I am hosting web2py for a client and don't want to give out web2py admin access. There are multiple applications running in web2py. All of them require approval for registered useraccounts. I would like to have a control which allows app admins to open a table of records with a pending registration key. The only function required is to remove the pending status from the registration key. This seems to be all but impossible outside of the appadmin control.
The Auth tables are just standard database tables and can therefore be accessed and edited as any other tables. There is no need to use appadmin in particular for this task. You have at least three options:
Use the built-in Application Management functionality. If you want to limit which auth_user fields are writable, you could include a condition such as the following in a model file:
if request.function == 'appadmin' and request.args(0) == 'manage':
[setattr(field, 'writable', False) for field in db.auth_user
if field.name != 'registration_key']
Create a custom action and use the built-in grid functionality. To limit the viewable records to those with pending registrations, you can pass a query as the first argument to the grid:
SQLFORM.grid(db.auth_user.registration_key == 'pending', ...)
You can create your own CRUD functionality using any of the facilities available in web2py.

Rails 3 destroy oldest item in database if user owns more than 10

I have an Activity stream I have designed using STI in rails 3.
I want to do a check to see if a user has more than 10 activity items on create. When a user creates his 11th Activity in the database, I want it to destroy the oldest record * essentially the first record the user made and so on.* This way I am keeping my database clean of thousands of old useless records.
I am assuming I would start at the model level, but since I dont want to define the "current_user" in the model, it should be in the controller.
Any help would be appreciated to accomplish this task, I am fairly new at these type of more advanced tasks in rails.
You can hook this logic up in an AR callback. Assuming you kept rails conventions when modeling you classes, and each activity belongs to a user, you can then easily do the following in your Activity model:
after_save do |record|
if Activity.where(:user => record.user).count >= 11
Activity.where(:user => record.user).order('created_at asc').first.destroy
end
end
I guess this will create three transactions to the db (one for count, another to find the first record, and one to delete it). I wonder if there's a more efficient way to do this, as it will be invoked on every Activity#save...

Update an entity inside an aggregate

I was reading a similar question on SO: How update an entity inside Aggregate, but I'm still not sure how a user interface should interact with entities inside an aggregate.
Let's say I have a User, with a bunch of Addresses. User is the aggregate root, while Address only exists within the aggregate.
On a web inteface, a user can edit his addresses. Basically, what happens is:
The user sees a list of addresses on its web interface
He clicks on an address, and gets redirected to this page: edit-address?user=1&address=2
On this page, he gets a form where he can modify this address.
I we decided to bypass the aggregate root, this would be straightforward:
We would directly load the Address with its Id
We would update it, then save it
Because we want to do it the DDD way, we have different solutions:
Either we ask the User to get this Address by Id:
address = user.getAddress(id);
address.setPostCode("12345");
address.setCity("New York");
em.persist(user);
The problem with this approach is, IMO, that the aggregate root still doesn't have much more control over what's done with the address. It just returns a reference to it, so that's not much different from bypassing the aggregate.
Or we tell the aggregate to update an existing address:
user.updateAddress(id, "12345", "New York");
em.persist(user);
Now the aggregate has control over what's done with this address, and can take any necessary action that goes with updating an address.
Or we treat the Address as a value object, and we don't update our Address, but rather delete it and recreate it:
user.removeAddress(id);
address = new Address();
address.setPostCode("12345");
address.setCity("New York");
user.addAddress(address);
em.persist(user);
This last solution looks elegant, but means that an Address cannot be an Entity. Then, what if it needs to be treated as an entity, for example because another business object within the aggregate has a reference to it?
I'm pretty sure I'm missing something here to correctly understand the aggregate concept and how it's used in real life examples, so please don't hesitate to give your comments!
No, you're not missing anything - in most cases the best option would be number 2 (although I'd call that method changeAddress instead of updateAdress - update seems so not-DDD) and that's regardless whether an address is an Entity or Value Object. With Ubiquitous Language you'd rather say that User changed his address, so that's exactly how you should model it - it's the changeAddress method that gets to decide whether update properties (if Address is an Entity) or assign completely new object (when it's VO).
The following sample code assumes the most common scenario - Address as VO:
public void ChangeAddress(AddressParams addressParams)
{
// here we might include some validation
address = new Address(addressParams);
// here we might include additional actions related with changing address
// for example marking user as required to confirm address before
// next billing
}
What is important in this sample, is that once Address is created, it is considered valid - there can be no invalid Address object in your aggregate. Bare in mind however, that whether you should follow this sample or not depends on your actual domain - there's no one path to follow. This one is the most common one though.
And yes, you should always perform operations on your entities by traversing through aggregate root - the reason for this was given in many answers on SO (for example in this Basic Aggregate Question).
Whether something is an entity or VO depends on the requirements and your domain. Most of the time address is just a Value Object, because there's no difference between two addresses with the same values and addresses tend to not change during their lifetime. But again, that's most of the time and depends on domain you're modeling.
Another example - for most of the domains a Money would be a Value Object - 10$ is 10$, it has no identity besides amount. However if you'd model a domain that deals with money on a level of bills, each bill would have its own identity (expressed with a unique number of some sort) thus it would be an Entity.