views:

82

answers:

2

In my application, these "planners" (essentially, article ideas) follow predetermined templates, written in Markdown, with some specific syntax here:

Please write your answer in the following textbox: [...]

Please write your answer in the following textarea:
...So here, on line, you should write one thing.
...Here, on line 2, you should write another.
...
...
...

Essentially, [...] is a text input, and a group of lines starting with ... are a textarea. That's not really the issue - it's just to explain what part of this code is doing.

On actions new and edit, the standard planner form is displayed, with the correct fields based on the template (for new) or current planner body (for edit). On save, the template's fields are filled in with params[:fields], and the resulting Markdown is saved as the planner's body. The code, I'd hope, is now possible to follow, knowing this context. Only relevant controller code is provided, and it uses make_resourceful.

class Staff::PlannersController < StaffController

  make_resourceful do
    actions :all

    before :create do
      find_planner_format
      if @planner_format
        current_object.body = fields_in_template @planner_format.body
      else
        flash[:error] = 'Planner format not found!'
        redirect_to staff_planners_path
      end
      current_object.user = @current_user
    end

    before :update do
      current_object.body = fields_in_template(current_object.body)
    end
  end

private

  def fields_in_template(template)
    fields = params[:fields] || {}
    if fields[:inline]
      template.gsub! /\[\.\.\..*\]/ do
        "[...#{fields[:inline].shift}]"
      end
    end
    if fields[:block]
      template.gsub! /^\.{3}.*(\n\.{3}.*)*$/ do
        fields[:block].shift.split("\n").collect { |line|
          "...#{line}"
        }.join("\n")
      end
    end
    current_object.body = template
  end

end

And now, the mystery: in the update action, changes to the body are not saved. After debugging, I've determined that the issue does not lie only in current_object.save, since the following before :update code does what you would expect:

before :update do
  current_object.body = 'test string'
end

In fact, even this gets the expected result:

before :update do
  current_object.body = fields_in_template(current_object.body) + 'a'
end

So now, the question: why is Rails so insistent that it not save the result of the function - and even then, only when it comes from update? More debugging showed that the object attribute is set, and even claims to save successfully, but reloading the object after save reverts the changes.

At first it looked like the resulting string was just a "poisoned" variable of sorts, and that rebuilding the string by appending "a" removed that strange state. However, the following code, which ought to add an "a" and remove it again, also failed to save.

before :update do
  new_body = fields_in_template(current_object.body) + 'a'
  new_body.slice! -1
  current_object.body = new_body
end

This is just bizarre to me. What am I doing wrong here, and what can I possibly do to debug further? (Or if you happen to instantly see my mistake, that'd be nice, too...)

EDIT: After checking SQL logs (not sure why I didn't think to do this earlier), it would seem that Rails doesn't seem to acknowledge the new body attribute as actually being different, even though checking the string in the debugger confirms that it is. As such, Rails doesn't even run an UPDATE query unless something else is modified, in which case body is not included.

A: 

I'm not a Ruby programmer but does adding an 'a' convert the type of the variable to string? Maybe your variable is of the wrong type without adding 'a'.

It occurred to me, but I checked in the debugger - it's a string =/
Matchu
+1  A: 

Got it! Sometimes it just helps to state the question out loud...

The deal is, I had forgotten that, when passing current_object.body to fields_in_template, it was being passed by reference. As such, all gsub! methods were running directly on current_object.body, so Rails acknowledged no real "changes" by the time I set body to what had just been set.

The solution:

def fields_in_template(template)
  template = template.dup
  # ...
end

Thanks for letting me talk to myself, and mission accomplished!

Matchu
Using gsub! and sub! on any parameters is usually hazardous for this very reason. When in doubt, clone with Object#dup or use chained gsub calls.
tadman