copasetickid / draftsman

Ruby gem that lets you create draft versions of your database records.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

draft_update updates published object and draft

katlandreth opened this issue · comments

I thought I could use Draftsman to save and update drafts of objects without also saving to the original object, but it looks like the draft_update method updates and saves both the original object and the draft.

Right now I'm saving an object (a post) as a draft until published, and I'm publishing it through a custom publish action in my controller. After publishing I'd like to be able to go back and create/update a draft of that published post without also updating the published post. I can't seem to find a method in the documentation that does this.

Is there a method that updates just the draft so that a published object isn't immediately updated when its draft is updated?

I think you need to provide some examples of your custom publishing method.

So the draft_update method always updates the draft state of the object, if you wish to publish it you have the publish! which makes a release.

You can still go ahead and create more drafts which will remain draft until you hit another publish... that is how I'm using it.

Thanks for taking the time to comment!

This is my publish method - it does use publish! to publish the draft.

#entries_controller.rb
def publish
    @entry = Entry.find(params[:entry_id])
    if @entry.draft?
      @entry.draft.publish!
      respond_with(@entry, :location => edit_entry_path(@entry.id))
    else
      @entry.published_at = Time.now
      @entry.save!
      respond_with(@entry, :location => edit_entry_path(@entry.id))
    end
end

After I publish though, all updates I make update the published post as well as the draft.

In the comments above the definition for draft_update I see this:

# Updates object and records a draft for an `update` event. If the draft is being updated to the object's original
# state, the draft is destroyed. Returns `true` or `false` depending on if the object passed validation and the save 
# was successful.
def draft_update
...

...which makes me think the draft_update method is supposed to update the original object as well as the draft object. Unfortunately, that's not the behavior I want.

You can still go ahead and create more drafts which will remain draft until you hit another publish

That is the behavior I want, but that's not what I'm getting.

I can see in my server log that when I hit the update action in my controller that both the post object and the draft object are getting updated. In the public section of my app, I can see the published post has all of the changes I made in the draft, even though I haven't re-published it.

Started PATCH "/admin/entries/162" for ::1 at 2016-06-07 17:11:35 -0500
Processing by Booqcms::EntriesController#update as HTML
  Parameters: {"utf8"=>"", "authenticity_token"=>"blah", "entry"=>{"author_name"=>"", "post_type"=>"blog", "content_format"=>"written", "all_tags"=>"", "slug"=>"", "title"=>"this kid", "payload"=>"Slim allows you to write very minimal templates which are easy to maintain and pretty much guarantees that you write well-formed HTML and XML\r\nWe also think that the Slim syntax is also aesthetic and makes it much more fun to write templates. Since you can use Slim as a drop-in replacement in all the major framework you can start easily.\r\n**The Slim** architecture is very flexible and allows you to write syntax extensions and plugins.\r\n"}, "commit"=>"Update Entry", "id"=>"162"}
  Booqcms::User Load (0.2ms)  SELECT  "booqcms_users".* FROM "booqcms_users" WHERE "booqcms_users"."id" = $1  ORDER BY "booqcms_users"."id" ASC LIMIT 1  [["id", 1]]
  Booqcms::Entry Load (0.2ms)  SELECT  "booqcms_entries".* FROM "booqcms_entries" WHERE "booqcms_entries"."id" = $1 LIMIT 1  [["id", 162]]
  Booqcms::Tag Load (0.2ms)  SELECT "booqcms_tags".* FROM "booqcms_tags" INNER JOIN "booqcms_taggings" ON "booqcms_tags"."id" = "booqcms_taggings"."tag_id" WHERE "booqcms_taggings"."entry_id" = $1  [["entry_id", 162]]
  Draftsman::Draft Load (0.2ms)  SELECT  "drafts".* FROM "drafts" WHERE "drafts"."id" = $1 LIMIT 1  [["id", 43]]
   (0.1ms)  BEGIN
