Some fields get old values in fragment cache in Rails - ruby-on-rails-3

I have a view to show the details of a resource that is saved in the database. Some of the details belong to the Resource model itself, and some details belongs to associated models.
In the view I use fragment caching for the details.
When the user presses a button on the view, a part of the view is replaced by a form, so the user can edit the details witout loading a new page. When the form is opened, the cache is expired (it actually is, I have checked). When the user submits the form (using :remote => true), the form is hidden, and the original content is reloaded using jQuery and render partial.
So far everything work just fine. The original content is reloaded correctly with the new values, from code inside <% cache ... do %> and <% end %>.
The strange thing is when I reload the page, some of the new values are gone. Instead some old values are shown (those that should have been cached after the last submit). And the thing that is even stranger is that some of the values are updated, even if they are in the same new cache file as the wrong values.
As far as I can see, values in associated models are correct, while values in the Resource model are wrong (old). I have wondered if it had something to do with sql caching, but I don't think that's the case, because I think the sql cache should be emptied when reloading the page.
If I open the form and submit again, the data is updated, and everything is fine. That is the data from the last submit is coming into the cache. If I change the data in the form before submitting, it is still the data from the last submit that is included in the cache-file. So it seems that data submitted for the Resource model is delayed by one submit, even if the other fields are updated correctly.
If I turn off caching in development.rb, everything works as expected. All data are updated every time.
I do, by the way, have the same problem on my server.
Anyone that has a clue?

