Closed (fixed)
Project:
MERCI (Manage Equipment Reservations, Checkout and Inventory)
Version:
7.x-2.x-dev
Component:
Code
Priority:
Normal
Category:
Feature request
Assigned:
Unassigned
Reporter:
Created:
18 Oct 2011 at 15:59 UTC
Updated:
11 Dec 2013 at 04:21 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #1
ohthehugemanatee commentedActually, it might make the most sense to abstract it out to allow any entity to be a merci type. For example, I'd like to be able to associate Users with my reservations. That would be pretty awesome...
Comment #2
tmsimont commenteddid this ever happen?
Comment #3
skriptble commentedNope. I think it's a good idea though. Although from a conversation I had with darrick it seemed like he wanted to build merci as a field that you can attach to any entity to make it reservable.
In theory you could then build up a custom entity type as well that would use this field, but you would still be able to make anything reservable, users, taxonomy, nodes, or some new custom entity type you create.
Comment #4
darrick commentedSKriP is correct. I did just commit the start of this. Which currently doesn't do all that much. But focusing on making a reservation entity which is fieldable to store the date, entity reference, reservation status, etc. etc fields most likely being added by separate modules. Then one can create another node or entity type and using a entity reference field with multiple values can allow the user to create a reservation with multiple items.
Time wise the goal is to complete the work by the time Drupal 8 is released. And there will be an upgrade path.
Comment #5
skriptble commentedHey darrick, have you made a branch for this? I was gonna start doing this from scratch as I think a full rewrite might serve us better than trying to carry the code from the D6 version into a new one.
Also, were you thinking of building this in D7 and D8 in parallel so it's ready when D8 is released?
Comment #6
darrick commentedYes there is a branch now: 7.x-3.x. Going to be building it mainly in D7 and then porting to D8. And it is a full rewrite.
At the moment it's based off the http://drupal.org/project/model project. Added a few extra things. But there is no validation yet or any other functionality. But I did want to get somewhere to start going.
You do have write access to cvs and are welcome to post changes there. Currently I'm trying to figure out where best to call the validation from and how to best extend that validation. And same with populating the select box.
Comment #7
skriptble commentedAh I thought I had checked out 7.x-3.x but I had checked out the master branch instead. Now I'm looking at the right thing.
I looked through the module quick, I'll see what I can do going forward. I built a pretty sizable module already using entities, but I wrote those by scratch... wish I'd known about the model project.
Comment #8
darrick commented&SKriP could you upload what you have to a sandbox somewhere. I'd like to see it. Also I'm one of the last people left in #drupal-openmedia on freenode. Which is probably as good of a spot as any to chat about moving things forward. We should keep this issue open for links to code, etc.
Comment #9
skriptble commentedYo Darrick, have you seen Reservations? It looks like a direct fork of MERCI, except it's still using content types and looks like it carried a lot of the stuff from the old version of MERCI with it.
Anyway, I'm thinking of building an actual reservation API module. I've noticed that all of the reservation systems are very specific to a type of reservation (airplane, restaurant, equipment) which makes it very hard when you want to reserve something else (possibly something like books?). It's like that saying of trying to shove a square peg in a round hole. Doesn't work too well. We can obviously MERCI as a use case for the API which will cut down on some of the custom construction needed and abstract things out a bit.
It's gonna be a real simple API, probably a few functions for saving a reservation, getting a reservation, and setting statuses. The benefit comes once we can get other modules that do reservy things to join in. Then we can build a solid system for handling reservations inside of Drupal (think Voting API).
Who knows what might happen. Maybe this is a crazy idea, but I'd at least like to give it a shot.
Comment #10
tmsimont commentedhey skriptble, you might want to take a look at what I've set up here: http://drupal.org/sandbox/tmsimont/1931666
it revolves around entity reference fields rather than individual entities or content types.
I am working on finishing up some rules integration, and then I am going to try to get some detailed docs and examples put up.
let me know if you have an interest in working with me here or if you might want to build on top of what i've done.
Comment #11
skriptble commentedOh I like what you did there, but I think it's a little different than my goal. I'm trying to get down to a reservation framework I guess. Ideally it would handle the low level stuff like creating, updating, and removing a reservation and changing the status of a reservation.
Of course I want to handle any other things that involve general reservations, like conflict resolutions as your module suggest.
In my mind, I would the module be an API with no user interface (not even creating a field to use) and then another module that implements the fieldable version of the module, and possibly create an entity.
One of the big things I've been struggling with is the inclusion of an entity in the API module. From what I can tell the Voting API does not have an entity it uses and simply saves the voting data to a table. I think this because it keeps everything pretty simple.
I think that a better addition would be to add the entity into a use case for the API, similar to what your module looks like it's aiming to do. (A field that attaches to any entity that facilitates the creation of reservations. Either way I want to include a bunch of hooks to make it extendable.
Comment #12
darrick commentedOn my last commit I used a Entity Reference behavior plugin to perform the validation. http://drupalcode.org/project/merci.git/commit/a9b0add
Issue #1803064: Horizontal extensibility of Fields: introduce the concept of behavior plugins talks a bit about what behavior can be used for. There is also issue #1801304: Add Entity reference field.
I do want to see a very limited basic merci api module. Now that I'm getting into the validation and how to allow for other modules to easily extend that validation the merci entity may not be neccessary and is most likely not needed in the base module.
Right now I'm thinking:
- Create behavior plugin to validate the datetime (i.e. item fails validation if it is already reserved.) Need to figure out how to switch validation based upon the type of item (bucket, kit, resource, etc.) Selection class provide a getInstance so you could load a subclass based on the input (i.e. reference has a merci_type to field to switch on) but the Behavior class does not have this time. In the settings form for the behavior allow the user to select which field holds the date to use for validation. So to create a reservation content type one would add a date field and entityreference field. Then add the entityref field to use the merci behavior with the date field. The validation should support multiple items.
- Status could be a separate behavior within a separate module. Is item checked out or not.
- Hours of operation could be a separate behavior and module as well.
Regardless in the long run I'd like to keep to the spirit of what MERCI stands for. Manage equipment reservations, checkout and inventory. This is not to say the core api can't be built bare bones to support the widest number of use cases.
Comment #13
tmsimont commentedthanks darrick. I did recognize that MERCI stands for a specific use case, which is why i went on to build my own module.
however, i think both darrick and skriptble could both benefit from what i'm doing.
skriptble
that's basically all i'm trying to do with my module. i understand your concern is more about a "reservation entity," but I think you could utilize what i've been working on in your code. your code would primarily concern the actual reservation, but you could use my code to handle the validation involved with a reserved entity's availability...
darrick
This pretty much describes almost exactly what I built my module to do. Right now i have almost all of this working, but I need some more testing, docs and I need to finish the Rules integration, which allows you to handle the validation differently based on various conditions made available to rules.
I don't mean to ram my module down your throats, but I could really use some help with this module, and it sounds like you guys need the same thing that i've started to build.
Comment #14
skriptble commentedGah, I wrote a post and then d.o went down for maintenance.... not cool :-(
Anyway I agree that MERCI should stick to equipment, reservations, and inventory.
I think we should follow the Aphorism of API Design, specifically N is the only number. So everything (buckets, kits, resources) are all validated through the same function. Buckets can simply iterate through all their items, or the opposite where the validation automatically iterates, and you simply only pass in 1 item for Resources (think entity_load and entity_load_multiple).
tmsimont -
I'll check out your code later today. It could very well do the whole validation part I'm looking for, and I'm not closed off to using it. My ultimate goal is to have as few dependencies as possible, and have suggested enhancement modules instead. However, if your modules does validation in a clean and somewhat pluggable way (or if we can help you bring it there) then it would make sense to use it as a dependency.
Comment #15
tmsimont commenteddang same thing just happened to me!
i'm aware of a few holes in my module -- i'm working now on a site where i can put up some detailed explanations of how this works and some of the TODO's that are most important
Comment #16
darrick commentedI thinks what skriptble is proposing is a good worthwhile target. To look for the minimal amount of functionality required of a reservation system to allow other modules to build upon it. I've only quickly looked at tmsimont's code but it does appear to be pretty close to the direction I was headed toward. So, I'm willing to try my best to make everything work well together.
Comment #17
skriptble commentedI'm thinking that the API should come with some super basic functionality that is completely pluggable, and then we plug in the various pieces and recommend a configuration. So there could be some basic validation built into the system, but we design it so that tmsimont's module is able to be plugged into the system.
About the validation (for a simple system anyway), what if we made a basic system that relied on unix timestamps? Obviously a more robust system would be useful (something that you can have varying different types of validation and ways to handle that validation like in tmsimont's module), but for the basic API we could store the Unix timestamps, and then when we needed to run a validation we would do the following:
- Search for other reservations with the same ID that have a beginning time that less than or equal to our proposed begin time AND an end time that is greater than or equal to our proposed beginning time (If this is true we have a conflict because the proposed beginning time falls when that item is already reserved)
- Search for other reservations with the same ID that have a beginning time that is less than or equal to our proposed end time AND an end time that is greater than or equal to our proposed end time (same as above, if they overlap our end time falls in the span of another item's beginning time).
- Search for other reservations with the same ID that have a beginning time after the proposed begin time and an end time before the proposed end time.
I believe that those three cases should catch everything that would result in a conflict (wrapping the proposed reservation around another, beginning at the same time or in the time of another reservation, and ending at the same time or in another reservation). If I missed something, let me know.
The API function would simply take a beginning time, end time, and entity ID. It would return a list of reservation ID's that match or NO_CONFLICT (string or constant) if there is not a conflict. The API would not handle displaying the results in any fashion or doing any validation other than time conflict. It would also be using simple number operations to compare time:
1364443200 > 1364356800
As I've been saying, we make this pluggable, so the API implementer can choose which validation plugin they want to use. First thought on making it pluggable is to use Dependency Injection and pass in the Validation object. But maybe I'm thinking about it too complexly? Maybe a simply array with defined parameters would be less heavy?
I think in general creating an OO interface gives us the benefit of automatically throwing errors if the validation plugin doesn't abide by the rules. We can also use a configureable object for instantiation so that we can set the dependencies once in configuration and not have to do it every time we want to use the validate function.
Thoughts?
Comment #18
tmsimont commentedtake a look at this function from my module:
The
EntityAvailabilityConflictthen gets passed to Rules. I then give Rules a bunch of conditions and actions so Rules can react on the beginning time, end time, and entity ID. One of the actions I give is "show validation error" in case the action is triggered during a standard form validation.Recently I've been working on integrating
EntityAvailabilityResolutionobjects to handle resolution of conflicts.. That part is still a ways off from completion.I really think that what you're looking for is what I built :)
I have a few functions to create, get and delete "Availability Rules" but the "update" is not possible and this whole interface could use some love to make it a bit more pluggable:
Comment #19
tmsimont commentedps what I have is really mid-development right now and I could use some help making it more pluggable -- like allow modules to define default rules, etc... also the CRUD on the "Entity Availability Rules" kinda blows right now. That needs some testing and love.... It works for my basic implementation of the module at the moment, but I know there's gonna be bugs in here.
I'm going to start putting up detailed docs on how it works and what's going down in that code base tomorrow.
Comment #20
skriptble commentedI saw that code. And yes, what you have is what we want to do for the MERCI module, but not the Reserve API. It is possible that some implementations of this API will not want rules integration, will not have entities, and will only wish to check to see the reservation they are trying to create interferes with another one they've made. The method that I suggested uses no external modules, and is a simple callback that returns simple data.
Those docs will be great because I'm totally confused about how exactly you're supposed to configure this module. I have a little idea from the screenshots but it's still pretty confusing.
Comment #21
darrick commented@skriptble I think what you are talking about is this (which is from this commit: http://drupalcode.org/project/merci.git/commit/76c6041)
@tmsimont How do you think _entityavailability_get_flagged_availability_date_ranges and entityavailability_check_availability compares to this function? I'm also curious if in your code a single entityref field can be used to reserve more then one content type. I downloaded your code and created one rule but it seemed I could only choose one content type rather then multiple. In the use case for MERCI the user may want to reserve multiple content types in single reservation. And depending upon the content type reserved conflicts will be handled differently.
Comment #22
skriptble commented@darrick Yup. Update the fields so their using whatever we name the columns and that function should work grand.
@tmsimont I'm getting an error about
$entity->identifier() on line 362, so I probably did something wrong when setting up the module.Comment #23
tmsimont commented@darrick that function looks very similar. here are some differences i see:
What i built is possibly (probably) less efficient because it pulls in all of the dates into a PHP array first and then loops over that, rather than assembling a nice 1-shot db query. Mine differs in that it uses a variable date field and variable entity relationships between the "available entity" and the "referring entity" and the "conflicting entity." Mine also tracks where the conflict is coming from and what "Entity availability rule" has defined that conflict, which then gives you a bit more information to use when deciding to respond.
You could definitely have a single field reserve multiple content types with my code, it would just require more "Entity availability rules" to be configured. The error about the
$entity->identifier()is a bummer -- the entity you're using doesn't use the Entity API... What kind of entity is it? I had a feeling I wouldn't be able to rely on theidentifier()function@skriptble
You're right I recently dropped in Rules and that may not be ideal. I can easily break the rules out into an optional secondary module, however, which would then make what I've got more light-weight.
What I have built is definitely dependent on entities, because it depends on Date fields and Entity Reference fields that will not work without entities, so if you want to avoid entities then you're not going to want to use this. But why not utilize Date and Entity Reference fields?
@both I know the configuration of this blows right now... the multistep configuration wizard generates a bunch of rules at once sometimes and then you can't go back and edit, you have to remove/replace... I was wondering if the Entity Availability Rule shouldn't be an entity itself... but I don't want to over complicate the code. It's also buggy and in early development right now.. Let me go through and post up some docs..
Comment #24
skriptble commented@tmsimont for the third one, the case of an exact overlap is covered by the first scenario, because it uses less than or equal to.
I used an article, so yeah the node API doesn't use Entity API because it's core. That could be a potential issue with integration, although for MERCI we might want to make all of the equipment some form of MERCI entity. Although we probably don't want to close ourselves off like that either... we'll need to discuss it more.
The main reason I want to stay away from entities and Date fields goes back to the simplicity of the API I want to build. As a developer and site builder I know I dislike when I'm forced to install modules just because they are dependencies for one other module. Using strtime makes it simple to use a textfield for input if you want, or feed a date or time into the API in another manner. I don't want to make decisions for people on how to use the API out of the box, I want to make it pluggable for them. It's like the caching system in Drupal, sure memcached is an awesome cache to use, but Drupal gives you a simple DB cache that works for most people great and the ability to swap it out with another one if you want. So I want to use your module, but I also don't want to package a bunch of dependencies that people might not want to use when implementing the API.
I think it's kind of clunky how it works currently. I understand the idea of reserving an entity but I feel like it can be done without so much entity creation occurring, although it does make sense now that I read over your example. So Entity B would be a piece of equipment (say a DSLR), and entities A & C would be reservations of the DSLR..... it actually does make sense doing it that way. I think we should focus on building an interface that utilizes that thought process. I'm not sure if rules is that interface. I see how you could build some really awesome complex reserving mechanisms, but it feels too bulky to do simple reserving mechanisms. It seems to me that a complex reservation should be made up of a bunch of simple pieces. A first step might be to create a default field that comes with the module that is automatically referenced as the reserve field unless otherwise specified.
Don't get me wrong, I think we should keep that complex functionality in there, just make it a bit easier for getting up and running. No one likes installing a module and having to go through 50 steps to get the basic part of it working.
Comment #25
tmsimont commentedalthough i agree that installing a bunch of modules can be a headache, it comes with its advantages. it's also more or less "the drupal way"
The advantages are that you hand off code maintenance to other developers.
I know you want full control over the data model, and you want to keep it simple with unix timestamps, but consider this...
How does an end user enter the time stamp? You'd end up having to build a date entry widget..
What if somebody installs the site and needs timezone support? You'd end up having to build in your own timezone resolution...
These are problems that the Date module maintainers have been dealing with for years now. Date will actually be in the D8 core, so there isn't much point in avoiding that module. Most sites are probably already using it.
On the surface, avoiding additional modules feels good, but once you start coding you'll find yourself re-inventing the wheel more times than you should. The same can be said about entity reference fields. We could use our own tables and our own input widgets for handling entity references, but all of that work is already being done by another module.
I think that as developers we should write as little code as possible, so I am strongly in favor of utilizing the widely used/tested Date and Entity Reference modules...
As for the clunkiness... If you're referring to what I've built (I think you are) then yes I agree, the management UI is super clunky. I'd love to rebuild that part of the module to make it smoother. I started with a great focus on the data model and interacting with the Date and Entity Reference fields in a way that will make this all happy. The admin UI's are throw-togethers so I could get it set up and tested.
Also don't be confused by the confusing title of "Entity Availability Rule" -- this isn't a Rules module Rule -- it's just a database record that controls my various validation functions... I should probably call it something else so it's not confused to be part of Rules.
The only Rules integration in the module right now involves conflict resolution. Per your advice, I will break that out into a separate module so the base API can be more light weight.
Writing docs now
Comment #26
skriptble commentedThe API has no UI. No one would install this on their site unless another module requires it (MERCI for instance). The module that implements the UI would take care of properly formatting the data and passing it in. The API makes no judgements about how that data is submitted as long as it is a valid PHP time.
The validation function isn't meant to cover all situations. That's why it's being made pluggable. If you need more robust handling of Date and Time formats, you install a module like yours that will handle date and time validation better. I want to make the storage pluggable too. Sure, most people will end up storing the reservations in a database, but what if you wanted to store them as plain text files for some reason? What about integration with another service like Google Calendar? I want the API to be a centralized way to handle Reserving of something, that's very hookable and pluggable.
Like with MERCI, maybe I don't want to store my reservations in the DB, but want to use Google Calendar. It'd be nice if I write a module that handles that for it to also be able to handle a system for reserving hotel rooms. While this might not be achievable easily, I want to make it easier. The voting API is a good example of this. It provides a way to save and get votes, and a few other niceties. Now modules that wish to have a voting system don't have to write all that code their self, they can just use the Voting API module to handle a bunch of that functionality. That boost the code reusability and it adds more developer power to the base API.
So the API shouldn't care or even think about Entities, Dates, Rules, or anything of that sort. It should say "Give me me a reservation, I'll check to see if it overlaps with something, and if it doesn't I'll save it for you." and you should be able to say to it "Hey, can you give me all my reservations?" or "Hey, can you give me reservation #15923?" and it should hand what you requested to you. You need to decide as a module developer how you want users to input that data. Maybe you're using google calendar as a front end and users sign up on their and it passes the date back to your Drupal backend. You shouldn't have to install the Date module on your site just because you want to use the Reserve API.
There are a bunch of other usecases, and there will always be ones we can't think about here, so the less assumptions we make now, the more useable the API will wind up being later. Sure, we don't know right now why you wouldn't want to use the Date module, but that doesn't mean we'll think the same thing a month from now, so let's not premake that decision for us now.
Comment #27
tmsimont commentedI suppose our goals are different in approach.
You define a "reservation" as an object that reserves "something" for a time period and then recalls what has reserved that "something." Then you let other modules handle how dates are collected, how the API has an affect on anything and how object is related to a reservation.
I'm defining "availability" as "access to an entity reference field value based on an accompanying date field, and potentially based on other entities referencing the field value entity." I then let the other modules and/or admins decide what drupal entities are parent to these entity reference and date fields.
I guess I don't want my module to care about a "reservation" -- I just want it to be able to check if an entity is referable in a given time frame. Whether or not it's referable (available) depends on what else is referring to it.
"reservations" could very well use this functionality. They could use it to determine if the "reservable" entity is available when the reservation is being made.
I could hypothetically implement your api functions to check for a conflict, but I wouldn't want the overhead you'd be creating to handle "reservations" and their date models. It would be an extra step to glue the Date module input and data into your abstracted timestamp table. Your module would require a lot of development work to get anything at all actually working with user input.
I know I'm setting limits by depending on Entity Reference and Date, but these are Fields that are very close to core. Entities are extremely flexible, and even though I'm limiting relationships to Drupal Entities, that doesn't mean that you can't build just about anything on top of this with the Entity API CRUD.
I think that creating a "reservation" as its own entity defined in whatever way you choose (google, drupal entity, etc) is not useful to situations where you don't necessarily need a "reservation" as much as you need a "reservable" entity to become unavailable to reference.
I started this because i needed to allow "managers" to put an event with an "instructor" reference onto a schedule. The "manager" selects a time frame for the event in which they need a reference to an "instructor" -- they need to see a validation error if the "instructor" is already "booked" onto another schedule in the same time in a different event.
The "reservation" in this case would be the "event." The "event" is already its own type of entity, and doesn't need additional overhead for tracking the "event" instance as a "reservation." In the same scenario, the "instructor" might create a time period where he is "unavailable" -- either on vacaction, or out sick, etc. For this I have another entity -- "Exclusion." The "instructor" could create "Exclusions" on his calendar. Both "exclusions" entities and "event" entities rule the instructor out as "unavailable" during specific time periods. The time periods are handled by date fields that are then useable in other views, etc with the power of the Date module.
In this arrangemtn, the "reservation" is not actually a specific record in a database somewhere, it is merely inferred by the relationship of "event" entities, "instructor" entities and "exclusion" entities, and their related date and entity reference fields.
Exactly how that is "inferred" is defined by "Entity Availability Rules"
An "instructor" could easily be a "resource" like a DSLR camera or something, and the "reservation" could even be it's own entity. But, rather than defining a "reservation" as its own entity, you could instead use something else... like a content type, a field on a profile, or even something related to a field on a commerce product...
My goal was to provide flexible entity relationships that define availability or lack of availability based on the Entity API and 2 widely used field modules.
It sounds like your goal is to provide a concrete reservation type that has flexible abstract affects based on dates that may or may not be related to the date module.
Does that sound right?
Comment #28
skriptble commentedNope. In reality, what your module is storing and the API would be storing are the same thing, we're just using semantics differently. You have lots of built in semantics, which makes sense for a complete solution, I don't have many semantics at all because we're building an API. API's don't tell you what you should do or how you should do it, they should give you tools to use to do things easier. The Reserve API could be used to do the same thing you want. Just because you call one thing an exclusion and the other an event doesn't change anything except for semantics. The goal is that you can't reserve the same professor in the same time. The Date API is storing a beginning date and an end date. The Reserve API simple validation stores a beginning date and an end date.
You can easily use the date module for this, you would pass the form item value into the validation function. Like I said, you would be able to use any valid PHP date, and I'm 100% sure that the date module uses valid PHP dates, because if it doesn't, it wouldn't work. I'm pretty sure the date module would even let you store the value as a unix time stamp and then display it as a human readable format. (Hence the UNIX Timestamp filed provided by the date module).
The point of the Reserve API is to provide a way to get started making a module that allows you to reserve things. Whatever and however you want. It does not force you to use entities if you don't want to. It doesn't force you to use the date module if you don't want to. What it does do is take an ID, begin time, end time, and a data array, checks to make sure the times don't conflict, and saves if there is not a conflict. Will it grow beyond this? Yup. Will we have vastly different validation systems and data stores added? Yup.
If you were to use the Reserve API, you would build the validation & datastore backend that you like (ie instead of using a simple table to store data you use an entity, instead of doing the simple validation you make it more complex with different returns.) Then you would build the front end components, so the interface for making rules and such. Then when you wanted to actually save the reservation you would pass the information into the API call.
I'm not saying that you should or have to do this. What I did say, and have said for a while, is that I think it's valuable to explore your module's validation as a plugin for the Reserve API's validation system.
So, we're using different semantics and different levels of semantics. Reserving something is making it unavailable. We're attempting to do the same thing at different levels as well. You're making a user facing module, I'm making an API. They serve two different functions.
Comment #29
tmsimont commentedI'm just trying to fully understand what it is you want to do. If we are doing the same thing it would make sense to work together on getting some usable code out to the world for testing and use.
If you have an API that will centralize some aspects of what I've done I'll help explain the code in my project so we can integrate it with what you've got and make everything cleaner. But I think I'm getting lost trying to figure out what it is that you are building or have built... Do you have any code? Maybe it would be easier if I could just dig into your PHP.
Comment #30
skriptble commentedI don't have anything built yet... but it's a rather simple thing to start so I'll start writing and see where I end up.
Comment #31
skriptble commentedOkay, so now that I started at least blueprint this thing I realized what it is I really want to do. I don't think I've been articulating it well so let me try this more objectively.
The goal of the reserve API is to provide a space for common reservation functionality that can be used for any module that wants to reserve something. It makes no assumptions about what that thing is, making it flexible.
Examples of API functions:
Create Reservation
Save Reservation
Retrieve Reservation
Validate Reservation
Find Next Available Reservation
Cancel Reservation
That isn't meant to be an exhaustive list. The API will also implement a plugin system for the storage and validation mechanisms for reservations. Think of this in the same way that Field API Storage works, or the Database storage works: You have a default storage type set, but it's pluggable so anyone can come along and create an entirely new storage method. Because this is a pluggable system, and you are forced to implement certain methods, now any module that implements the Reserve API can use that storage method. The same is true of validation plugins.
There are like 10 or 15 reservation type modules out there, they serve different functionality, and I'm sure there is a bunch of overlap in things like validation functions, storage, and other reservation related things. Just look at #1313420-21: Use custom entities/bundles instead of nodes/content types. The validation for your module is really close to the one MERCI has built in, but a lot of time was expended to do the same thing with slight variances.
In reality it does make sense to use Date and Entity in the Reserve API, I'm not denying that. I think that a majority of people who use this API would have both those modules installed. I just want to start out with the most basic API possible, and build on top of that. I don't want to make assumptions about how someone will use something: as I said before if I'm just using Drupal as the backend and the user interface isn't using Drupal, maybe I just want to send back the identifier, and two times with nothing else.
Here's the reality: I like your module. I like what it does. I think it'll even be one of the base implementation options that gets included with the API. So, what I'm saying, is we should have more than 1 option to test the strength of the API.
If you're still confused I'll just post the code in a few days when I get more of it done. Right now it's stubs.
Comment #32
darrick commentedI think sticking with a entityreference and date field is the best option as the field api is very robust and allows for storing the field data in any number of ways (i.e. sql, amazon, google, etc.)
@tmsimont as for our differences in validations. My plan is to make 1 & 2 user configurable. As for 3-4 working with MERCI I haven't come across a use case which needs that info. But I could see modifying the function to return the ids of conflicting entities rather then the count. So, submodules could then do some queries to answer those questions.
What I think MERCI needs from an api is:
- Call the appropriate availability validation function based on the selected entity. i.e. if selected item is a entity then call the availability function we are looking at. If entity is a bucket (basically a node with a multiple entity ref field) then the validation would recurse over the multiple field to find a available entity. If a kit (basically a node with a multiple entity ref field) the validation would recurse over the multiple field to insure all the entities are available. In your use case this would be similar to a student reserving from a pool of professors (bucket) with no concern for meeting with a particular professor or a student wanting to reserve with a group of professors (kit).
- Call the appropriate status validation. Is the item still checked out and past due? Validation would be different based on if the entity is a resource, bucket or kit. Because some would have to recurse as above.
- What if the item is broken (professor is sick). The item shouldn't show up in the select list or maybe you'd want to note that in the select list (greyed out sick appended).
I believe where we are diverging is where we call this code at. I'm still wanting to call the api via behaviors and selection classes. @tmsimont is calling things via a widget_form_alter. @skriptble is looking at what it is we call (the api).
Comment #33
darrick commented@skriptble looks like I write slower then you.
I agree with the gist of your post. But I think in the long run you'll only need to implement "Validate Reservation". If anything @tmsimont and I agree a reservation is defined as a entity with a date and entityref field. Drupal core already handles quite well plugins for CRUD.
Comment #34
skriptble commentedWhile it is awesome we all agree right now that a reservation is an entity with a date and entity reference field, why are we making that decision now? What harm is there in storing reservation and reservation information in a table that isn't related to an entity. An API does not make assumptions about your preferred user interface . It is as general as possible.
Here's a grand example: We agree that MySQL is awesome, so instead of using the Drupal Database API we write function using mysqli instead. We've made an assumption about how someone wants to use our modules that could be very inaccurate. If I use the Database API instead, if someone wants to use and Oracle Database, or MS SQL server, they are welcome to do so.
Not everyone is going to want to make a reservation using entityreference, not everyone is going to want to make a reservation using the date module. Sure most people will, but most people will also want to use MySQL. Is that a good reason to reject the other people who might want to use our API?
Maybe in the end we will wind up having a reservation be an entity (it'll at least be an object). However, starting with that is like starting training for a marathon by running 15 miles. Let's start out slow and build from there. We should program what we need to solve the problem, and build on it as we go. Date field and Entity field make sense for our use case. I really suggest you guys watch Aphorisms of API design. Especially the part about the 4.6 and 4.7+ path system. They made decision in the beginning and then months later they had issues that could have been prevented by not making a decision before it was necessary.
Comment #35
tmsimont commentedThe Drupal database API is a complex Facade to numerous database interfaces. There are hundreds of tasks involved with database interaction that can have myriad differences. In the case of a Reservation API, all you're saying is "is there an overlap between 2 times?" That's not hard to write code for... both MERCI and my module have done that in 1 single function. Introducing an entire API (and another module) to solve such a simple problem doesn't seem entirely worthwhile, but I can't stop you... If you want to build it out then I'd gladly offer my code and consider integrating an extension of it into Entity Availability, but I don't see it really solving any problems. Instead it just adds an additional module requirement.
By doing this, you're creating storage mechanisms that duplicate functionality that is already built. You then have to write CRUD functions for those storage mechanisms, and you're essential re-inventing the wheel. Also, you're potentially duplicating data models. If I want to use an entity with a date field, then the date value gets stored in the date field tables, and then in the reservation table as well. I already have entities and fields all over the place, because this is Drupal, and Drupal makes heavy use of entities and fields. I wouldn't want to introduce a whole new data storage/retrieval mechanism when all of that is already being done.
All that said, I do hear your points. I understand what you're trying to do. Here's some recommendations.
This is about as complex as your API should be if you want it to be what you are describing...
I would then extend AvailableObject as an AvailableEntity drupal Entity
I would then extend ReservationObject as a ReservationEntity drupal Entity...
it works, and opens up the door to "Google Calendar Availabliltiy" etc... but you can see how painfully simple the API is.. it may not be worthwhile...
Comment #36
tmsimont commentedoh i guess each ReservationObject and AvailableObject would then need ::load() and ::identifier() as well
Comment #37
tmsimont commentedas for "Find Next Available Reservation"
I think that gets into the territory of
ReservationConflictObjectsandReservationConflictResolutionObjectsOffering the "next available reservation" may be worthless to implementers of your API, but "resolving a conflict between reservations" is more applicable to other implementers.
I have already got Conflicts and Resoultions working as PHP objects in my EntityAvailability module for this very reason.
Comment #38
tmsimont commentedi put up some docs on Entity Availability... needs more work yet but the code is well commented.
http://d7.westernascentinc.com/open-source-projects/entity-availability
Comment #39
skriptble commentedIf I said that I didn't want this API to expand or that I only wanted to use this one CRUD method of building a table that stores everything together, then I apologize because that's not what I meant.
I want to write a module that allows for reservations. It isn't a good fit for MERCI. Neither is the module you're building. I want a different type of user interface, and I want to use different types of components. This shouldn't mean I should rewrite all the functions you guys have already written... twice. I need to validate. I need to save reservations. I'll want to be able to get the next available reservation. Using an API, that becomes free functionality for both your module and MERCI. If your validation system for dates and entities is a plugin for the API, I can use that system for my module now.
I'll concede that this probably shouldn't be as simple as I've been wanting it to be. You're right, having an uber simple API is silly. So let's build a good default storage system, make it a good default. Perhaps an entity. I checked out how Voting API does it's storage, it saves the class you want to use as a variable. The Reserve API could do this as well. You mentioned not wanting to double save data when using the Date module? The save function would do nothing, so you're double saving. The validate function would fetch the fields you specified using the date module, and validate using those.
I think the real advantages come when we go from simply just reserving stuff (which is still where I want to start) to integration: Views, Rules, etc.... Sure, we can implement these pretty easily in our own modules, but why not get it for free? After all, we're using Entity API instead of using the core Entity system because it gives us stuff for free.
Also, if we're accessing the data in a uniform way (with our standardized backend) we can also get statistics. Reports and statistics, views based or based on something else, isn't something that we should have to write more than once.
So you've shifted my thinking. Complex made up of simple parts. Our two modules are not going to be the last reservation modules created. Someone will come along and will want to create a module to reserving something that neither of our modules handles (possibly they want something more specific). If we have an API, their module can now not repeat functionality we've already built, and they can give back to our module (more free stuff).
Comment #40
skriptble commentedNow that I'm actually writing this I think I know how to solve the "saving dates twice" problem. If you use the date module to store the beginning and ending times instead of the default reservation table, you can never actually submit the beginning and end times and simply hook in when a reservation loads and populate those field from the Date field. Although I wonder how useful this is. I know storing the same data twice is bad, but being able to run validation as an SQL query is most likely faster than running an SQL select query and then running over the same operations with PHP.
Although I think if we do wind up going the route of statistics and other useful information/functions, we'll want to keep the dates stored in one table so if you're running through a bunch of them (say you wanna see the reservation trends over the past 3 years), it'll definitely be faster if all that's in 1 table. But it's extendable.... so you can always write a new storage class if you want to use the date field to store it only in 1 place, I figure as long as the retrieveReservation function returns a fully populated Reservation object it'll work fine.
Comment #41
tmsimont commentedif you are comfortable with using Date fields and Entity Reference fields I think you could rewrite aspects of the Entity Availability module to get it to work just the way you want.
I avoided the double storage problem by simply offloading 100% of the data storage to the Entity and Field APIs. The "reservation" is just a relationship between entities that are designated as the "reserving entities" that can reserve the "reservable entity." The reserving entities and the reservable entity are just Drupal Entities with Fields. The cross-referencing and date storage is handled by Entity Reference and Date fields.
If you want to refactor aspects of it, these would be starting points to make it more like what you want:
1) re-write
entityavailability_check_availabilityand_entityavailability_get_flagged_availability_date_rangesto use 1 giant query that gets flagged availability and at the same time checks against specific dates2) write queries and integrate them with views to pull reservation instances based on involved entities
3) clean up the confusing "Entity Availability Rule" wizard UI and make it cooler / make more sense
4) allow other modules to quickly create default Entity Availability Rules in some kind of hook or module.entityavailabiltiy_deafults.inc file or something
5) extend the EntityAvailabilityResolution to provide "Find next reservation" option
If you really want to get ambitious you could re-write large portions of it into PHP Classes, which could then be reverse engineered to come up with some kind of extendible design pattern that could then be used for service-based storage and management...
The end result would be something that MERCI could use for managing resource relationships where a "resource" is any drupal entity and the "reservation" is any drupal entity.
It would then also be useable by my original purpose for doing all of this: the Date Set Management module
Comment #42
skriptble commentedIf you're okay with it, instead of writing my own module to implement it I can overhaul your module to serve both our purposes. I think I might still write a small module that provides a single field you can attach to an entity that gives a reservation. I really love the idea of something really simple and compact for people who just want to be able to reserve and not much else. Added bonus, having 2 very different use cases allows us to stress test the API and figure out which functionality sits in the API and which sits in the implementation layer.
I think a storage layer that allows to have the storage in a single succinct table OR have it live in the field storage system. If we can figure out a good method to make it pluggable, our API will definitely be able to handle extension to other services like Google Calendar, MongoDB, and who knows what else. I think that's a good starting place, but that doesn't mean we can't work on multiple parts at once.
Comment #43
tmsimont commented@darrick I added some basic node support... but it's a little ugly.
i'll put up a basic reservation/reservable example on that docs page
Comment #44
tmsimont commented@skriptble -- we should develop tests... i haven't tried using D7's testing mechanism yet. also, throughout your overhaul, if you could commit locally in git as frequently as possible and use the issue queue to post patches, that would help me track your changes to the code.
if you pull it down please pull the most recent version i just committed like 2 minutes ago
Comment #45
skriptble commentedI can always fork and then we can merge later down the line. I commit like the wind usually, so no need to worry about not having a lot of commits.
Comment #46
tmsimont commentedfyi i put up a step-by-step tutorial of how to create a basic reservation system with Entity Availability here: http://d7.westernascentinc.com/example-simple-reservations-nodes
Comment #47
darrick commentedI've done a number of commits over the weekend. Still using behaviors because so far that's the easiest way to refactor all the code. I've been splitting up all the core validations in the current 2.x branch into separate modules. (i.e. merci_permissions, merci_hours, merci_blackout_dates, etc.) I also moved the merci_entity to a separate module and am not focusing on that at the moment.
The merci_api module implements a entityreference behavior. Currently only the validate and settingsForm functions. Two hooks are then called. hook_merci_settings_form and hook_merci_validate.
So for the basic validation of "is the item available at the selected time." hook_merci_settings_form allows the user to select the date field for the validation and hook_merci_validate does the check.
For merci_hours hook_merci_setttings_form selects the entityreference field which holds the timefields for hours of operation and also selects the name of the timefield in the entity. This allows a user to define a entity (i.e. location) with timefields for hours open. You can then create new entity content for a number of different locations and then under the reservable item choose which location the item is located at.
Merci_blackout_dates works similar to merci_hours. Choose the entityreference holding the blackout dates and the choose the datefield holding the blackout dates. Blackout dates are like holidays or staff retreats. The entity can be the same as that holding the hours of operation.
For merci_permissions I added a entityreference to the user entity to hold which items the user can reserve. hook_merci_settings_form then allow the user to select the name of that field. Currently I'm not liking this approach. Although I'd like the ability to to define permissions on a per user basis as opposed to a group basis I would prefer the permissions to be at first group based and then fine tuned with user permission overrides.
Here's what the entityreference settings looks like:

I have yet to start work on checkout/checkin workflow or item status (broken, lost, etc.) Which will both affect whether or not the item can be reserved.
So far I've been trying to move everything into fields. So the module won't be locked down to specific entities and one can take full avantage of tokens, rules, views, features, etc.
I do like what @tmsimont has done as far as creating rule events should validation fail for some reason.
I'm not locked into using the behavior plugin type. It's just so far the validation function I've written depend on certain field types existing and the behavior plugin allows for one to easily select the names of those fields as opposed to hard coding a field name within a bundle and entity type.
Comment #48
tmsimont commentedlooks great -- I like that you have it on the entityreference field settings form. Is it possible to have a specific entity's Merci blackout dates field control blackouts?
Comment #49
darrick commented@tmsimont
Yes. For the content type defining the reservable entity you add a entityreference field to hold the reference with the blackout dates. At first I thought about using your approach where the blackout entity would be the restricting entity but then you would have all or nothing if different items had different blackout dates. I.e. with my approach you could have a professor control the days they aren't available. I.e. add a entityreference field to the user entity to reference the users blackout dates entity. Then under the settings for the entityreference field for the reservation entity choose that field and the fieldname for the datetime of the blackout entity. When you go to reserve time with professor it will lookup the referenced blackout entity and validate based on the datetimes added by the professor.
Hours of operation works about the same except rather then a datefield I'm using a timefield (http://drupal.org/project/timefield).
Now that I'm typing this I could see some work to do to allow a shortcut for the blackout date and hours of operation entities to be the same as the reservable entity.
I'm also going to move the validation of basic availability to a separate module so it would be possible for you to use your code for availability and then also my code for blackout dates and hours of operation on top of that for extended validation.
Comment #50
darrick commentedHi all
I've been doing a bit more work on 3.x branch and posted an update here: https://drupal.org/node/2151715#comment-8262453
I'd like to close this issue in lieu of the other. I still haven't seen a good reason to define any custom entities or bundles yet. I also felt I had to step back from trying to figure out some one size fits all solution. But trying to keep things as modular and abstract as possible and do still appreciate your input.