DEPRECATION WARNING: `serialized_attributes` is deprecated without replacement, and will be removed in Rails 5.0. (called from update at /Users/katherinelandreth/Desktop/Rails_Apps/booqcms/blog/booqcms/app/controllers/booqcms/entries_controller.rb:39)

  SQL (0.3ms)  UPDATE "drafts" SET "whodunnit" = $1, "object" = $2, "object_changes" = $3, "updated_at" = $4 WHERE "drafts"."id" = $5  [["whodunnit", "#<Booqcms::User:0x007f9c16f399b8>"], ["object", "---\nid: 162\npost_type: blog\ntitle: this kid\nslug: ''\npayload: \"Slim allows you to write very minimal templates which are easy to maintain\n  and pretty much guarantees that you write well-formed HTML and XML\\r\\nWe also think\n  that the Slim syntax is also aesthetic and makes it much more fun to write templates.\n  Since you can use Slim as a drop-in replacement in all the major framework you can\n  start easily.\\r\\n**The Slim** architecture is very flexible and allows you to write\n  syntax extensions and plugins.\\r\\n\"\nuser_id: \nauthor_name: ''\npublished_at: 2016-06-07 19:10:06.048073000 Z\ncreated_at: 2016-06-06 22:45:28.269320000 Z\nupdated_at: 2016-06-07 21:40:11.473095000 Z\nfeatured_image: https://booqcms.s3.amazonaws.com/uploads/booqcms/medium/file/179/watercolormap.jpg\ncontent_format: written\ndraft_id: 43\ntrashed_at: \n"], ["object_changes", "--- !ruby/hash:ActiveSupport::HashWithIndifferentAccess\npayload:\n- \"Slim allows you to write very minimal templates which are easy to maintain and\n  pretty much guarantees that you write well-formed HTML and XML\\r\\nWe also think\n  that the Slim syntax is also aesthetic and makes it much more fun to write templates.\n  Since you can use Slim as a drop-in replacement in all the major framework you can\n  start easily.\\r\\nThe Slim architecture is very flexible and allows you to write\n  syntax extensions and plugins.\\r\\n\"\n- \"Slim allows you to write very minimal templates which are easy to maintain and\n  pretty much guarantees that you write well-formed HTML and XML\\r\\nWe also think\n  that the Slim syntax is also aesthetic and makes it much more fun to write templates.\n  Since you can use Slim as a drop-in replacement in all the major framework you can\n  start easily.\\r\\n**The Slim** architecture is very flexible and allows you to write\n  syntax extensions and plugins.\\r\\n\"\nupdated_at:\n- 2016-06-07 19:10:06.049948000 Z\n- 2016-06-07 19:11:28.224119000 Z\n"], ["updated_at", "2016-06-07 22:11:35.869510"], ["id", 43]]


  SQL (0.2ms)  UPDATE "booqcms_entries" SET "payload" = $1, "updated_at" = $2 WHERE "booqcms_entries"."id" = $3  [["payload", "Slim allows you to write very minimal templates which are easy to maintain and pretty much guarantees that you write well-formed HTML and XML\r\nWe also think that the Slim syntax is also aesthetic and makes it much more fun to write templates. Since you can use Slim as a drop-in replacement in all the major framework you can start easily.\r\n**The Slim** architecture is very flexible and allows you to write syntax extensions and plugins.\r\n"], ["updated_at", "2016-06-07 22:11:35.871216"], ["id", 162]]
   (0.8ms)  COMMIT

I think my update action might be where the issue is, so here it is:

#entries_controller.rb
  def update
      @entry.attributes = entry_params
      if @entry.draft?
        @entry.draft_update
        flash[:success] = 'a draft of this entry was saved'
        render :edit
      else
        @entry.draft_creation
        flash[:error] = 'there was an error saving this draft'
        render :edit
      end
    end

If I'm just expecting Draftsman to work in a way that it wasn't intended, I figure I'll just roll my own drafting solution since what I want seems pretty straight forward. Otherwise, I must be misunderstanding how to use Draftsman I could use some guidance.

This is what i have only on my edit action as a before action.

  before_action :reify_post, only: [:edit]

  def reify_post
    @original_post = @post
    @post = @post.draft.reify if @post.draft?
  end

And in the form i either recieve the original post or the @post through @original_post ||= resource , makes sense?

Therefore i create new drafts upon the released version, but the currently published stays intact.

@katlandreth What happens if you change the update action to look like this?

def update
  @entry.attributes = entry_params
  if @entry.draft_update
    flash[:success] = 'a draft of this entry was saved'
    render :edit
  else
    flash[:error] = 'there was an error saving this draft'
    render :edit
  end
end

#draft_creation is intended to be used when you're creating a new record. #draft_update is intended to be used when you're updating a record. Think of these 2 methods as being focused on the @entry record, not the state of the draft. Hope that makes sense. I'll have to review if the docs could be clearer about that.

