Conflicting updates

Just had my first instance of conflicting updates - I edited a page
while somebody else was editing it too without realising it -
luckily in this instance we were both trying to do the same thing, but
this could lead to data loss without warning.

Time for some big red flashing warning lights.

I’m going to add a hidden field to the Page editing page containing the
last updated value of the page - that will be sent back to
the server and the validation should check that the updated_at field in
the database matches - if not, a bit red error saying
something like

“This page has been modified since you last loaded it. The last user to
edit this page was . If you continue
saving this page their changes may be lost”.

There will then by a checkbox with that option saying something like
'ignore ‘s changes and save my changes anyway’
allowing you to continue with your save if that’s what is appropriate -
that will set a second field that will tell the object to
ignore that validation. I’m not 100% sure of doing this - you could
always copy/paste and just do it again, but I think this way is
probably nicer.

Anybody have any objections to handling conflicts in this way? Or better
ideas (that DON’T involve writing making a version control
system for pages)? I’ll probably check in a change tomorrow if there
isn’t any.

Dan.

Good idea Daniel, although this method may still run into
synchronisation issues - I think the best way would be to use
optimistic locking and defer it until you save in the database - that
way it’s atomic. Then you’d throw the error to the user after you
tried to save and failed.

http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html

For the time being, using what you mentioned (via Rails’ built-in
locking - lock_version is the column name IIRC) would probably be best.

Sean

Daniel S. wrote:

“This page has been modified since you last loaded it. The last user to
edit this page was . If you continue
saving this page their changes may be lost”.

There will then by a checkbox with that option saying something like
'ignore ‘s changes and save my changes anyway’
allowing you to continue with your save if that’s what is appropriate -
that will set a second field that will tell the object to
ignore that validation.

I like the notification of the conflict - it’s the warning message I
wonder about. Is it really possible for the user to make this decision?
How can I know whether to keep my changes or the other user’s without
knowing what the other user did?

From a use case perspective one of the following 3 options must happen:

1.) User is notified of conflict. Must call/IM/email other user to
discuss their changes to make a decision. User makes the choice to keep
his or other user’s work only.

2.) User is notified of conflict. System lets him review the other
user’s changes so that he can make the determination of what to
save/delete without needing to consult the other user. This could also
let him cut/paste from the other users work to manually merge the
changes. (If you really want to get geeky, provide a diff view).

3.) User is notified of conflict. The other user wins. The only
option is to copy their work to somewhere and “check out” the new
version of the page to start over.

All this assumes that we aren’t going down the road of: User can’t even
open the page because user B has that page locked (opened).

1.) User is notified of conflict. Must call/IM/email other user to
discuss their changes to make a decision. User makes the
choice to keep his or other user’s work only.

3.) User is notified of conflict. The other user wins. The only
option is to copy their work to somewhere and “check out” the new
version of the page to start over.

I’ll be implementing at least (3) and then probably extending it to (1)
unless I get busy or bored.

2.) User is notified of conflict. System lets him review the other
user’s changes so that he can make the determination of what to
save/delete without needing to consult the other user. This
could also
let him cut/paste from the other users work to manually merge the
changes. (If you really want to get geeky, provide a diff view).

This would be best, but I’m throwing it into the too hard basket. If a
page was just a big text field, this would be fairly
straightforward, but when there’s multiple fields and the possibility of
adding new fields with extensions this is way beyond
anything I care to think about.

All this assumes that we aren’t going down the road of: User
can’t even open the page because user B has that page locked (opened).

That’s really not possible over http (well, possible, but not reliably
so).

Dan.

Sean C. wrote:

For the time being, using what you mentioned (via Rails’ built-in
locking - lock_version is the column name IIRC) would probably be best.

Yes, please. Adding optimistic locking would be great.


John L.
http://wiseheartdesign.com

On 20/03/2007, at 3:51 PM, Daniel S. wrote:

All this assumes that we aren’t going down the road of: User
can’t even open the page because user B has that page locked
(opened).

That’s really not possible over http (well, possible, but not
reliably so).

Well, it is possible using pessimistic locking (HTTP’s not involved)

  • but I think for a CMS, optimistic would be the way to go.

http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html

All this assumes that we aren’t going down the road of: User
can’t even open the page because user B has that page locked
(opened).

That’s really not possible over http (well, possible, but not
reliably so).

Well, it is possible using pessimistic locking (HTTP’s not involved)

  • but I think for a CMS, optimistic would be the way to go.

