I have the following structure in my rails4 app for the posts. Users can comment on the post and replies can be written on the comments. I'd like to use russian-doll-caching with auto-expiring keys on the page, but I don't know how I should exactly do it in this case.
Can sby tell me how to use it in this case?
Models:
#post.rb
belongs_to :user
has_many :post_comments, dependent: :destroy
#post_comments.rb
belongs_to :user
belongs_to :post
has_many :post_comment_replies, dependent: :destroy
#post_comment_replies.rb
belongs_to :user
belongs_to :post_comments
posts/index.html.erb
<div class="post-index new-post-insert">
<%= render #posts %>
</div>
_post.html.erb
<%= post.body %>
<%= post.user.full_name %>
....
<%= render partial: 'posts/post_comments/post_comment', collection: post.post_comments.ordered.included, as: :post_comment, locals: {post: post} %>
_post_comment.html.erb
<%= post_comment.body %>
<%= post_comment.user.full_name %>
......
<%= render partial: 'posts/post_comment_replies/post_comment_reply', collection: post_comment.post_comment_replies.ordered.included, as: :post_comment_reply, locals: { post_comment: post_comment } %>
_post_comment_reply.html.erb
<%= post_comment_reply.user.full_name %>
<%= post_comment_reply.body %>
You need to do a few things
Add touch to your belongs_to relations
The children and grandchildren of Post needs to touch their parents so that the updated_at column updates which in turn invalidates the cache keys.
#post_comments.rb
belongs_to :user
belongs_to :post, touch: true
has_many :post_comment_replies, dependent: :destroy
#post_comment_replies.rb
belongs_to :user
belongs_to :post_comments, touch: true
Add the cache command to your views
posts/index.html.erb
In the main list of posts we want to cache on the latest updated_at for posts and the latest updated_at for the respective user.
<div class="post-index new-post-insert">
<% cache ["posts", #posts.maximum(:updated_at).to_i, #posts.map {|p| p.user.try(:updated_at).to_i}.max] %>
<%= render #posts %>
<% end %>
</div>
_post.html.erb
<% cache ["postlist", post, post.user] %>
<%= post.body %>
<%= post.user.full_name %>
....
<%= render partial: 'posts/post_comments/post_comment', collection: post.post_comments.ordered.included, as: :post_comment, locals: {post: post} %>
<% end %>
_post_comment.html.erb
<% cache ["postcommentlist", post_comment, post_comment.user] %>
<%= post_comment.body %>
<%= post_comment.user.full_name %>
......
<%= render partial: 'posts/post_comment_replies/post_comment_reply', collection: post_comment.post_comment_replies.ordered.included, as: :post_comment_reply, locals: { post_comment: post_comment } %>
<% end %>
_post_comment_reply.html.erb
<% cache ["postcommentreplylist", post_comment_reply, post_comment_reply.user] %>
<%= post_comment_reply.user.full_name %>
<%= post_comment_reply.body %>
<% end %>
This could be improved by using cached: true in the render partial function. However since we want to expire the cache if the user changes their username it becomes a bit tricky.
You can do that if you override all the models cache_key functions.
Why should I use cached: true in render partial?
Instead of calling cache inside each partial (like we do above) we could do
<%= render partial: 'posts/post_comments/post_comment', collection: post.post_comments, cached: true %>
If we only need to cache on the post_comment´s updated_at.
The difference between the two are that when we cache inside the partial Rails issues a get command to the cachestore (e.g. memcache) one time per object. Therefore if you have 50 postcomments, there will be 50 separate requests to memcached to retrieve all.
But if we instead use cached: true in the render call Rails will issue a multi_get request to memcached and retrieve all 50 objects in one request. Thus improving page loadtime. In tests we have conducted in our production env. it decreased the page loadtime by ~50ms - ~200ms depending on the amount of data.
Related
I have a model that has two different associations with another model:
class Order < ApplicationRecord
belongs_to :customer, required: true
belongs_to :delivery_address,
class_name: 'Address',
foreign_key: :delivery_address_id
belongs_to :collection_address,
class_name: 'Address',
foreign_key: :collection_address_id
accepts_nested_attributes_for :collection_address, :delivery_address
end
It accepts nested attributes:
<h1>Edit order</h1>
<%= simple_form_for #order, url: order_path do |form| %>
<strong>Collection address</strong>
<%= form.simple_fields_for #collection_address, as: :collection_address do |collection_address_form| %>
<%= collection_address_form.input :address1 %>
...
<% end %>
<br>
<strong>Re-delivery address</strong>
<%= form.simple_fields_for #redelivery_address, as: :delivery_address do |delivery_address_form| %>
<%= delivery_address_form.input :address1 %>
...
<% end %>
<%= form.button :submit %>
<% end %>
I want to receive two sets of params: params[:collection_address and params[:delivery_address.
However when the form submits, the controller does not receive params for both addresses. Instead it receives one set under the key params[:address].
I understand that form_for accepts the :as option. I've used it above. I expect to gete params[:delivery_address] and params[:collection_address] but I only get params[:address].
Is this supposed to work with Simple Form or do I need to do something different? Is there anything wrong with my code that's preventing the two sets of params from being created?
You can specify the
<%= delivery_address_form.input :address1, input_html: {name:'delivery_address[address1]' } %>
for each of the fields. I know this feels kind of dirty, but eh... It's going to get the job done
I can't get my CheckIn record to save because the associated Tenancy isn't saving.
I have three models with associations:
class Property < ApplicationRecord
has_many :tenancies
end
class Tenancy < ApplicationRecord
belongs_to :property
has_many :check_ins
end
class CheckIn < ApplicationRecord
belongs_to :tenancy
accepts_nested_attributes_for :tenancy
end
I want the CheckIn new action to create both the CheckIn and the associated Tenancy:
def new
#check_in = CheckIn.new
#check_in.build_tenancy.property_id = params[:property_id]
end
I have to include the property_id part otherwise the Tenancy won't save.
The form in check_ins/new.html.erb:
<%= form_for #check_in, url: property_check_ins_path do |f| %>
<%= f.label :date_time %>
<%= f.datetime_select :date_time, {minute_step: 15} %>
<%= f.label :tenancy %>
<%= f.fields_for :tenancy do |i| %>
<%= i.date_select :start_date %>
<% end %>
<%= f.submit "Create Check In" %>
<% end %>
I've added tenancy attributes to the strong params in the CheckInsController:
def check_in_params
params.require(:check_in).permit(:tenancy_id, :date_time, tenancy_attributes: [:start_date])
end
It's worth noting that the check_ins routes are nested in properties:
resources :properties do
resources :check_ins, only: [:new, :create]
end
So the problem is that by the time I get to the create action in the CheckInsController, the tenancy that I built has disappeared. I'm not sure how and when each of the records should be being saved and the slight complexity of what I'm trying to achieve has made it quite difficult to find relevant help so any ideas?
I'm using Rails 5.
The problem was that the property attached to the tenancy was being forgotten. I removed the property attachment from the new action:
def new
#check_in = CheckIn.new
#check_in.build_tenancy
end
Added a hidden field for property_id to the form (as well as adding :property_id to the strong params):
<%= f.fields_for :tenancy do |i| %>
<%= i.date_select :start_date %>
<%= i.hidden_field :property_id, value: params[:property_id] %>
<% end %>
And saved the tenancy in the CheckIn create action, prior to saving the check in itself:
def create
#check_in = CheckIn.new(check_in_params)
#check_in.tenancy.save
if #check_in.save
redirect_to property_check_in_path(#check_in.tenancy.property.id, #check_in)
else
render :new
end
end
I'd certainly be interested if anyone could pick holes in this solution or offer a better one.
Using nested resources (check_ins depends from properties) you create a namespaces routes. form_for helper ( rails guides - form helpers ) when you build your form, need a Property reference also.
I try to explain me better with an example:
#checks_controller.rb
def new
#property = Property.new
#check_in = #property.build_check_ins
#check_in.build_tenancy
end
#check_ins/new.html.erb
<%= form_for [#property, #check_in], url: property_check_ins_path do |f| %>
<%= f.label :date_time %>
<%= f.datetime_select :date_time, {minute_step: 15} %>
<%= f.label :tenancy %>
<%= f.fields_for :tenancy do |i| %>
<%= i.date_select :start_date %>
<% end %>
<%= f.submit "Create Check In" %>
<% end %>
I haven't tried this code, but I hope this give you at least a way to follow to solve your problem.
In my rails 4 app I have comments for different models with polymorphic association. For the post model I display comments on the index page, but for product model I do it on the show page. I have problems with fine-tuning the rendering and caching on the post index page, since it's comments index in posts index. I provided my solutions both for the post version and product version.
I don't use touch:true in comment model since it doesn't make sense on the product show page. Thanks to this I don't use caching for the posts on the posts index page since the cache key would be too complex thanks to the comments.
My questions:
Is there a better way to render comments for the posts?
Is my caching strategy good enough or I should use an "outer" caching for product or post?
Post has_many :comments, as: :commentable
Product has_many :comments, as: :commentable
Comment belongs_to :commentable, polymorphic: true ###### NO touch: true
posts index
<%= render #posts %> #no caching here, key would be too complex
_post
<% cache ['post', post, post.user.profile] do %>
<%= post.user.full_name %> #delegated from profile
<%= post.body %>
<% end %>
<%= render partial: 'comments/form', locals: { commentable: post } %>
<%= render partial: 'comments/comment', collection: post.comments.ordered.includes(:user, :user_profile), as: :comment %>
products controller
def show
#product = Product.find(params[:id])
#comments = #product.comments.ordered.includes(:user, :user_profile)
end
products show
<% cache [#product, #product.user.profile] do %>
<%= product.user.full_name %> #delegated from profile
<%= product.name %>
<%= product.description %>
<% end %>
<% cache ['comments-index', #comments.map(&:id), #comments.map(&:updated_at).max,
#comments.map{ |comment| comment.user.profile.updated_at }.max] %>
<%= render #comments %>
<% end %>
_comment (same both for product and post)
<% cache ['comment', comment, comment.user.profile] do %>
<%= comment.user.full_name %> #delegated from profile
<%= comment.body %>
<% end %>
Edit: Essentially looking to pass something like this:
{
'tabled_id' : '1',
'recipes' : [{
{ 'recipe_id' : '3',
'quantity' : '2'
}
{ 'recipe_id' : '5',
'quantity' : '1'
}
}]
}
And I think I should do params.require(:order).permit(:table_id, {recipes:, [:id,:quantity]} ) on the controller side.
I'm learning Rails building an ordering system and I'm stuck trying to build a form for Orders that passes quantity. Where Orders is a nested resource for Restaurant.
My models look like this:
class Restaurant < ActiveRecord::Base
has_many :orders
has_many :recipes, dependent: :destroy
end
class Order < ActiveRecord::Base
belongs_to :restaurant
has_many :order_recipes, dependent: :destroy
has_many :recipes, through: :order_recipes
end
class Recipe < ActiveRecord::Base
belongs_to :restaurant
has_many :order_recipes
has_many :orders, through: :order_recipes
end
View:
<%= form_for([#restaurant, #order]) do |order_form| %>
<%= order_form.label :Table_Number %>
<%= order_form.number_field :table_id %>
<h3>Recipes: </h3>
<br>
<% #restaurant.recipes.each do |recipe| %>
<%= order_form.fields_for :recipe, recipe do |r| %>
<%= r.label recipe.name %>
<%= r.hidden_field :id %>
<%= r.number_field :quantity %>
<% end %>
<% end %>
<%= order_form.submit(#order.new_record? ? "Create Order" : "Edit Order", class: "btn btn-success") %>
<% end %>
This will yield a form that looks correct, but won't pass all parameters. Let's say I have 3 recipes. And I set their quantities to 2,3,4 respectively, and the table_id to 1. When I inspect the parameters, I see that only the last recipe with its quantity has been passed. params[:order] => {"table_id"=>"1", "recipe"=>{"id"=>"4", "quantity"=>"4"}} I need to be able to send all recipes with their assigned quantities. Also, I'm using the accepted answer in this question to be able to access the quantity column: Rails 4 Accessing Join Table Attributes
When you hand in fields_for :recipes multiple times, the fields_for method is not aware of you sending an array of things. Therefore it will name the parameters as if it was only one instance, so only the last instance will come through. You have to hand in the array of recipes to the fields_for, so it can name the parameters, so that rails knows it is an array of things when it gets picked up again (docs).
This is because form parameters in browsers do not support nesting by default. The actual parameters are flat key-value paramters. Rails has some naming conventions on how paramters can be named, so they will automatically be coerced to an array.
<%= form_for([#restaurant, #order]) do |order_form| %>
<%= order_form.label :Table_Number %>
<%= order_form.number_field :table_id %>
<h3>Recipes: </h3>
<br>
<%= order_form.fields_for :recipes, #restaurant.recipes do |r| %>
<%= r.label recipe.name %>
<%= r.hidden_field :id %>
<%= r.number_field :quantity %>
<% end %>
<%= order_form.submit(#order.new_record? ? "Create Order" : "Edit Order", class: "btn btn-success") %>
<% end %>
I have two models post and sign like this.
class Post < ActiveRecord::Base
has_many :signs
accepts_nested_attributes_for :signs
end
class Sign < ActiveRecord::Base
belongs_to :post
end
And I use nested_form gem, and this is form for post
<%= nested_form_for(#post) do |f| %>
...
<%= f.fields_for :signs do |sign| %>
<%= render 'sign_fields', :f => sign %>
<% end %>
...
<% end %>
And this is _sign_fields.html.erb.
<div class="sign">
<%= image_tag "#{"%02d" % #post.signs[f.options[:child_index].to_i].image_number}.jpg" %>
</div>
It works though, I think there is a better way than #post.signs[f.options[:child_index].to_i].image_number.
How can I access the attributes of child model?
You can use
f.object.image_number