Not sure if this will help, but in the last couple of days I have implemented a cache-key based fragment cache scheme on my own site with some success.
I implemented a get_cache_key function in all my models
def get_cache_key(prefix=nil)
cache_key = []
cache_key << prefix if prefix
cache_key << self
child_objects.in_sort_order.each do |child_object|
cache_key << child_object.get_cache_key
end
return cache_key.flatten
end
In my views I then used
<% cache(#home_page.get_cache_key('some_name_for_fragment')) do %>
...Render View
<% end %>
The models now produce a cache key that will invalidate the cache if parent model or any of its children are changed.
The full write up is here, on my website.
Rails caching strategy using key-based approach

It seems like the data are cached a bit to soon after they are updated. My solution so far is to drop caching in the view if it is less then two minutes since the resource was updated. The fragment is uncached until someone updates the page a bit later. This is not a very good solution, while each resource (30 resources) on the page must be queried for update time each time the page is viewed.

Related

Yii CListView pagination linking to previous result

I have a CListView with pagination showing all results. Everything works fine.
I have a search widget on the page that renders a partial view to replace the existing list with search results.
The first search result page loads, looks great, and even shows the correct number of results in pagination, but if I try to go to one of the next pages, items from the first list are loaded.
Does anyone know what I have to do in order to fix this? Do the search results need their own full view rendered?
Thanks you in advance.
I was mistaken.
The pagination was not linking to the previous result, it was loading everything which the previous result also does, which is why I was mistaken.
I thought that the data provider in the controller kept track of the result set but you have to keep resending the same criteria every time. I was sending the criteria the first time and not on subsequent results so it loaded everything in absence of any constraints. I simply put the search parameters in session and then retrieved them so they could be considered on every request and it fixed the problem.
In short there is no bug, just me being new working with Yii.

rails render form after error in other controller

I have two models in the following releationship: Client has_many products, and Product belong_to client. In the client show view I present a form to create new products, that automatically belong to the current client. The show method in the client controller
def show
#client = Client.find(params[:id])
#products = #client.products.paginate(page: params[:page])
#product = #client.products.new
#product.client_id = #client.id
end
and the show view renders a partial
<h1>New Product:</h1>
<%= render 'shared/product_form' %>
That works, products are correctly created.
When an validation error occurs I set a flash in the product create method and redirect_to the client show page. There I loose the data that has been filled in correctly. I tried save the #product instance variable, which has all the data (including the wrong fields) doing
render client_path(client)
from the product controller, but that produces an error
Missing template /clients/17
with the address being
http://localhost:3000/products
Am I calling this wrong? I know that render ususally renders action of the same controller. Can I somehow render Client::show from the product controller? Is there another way to save the data the user has typed in?
If a validation error occurs you should redirect back to the page that generated the validation error. Ie: if the user is at products/new when they submit the form, then your products#create action should end with render :new to present the products form again.
If your products#create action is receiving the form from clients#show, then you do want to render clients#show with validation errors. In that case, all the information that was completed in the form will be available at params[:product], just like it is coming in to products#create.
You might want to take a look at another answer I wrote recently to understand the flow between controllers.
Specifically, the misunderstanding in your case is as follows:
When you have a validation error the record will not save, so you cannot "go back to that data" because your app has not kept it anywhere. The only copy of the data that was submitted is in the request.
If you REDIRECT you are not forwarding a request, you are responding to the initial POST request (which includes all the form information as params[:product]) by making a new request to a different url. This is why you want to use RENDER.
However, if you try to render clients_path(client), what happens is Ruby will first evaluate clients_path(client) to the string clients/(client.id), or in the example you gave, clients/17.
Then render tries to call render 'clients/17', which you don't have a template for. It's looking for a file called clients/17.html.erb. That's why you get the error you're getting.
So, again, to sum up - your products#create action receives the information that was sent from the form as params[:products]. That information is not available outside of this controller action. So, if there is a validation error, instead of creating the product, this controller action should render the same page that the user came from originally (normally products/new) so that they can see the form they just had (with the information filled back into it if you're using a form builder) and also see the error that prevented saving.
I hope that makes sense, feel free to ask follow-up questions.
Yes, you were calling it wrong.
There are three problems:
render should render a template name, say client/new, shared/form etc. The argument could not be a path or variable. The variable is passed by controller to view, nothing to do with render.
You should not use render for a saving fail. Even if you use a template name as #1 mentioned, you'll end up with a wrong URL like products/create with the client page. That's not acceptable.
My suggestion is to always use redirect for saving fail.
Minor problem. In controller, if you've used #product = #client.products.new, the #product object will have every attributes empty but with a valid client id. So you don't need to assign client id again by #product.client_id = #client.id. But this doesn't hurt the result.
Andrew's great answer plus this:
Where to render comments controller in Rails on model validations failure?
makes the solution more clear.
Your specific example: Just watch out that your create action in the ProductsController has all instance variables it needs to render 'clients/show' from it.

Get ancestor object of un-saved object in Hobo

I'm working on a Hobo app trying to tie together a few models properly.
Activity objects have many Page children. They also have many DataSet children.
Page objects have several different kinds of children. We'll talk about Widget children, but there are several types with the same issue. An instance of a Widget belongs to a Page but also has a belongs_to relationship with a DataSet. Here's the important point: the DataSet must belong to the containing Activity. So for any given #widget:
#widget.page.activity === #widget.data_set.activity
It's easy enough to enforce this constraint in the model with a validation on save. The trick is presenting, within the Widget's form, a select menu of available DataSets which only contains DataSets for the current Activity
I was able to get this working for existing objects using a tag like this:
<data_set-tag: options="&DataSet.activity_is(&this.page.activity)" />
However, for a new Widget, this fails messily, because either &this or &this.page is not yet set. Even for a route which contains the page ID, like /pages/:page_id/widgets/new, I'm not really able to get an Activity to scope the list of DataSets with.
If this was proper Rails, I'd get in to the relevant controller method and make the Activity available to the view as #activity or something of the sort, but in Hobo the controllers seems to be 95% Magic™ and I don't know where to start. The knowledge of which Activity is current must be in there somewhere; how do I get it out?
This is Hobo 1.3.x on Rails 3.0.x.
ETA: The code producing the errors is in the form tag for Widget, like so:
<extend tag="form" for="Widget">
<old-form merge>
<field-list: fields="&this.field_order">
<data_set-tag: options="&DataSet.activity_is(&this.page.activity)" />
</field-list>
</old-form>
</extend>
As I said above, this works for editing existing Widgets, but not new Widgets; the error is undefined method 'page' for nil:NilClass. Bryan Larsen's answer seems to suggest that &this.page should not be null.
it looks like you tried to post this question to the Hobo Users mailing list -- I got a moderation message, but it doesn't appear that your post got posted, nor can I find it to let it through. Please try reposting it, there are several helpful people on the list that don't monitor the Hobo tag here.
In Hobo 1.3, the new action doesn't support part AJAX, so there really isn't much magic. You can just replace the action with your own:
def new_for_page
#activity = Activity.find(...)
#page = Page.find(params[:page_id])
#widget = #page.widgets.new
end
There is a little bit of magic referenced above: if you're in WidgetsController, assigning to #widget will also assign to this.
But as you said, the knowledge is obviously in there somewhere, and your custom controller action shouldn't be necessary.
This statement seems wrong: However, for a new Widget, this fails messily, because either &this or &this.page is not yet set.
It looks like you're properly using owner actions. /pages/:page_id/widgets/new is the route. In widgets_controller it's the new_for_page action. In a new or new_for action, this is set to an unsaved version of the object. In your action, it should have been created with the equivalent of Page.find(params[:page]).widgets.new. In other words, both this and this.page should be populated.
I'm sure you didn't make your statement up out of thin air, so there's probably something else going on.
In the end, it turned out to be syntax. Instead of
<data_set-tag: options="&DataSet.activity_is(&this.page.activity)" />
I needed
<data_set-tag: options="&DataSet.activity_is(#this.page.activity)" />
(note the #).
We actually made this into a helper method, so the final code is
<data_set-tag: options="&DataSet.activity_is(activity_for(#this))" />

Use JS to change <form> from remote to non-remote in Rails 3, HAML

The problem is that i have a remote form that, based on condition, id like to convert to a non-remote form (using UJS), and then submit.
note the form has a file upload.
Here's the details: I have initially rendered the remote form using
= form_for #myobj, :url => {:action=>"remoteAction", :controller=>"myobjects"}, :remote => true do |f|
... (f.fields....)
which produces the HTML:
<form id="new_myobj" class="new_myobj" method="post" accept-charset="UTF-8" data-remote="true" action="/remoteAction">
when i click submit, as expected, the form is submitted 'AS JS'.
in the controller action, i am doing some validation of the fields inside the submitted form.
If all the validations pass, i execute the following .js.haml template:
$('form#new_myobj').removeAttr("data-remote");
$('form#new_myobj').attr('enctype', 'multipart/form-data');
$('form#new_myobj').attr('action', '/myobjects/regularAction');
which successfully changes the HTML on the page (witnessed via Firebug) to:
<form id="new_myobj" class="new_myobj" method="post" accept-charset="UTF-8" enctype="multipart/form-data" action="/myobjects/regularAction">
since the form contains an f.file_field, i have to submit as multipart so the image can be uploaded, and i cannot submit 'AS JS'
now, when i click submit, the controller action 'regularAction' is indeed called, but its still 'AS JS'
the question is, what else do i need to change in the HTML so the form can be submitted non-xhr? is it related to the headers?
jQuery is a bit tricky with the data attributes since it both
reads the HTML5 data tags as well as its own storage bound to the
DOM element, that is also called data. When writing to an attribute
that value gets copied into jQuerys own data storage (presumably
when data("remote") is being called).
However, this only happens
if jQuery’s data is empty for that name. Thus setting the attribute will only work once, after that the "cached" value is being used
even if the attribute changes. In order to really get rid of the
value, we need to remove the attribute and jQuerys own storage
method in that order. The reason is that there’s a high-level
(element.removeData(…)) function and a low level one (jQuery.
removeData(element, …)). The former re-reads the HTML5 data
attribute and stores it in jQuery’s own storage. Using the rather
unusual low level function obviously works as well.
Also, we do really need to remove the attribute -- setting it to
false is not enough since Rails only checks if form.data('remote')
is not undefined (look for it in jquery_ujs.js).
TL;DR:
attr("data-remote") != data("remote")
These two lines make a form non-remote (again). Order matters.
$("form").removeAttr("data-remote");
$("form").removeData("remote");
It’s documented, if you actually know what you’re looking for:
http://api.jquery.com/jQuery.data/ (low level function)
http://blog.madebydna.com/all/code/2011/12/05/ajax-in-rails-3.html
StackOverflow doesn’t allow me to post more than two links, but you can guess the removeData one. The high-level functions are linked from the low level ones.
Avoiding the token authenticity error in Rails 4+:
As Stan commented below, just doing the above will fail with an InvalidAuthenticityToken error. The workaround is easy though, see here for details: https://stackoverflow.com/a/19858504/1684530
The problem is that your approach to disable the Ajax submission isn't quite correct. You need to unbind the JavaScript events that have already been added by rails.js (Rails UJS adapter) to the form.
You can do that by:
$('form#new_myobj').unbind() to unbind all events attached to the form. You also need to $('form#new_myobj').removeAttr('data-remote') and $('form#new_myobj').removeAttr('data-type') to remove data-remote and data-type attributes (if existent).

Using ajax for rails image preview in form

I've been working on this issue for about two days now. I've posted more task specific questions, got great answers, only to figure out the approach I was taking wouldn't work.
Basically, I want a user to be able to upload images in a form and see a preview of the image they upload before submitting the form. The images and the parent form have a has_many relationship. The images are nested in the parent form using fields_for.
I tried using a client side approach, but ran into cross browser and security issues. My current approach I'm trying is to save the images, reload the div portion of the page, and then assign the parent_id to the image after the partent form is submitted (since I will not have a id for the partent form until it is created).
Is it possible to use ajax to submit the fields_for portion of a form and not the entire parent form? Has anyone attempted to do something similar?
Of course you can submit only the fields of that image by using jQuery to select only the fields that you want to send, serialize them and send them as data of the ajax request.
Be careful, you need to send the authtoken, that's why I have type=hidden in the selector
var data = form.find('[name*=image],[type=hidden]').not('[name*=items]').serialize();
$.ajax({url: this.form.attr('action'), context: this, type: 'POST', data: data
The next step with the image I once implemented as storing the in their own database table, returning the client the id to that who then adds it to the form.