But pessimistic locking requires that the object be manually locked and
then unlocked - It requires statefulness. If a user goes to
edit a page, the app locks the page and the user closes their browser,
when does the object get unlocked? You could do it in a
roundabout way be making the edit page constantly set a ‘I’m on ur page
editing ur data’ request using ajax that updates a ‘last
locked’ field in the db, but it’s messy messy messy.

Dan.

For my two cents: I would be pleased to see a snazzy little red star or
some-such beside a page or snippet (on the list pages/list snippets)
which is currently being edited by someone else. Maybe with the name of
the other user in a mouseover? That way I could decide to do something
else, or check with the other user for clearance.

At the moment, Jens is “digitally restricted” (ie. his fingers are not
allowed to click/type outside of his area) to the German pages and I
stick to the English side. This works too.

Happy Tuesday!
N.

Daniel S. schrieb:

Jacob B. wrote:

Don’t know if this is a good idea, just throwing it out there…

Some CMSs use the concept of “checked out” and “checked in” content. As
soon as a user starts editing a page it becomes “checked out” and nobody
else can edit it. You can even save your changes without checking-in
the file. Then, you can display in the tree view who has what files
“checked out”. And, perhaps an admin would have the ability to “force
check in” for a page.

Basically, this is:

User can’t even open the page because user B has that page locked
(opened).

With some terminology to display to the end users.

Or, Maybe it’s time to consider addding versioning and history tracking.
If we detect a simulatneous edit, we simply save both versions… one of
those versions is the later version and one of them is the earlier
version. The User saving the later version would be notified of the
conflict and be able to use the history and rollback functionality to
resolve the conflict…

Don’t know if this is a good idea, just throwing it out there…

Some CMSs use the concept of “checked out” and “checked in” content. As
soon as a user starts editing a page it becomes “checked out” and nobody
else can edit it. You can even save your changes without checking-in
the file. Then, you can display in the tree view who has what files
“checked out”. And, perhaps an admin would have the ability to “force
check in” for a page.

Basically, this is:

User can’t even open the page because user B has that page locked
(opened).

With some terminology to display to the end users.

On 3/20/07, Jacob B. [email protected] wrote:

Or, Maybe it’s time to consider addding versioning and history tracking.
If we detect a simulatneous edit, we simply save both versions… one of
those versions is the later version and one of them is the earlier
version. The User saving the later version would be notified of the
conflict and be able to use the history and rollback functionality to
resolve the conflict…

This is definitely something I’ve been considering as well, there’s been
lots of cases where i’ve run into the need for version/history tracking,
usually with the outcome that I have to go back to a database backup
from
the previous night for a client and get them to re-do a lot of work. I
was
thinking of implementing something like the writeboard versions system
that
is included in basecamp, although I haven’t yet looked into how feasible
this is with the extensions system.

In any case, this approach certainly gets my vote! Especially if there
is
also a notification when saving that modifications have been made since
the
page/snippet/layout was opened.

Kev

Yes, there will be issues with Extensions… that’s why Version Control
would have to be a part of the core of Radiant, and there would need to
be hooks available for Extension authors so that they can decide how
their page additions should be versioned.

I agree, this is all very non-trivial…

Finally, just because the database tables and on-save hooks are part of
the core, doesn’t mean that the interface to actually work with the
version history, rollback, merge, etc… needs to be part of the initial
feature set. (it can all be done via script/console to start)

On Mar 20, 2007, at 8:13 AM, Jacob B. wrote:

“force
check in” for a page.

As a developer who has used multiple SCMs that have that
“feature”… Oh please no. Dear god, that’s horrid. Having that
kind of locking is a huge mess and tends to create more problems than
it solves. Not the least of which is “how does the admin know if he
should force the check in”.

Or, Maybe it’s time to consider addding versioning and history
tracking.
If we detect a simulatneous edit, we simply save both versions…
one of
those versions is the later version and one of them is the earlier
version. The User saving the later version would be notified of the
conflict and be able to use the history and rollback functionality to
resolve the conflict…

Yes. True version control is probably the way to do. Attempting a
three-way merge and showing conflicts (at least on page parts, a la
Wikipedia) is even better than simply notifying of the issue.
Version control is non-trivial, but extremely useful.

I believe there are functions to find the differences between objects
in ActiveRecord, which could help, although I don’t know how to make
a good API for that to the user. We also do run into issues with
extensions adding fields and possibly new associations. Blarg.

~~ Brian

I’m testing things out for our eventual rollout of 0.6 and I’m
receiving this error when running rake db:migrate (or rake db:migrate
production)
"undefinded local variable of method ‘class_name’ for #Page:0x27a850