I wonder if I should change the API to include a generic method similar to ActiveRecord's #save, that works no matter what the state of the main record's #persisted? is. Perhaps it could be called #save_draft. I think I like that.

On #publish!, the main @entry item should be updated, and the associated draft record should be destroyed. #publish! should always destroy the associated draft record.

Thanks for the reply!

My update action looked like that when I initially set up Draftsman, but I was getting an "undefined method for nil..." error when I tried to update the post's draft after publishing it once . I assumed that was because the draft was being destroyed after publishing, so I added in the conditional to re-create a draft if the post didn't already have one and the error went away.

I fixed/changed some other things at the same time though, so I'll try using the version you posted again and post back here with the results. Thanks again!

I changed my update action to the above suggestion from @chrisdpeters.

Then I created a new unpublished post, added some text to the body of the post and submitted the form to hit the update action in my controller. Just like before, I can see in my server logs that the draft was updated, as well as the original post object (is that supposed to happen?).

I then published the post using a link that hits my publish action. After that, I could see the post in my published posts index view. All good there. Then, going back to my update form, made some changes and clicked submit again, but this time I got the error side of the fork in my update action ie there was an error saving this draft.

I don't see any errors in the server log, but instead of Update drafts... and Update booqcms_entries... followed by a COMMIT like I was seeing above, now I only see BEGIN immediately followed by COMMIT.

Here's the relevant chunk of the log just in case I'm not describing this clearly:

Started PATCH "/admin/entries/163" for ::1 at 2016-06-08 12:38:19 -0500
Processing by Booqcms::EntriesController#update as HTML
  Parameters: {"utf8"=>"", "authenticity_token"=>"8jCtWZ80X5usJIZnjCps13yvbBMpGIJDWDMBAa7spIEYtyZECXauRWl+AHcsf2E5q0yXeJibaBgfTOg5UUrOlA==", "entry"=>{"author_name"=>"", "post_type"=>"blog", "content_format"=>"written", "all_tags"=>"", "slug"=>"", "title"=>"Cherries", "payload"=>"It allows you to offload your entire storage infrastructure and offers better scalability, reliability, and speed than just storing files on the filesystem.\r\n\r\nAWS S3, or similar storage services, are important when architecting applications for scale and are a perfect complement to Heroku’s ephemeral filesystem.\r\nIt allows you to offload your entire storage infrastructure and offers better scalability, reliability, and speed than just storing files on the filesystem.\r\n\r\n**AWS S3**, or similar storage services, are important when architecting and are a perfect complement to Heroku’s ephemeral filesystem."}, "id"=>"163"}
  Booqcms::User Load (0.3ms)  SELECT  "booqcms_users".* FROM "booqcms_users" WHERE "booqcms_users"."id" = $1  ORDER BY "booqcms_users"."id" ASC LIMIT 1  [["id", 1]]
  Booqcms::Entry Load (0.2ms)  SELECT  "booqcms_entries".* FROM "booqcms_entries" WHERE "booqcms_entries"."id" = $1 LIMIT 1  [["id", 163]]
  Booqcms::Tag Load (0.2ms)  SELECT "booqcms_tags".* FROM "booqcms_tags" INNER JOIN "booqcms_taggings" ON "booqcms_tags"."id" = "booqcms_taggings"."tag_id" WHERE "booqcms_taggings"."entry_id" = $1  [["entry_id", 163]]
   (0.1ms)  BEGIN
   (0.1ms)  COMMIT
Rendered etc. etc. ...

I'll keep poking around and see what I can come up with. I'll also give @alexanderkustov 's suggestion a try. Thanks for that!

Annnnnd now it's working exactly as expected.

The only thing I changed between my last post and now was using flash.now instead of just flash in my update action. I don't think that would have been causing the issues I was having... must have fixed when changing the update action to remove that if entry.draft? conditional that used draft_update and draft_create in the update action... maybe a cache cleared or something?

Anyway, thank you both very much for your help @alexanderkustov and @chrisdpeters !

@katlandreth Glad you got it sorted out. Thank you for sharing your experience.

I think that we will be able to make the API cleaner based on your feedback. I'll create a new issue for a #save_draft method that intelligently calls #draft_creation or #draft_update based on the state of the object being drafted..