0.5.2 was installed and working OK on mysql (pulled the source, not
from the gem)
But the MergeBehaviorsAndPages migration seems to fail every time.

I’m sure I’m missing something, but I don’t know what.

thanks for any help
-Jim

Jim,

Can you list the error that happens on migration? It’s usually
necessary to migrate if coming from a previous version.

Sean

Hey Jim,

I had the same problem when I migrated two sites from a full
installation (locally) of 0.5.2 (Application mode) to 0.6 (Instance
mode) on my server. Unfortunately, after much fussing around and an
embarrassing amount of random “trying stuff” the voodoo gods smiled on
my migration, and I was so happy that I went right on working, and
didn’t write a darn thing down. (my baaahd!)

Among the things I tried/did:

  • Update my gems (the gem package itself to 0.9.2)
  • Backup and delete my 0.5.2 folder, reinstall a clean radiant in
    instance mode and copy my files over.
  • I was using the language_redirect_behavior, which (at some point) I
    also had to disable (on the page level) in the database.

Hope this helps.
Best regards,
N.

Jim G. schrieb:

Hello

I’ve got simple problem to solve.
But I need your assistance. :slight_smile:

I can’t put my design on it.
Is there any tutorial about it?
How to make it working for my site?

Thanks,

Sincerely yours,
Alex P.

I’ve pasted the full rake trace below

On Mar 20, 2007, at 5:28 PM, Sean C. wrote:

production)
"undefinded local variable of method ‘class_name’ for #<Page:
0x27a850>’

0.5.2 was installed and working OK on mysql (pulled the source, not
from the gem)
But the MergeBehaviorsAndPages migration seems to fail every time.

== MergeBehaviorsAndPages: migrating

== MergeBehaviorsAndPages: converting behavior names to class names

rake aborted!
undefined local variable or method class_name' for #<Page:0x14b3334> /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/base.rb:1861:inmethod_missing’
/Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/…/app/models/
page.rb:199:in valid_class_name' /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/validations.rb:818:inrun_validations’
/Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/…/vendor/rails/
activerecord/lib/active_record/validations.rb:816:in run_validations' /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/validations.rb:780:invalid_without_callbacks?’
/Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/…/vendor/rails/
activerecord/lib/active_record/callbacks.rb:299:in valid?' /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/validations.rb:761:insave_without_transactions!’
/Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/…/vendor/rails/
activerecord/lib/active_record/transactions.rb:133:in save!' /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/connection_adapters/abstract/ database_statements.rb:59:intransaction’
/Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/…/vendor/rails/
activerecord/lib/active_record/transactions.rb:95:in transaction' /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/transactions.rb:121:intransaction’
/Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/…/vendor/rails/
activerecord/lib/active_record/transactions.rb:133:in save!' ./db/migrate//010_merge_behaviors_and_pages.rb:13:inreal_up’
./db/migrate//010_merge_behaviors_and_pages.rb:10:in real_up' /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/migration.rb:212:inmigrate’
/usr/local/lib/ruby/1.8/benchmark.rb:293:in measure' /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/migration.rb:212:inmigrate’
/Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/…/vendor/rails/
activerecord/lib/active_record/migration.rb:335:in migrate' /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/migration.rb:330:inmigrate’
/Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/…/vendor/rails/
activerecord/lib/active_record/migration.rb:297:in up' /Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/../vendor/rails/ activerecord/lib/active_record/migration.rb:288:inmigrate’
/Apps/Dev/RadiantCMS/0.6rc2/mental/radiant/config/…/vendor/rails/
railties/lib/tasks/databases.rake:4
/usr/local/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:387:in
execute' /usr/local/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:387:inexecute’
/usr/local/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:357:in invoke' /usr/local/lib/ruby/1.8/thread.rb:135:insynchronize’
/usr/local/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:350:in invoke' /usr/local/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:1906:inrun’
/usr/local/lib/ruby/gems/1.8/gems/rake-0.7.1/lib/rake.rb:1906:in `run’
/usr/local/lib/ruby/gems/1.8/gems/rake-0.7.1/bin/rake:7
/usr/local/bin/rake:18

Hi Alex,

Hmmm, page designs per se belong in the “layouts” tab - you can put your
css there too. If you have small bits of repeating content - those are
called "snippets and belong in the snippets tab.

The documentation is still pretty slim, but you could look here
(http://dev.radiantcms.org/) for the beginnings of a handbook,
explanations of radius tags, and howtos. Other than that, you might
spend some time boning up on Ruby on Rails in order to understand the
underlying structure of Radiant.

Hope this helps,
Nancy

ideafull schrieb: