I know dalli (caching) is pretty powerful plugin to enhance performance for static pages.
But what about dynamic pages with pagination, which are updated quite often?
What is the correct way to set up dalli?
One problem I've encountered for example: dalli recognizes different params[:page] as the same page when using pagination:(
How would you guys design the system when using dalli for both
the page that gets updated so often
the page that won't be updated so often
For example. I have 4 models such as User, Community, Topic, and Comment(defined Polymorphic to both Community, and Topic. They can be created or shown in #show pages)
It's like
Community > (has_many) > Topics > (has_many) > Comments
Community > (has_many) > Comments
All of them belongs to user(creator)
My current codes are just like this(This is quite bit long. Sorry about that)
I'm facing the pagination problem when using caching...
-------------------------------------Settings----------------------------------------
config/environments/development.rb
config.consider_all_requests_local = true
config.action_controller.perform_caching = true
config.cache_store = :dalli_store
routes.rb
resources :communities do
resources :topics do
resources :comments do
end
end
-------------------------------------Community----------------------------------------
controllers/communities_controller.rb#index&show
caches_page :show
caches_action :edit, :new
def index
....
if params[:sort] == 'new'
#communities = Community.scoped.page(params[:page]).order("created_at DESC")
elsif params[:sort] = 'popular'
#communities = Community.scoped.page(params[:page]).order("follow_count DESC")
elsif params[:sort] = 'reputation'
#communities = Community.scoped.page(params[:page]).order("reputation_count DESC")
else
#communities = Community.scoped.page(params[:page]).order("created_at DESC")
end
....
end
def show
#community = Community.find(params[:community_id])
#comments #community.comments.page(params[:page]
#topics = #community.topics.limit(10)
end
views/communities/index.html.erb
note: However, the content will be the same even if I move to next params[:page]:( It seems caching recognize different page as the same contents...
#here, the url will could be something like this example.com/communities?utf8=✓&location=14&genre=3&search=strawberry
But this is going to create gigantic petterns of the caches:(
So I want to make it work only when params[:sort] was not empty. Because, no other parameters come with when params[:sort] is not empty.
It could be like, example.com/communities?sort=new, example.com/communities?sort=popular, example.com/communities?sort=reputation
...
<% if params[:sort] %>
<% #key = "community_index_" + params[:sort] + params[:page] %>
<% cache(:controller => "communities", :action => "index", :action_suffix => #key) do %>
<%= page_entries_info(#communities, :entry_name => 'community').html_safe %>
<%= paginate #communities, :window => 4 %>
<% #communities.each do |community| %>
<%= render 'communities/community', :community => community %>
<% end %>
<% end %>
<% end %>
...
views/communities/show.html.erb
...
<% #key = params[:community_name] + "_community_show_information" %>
<% cache(:controller => "communities", :action => "show", :action_suffix => #key) do %>
<h2>Information</h2>
<div class="CommunityInformation">
<%= render 'communities/information'%>
</div>
<% end %>
<% #key = params[:community_name] + "_community_show_wall_" + params[:page] + %>
<% cache(:controller => "communities", :action => "show", :action_suffix => #key) do %>
<%= paginate #comments, :window => 4 %>
<h2>Topic</h2>
<div class="WallInformation">
<% #comments.eager.recent.each do |comment| %>
<%= render 'communities/comment', :comment => comment %>
<% end %>
</div>
<% end %>
<% #key = params[:community_name] + "_community_show_topics" %>
<% cache(:controller => "communities", :action => "show", :action_suffix => #key) do %>
<h2>Topic</h2>
<div class="TopicInformation">
<% #topics.eager.recent.each do |topic| %>
<%= render 'communities/topic', :topic => topic %>
<% end %>
</div>
<% end %>
...
models/community_sweeper.rb
class CommunitySweeper < ActionController::Caching::Sweeper
observe Community
def after_save(record)
expire_fragment(url_for(:action => 'index', :only_path => true) + '?????(all the caches related to the community#index)')
expire_fragment(url_for(:action => 'show', :only_path => true) + '?????(the cache related to the particular community#show)')
end
end
end
-------------------------------------Topic----------------------------------------
controllers/topics_controller.rb#index&show
caches_page :show
caches_action :edit, :new
def index
....
#community = Community.find(params[:community_id])
#topics = #community.topics.page(params[:page]
....
end
def show
#topic = Topic.find(params[:id])
#comments = #topic.comments.page(params[:page]
end
views/topics/index.html.erb
...
<% #key = params[:community_id] + "_topic_index_" + params[:page] %>
<% cache(:controller => "topics", :action => "index", :action_suffix => #key) do %>
<%= page_entries_info(#communities, :entry_name => 'community').html_safe %>
<%= paginate #communities, :window => 4 %>
<% #communities.each do |community| %>
<%= render 'communities/community', :community => community %>
<% end %>
<% end %>
<% end %>
...
views/topics/show.html.erb
...
<% #key = params[:community_name] + "_topic_show_" + params[:id] + "_comments" + params[:page] %>
<% cache(:controller => "topics", :action => "show", :action_suffix => #key) do %>
<%= paginate #comments, :window => 4 %>
<%= page_entries_info(#comments, :entry_name => 'comment').html_safe %>
<h2>Comment</h2>
<div class="CommentInformation">
<% #comments.each do |comment| %>
<%= render 'topics/comment', :comment => comment %>
<% end %>
</div>
<% end %>
...
models/topic_sweeper.rb
class TopicSweeper < ActionController::Caching::Sweeper
observe Topic
def after_save(record)
expire_fragment(url_for(:action => 'index', :only_path => true) + '?????(all the caches related to the topic#index)')
expire_fragment(url_for(:action => 'show', :only_path => true) + '?????(the cache related to the particular topic#show)')
end
end
-------------------------------------Comment----------------------------------------
models/comment_sweeper.rb
class CommentSweeper < ActionController::Caching::Sweeper
observe Comment
def after_save(record)
expire_fragment(url_for(:action => 'index', :only_path => true) + '?????(all the caches related to the topic#index)')
expire_fragment(url_for(:action => 'show', :only_path => true) + '?????(the cache related to the particular topic#show)')
end
end
I am following on on from my answer to
How should I set up dalli for a dynamic page with lots of content updates?
Please note that I am not an expert but I did try your approach and abandoned it as it just kept getting more and more complex as my application grew and I went for the cache key fragment based approach mentioned in the last question. There are a few things to bear in mind.
The cache sweeper approach requires you to make sure that the sweeper is up to date as you make changes to your application which means extra maintenance and testing work.
In a dynamic application you probably wont be able to easily cache whole pages especially if the views show information from many models.
You wont be able to deal with the paging issue unless the page parameter becomes part of your cache keys.
When your cache gets full, memcached will simply remove the least used / oldest cache items when putting in a new cache fragment.
So there is no issue with you just pushing in a new cache fragments when your models change and letting memcached clear out the old out of date cache items.
Therefore fragment caching in your views will probably be the easiest solution as it will deal with paging and you wont have to deal with manual cache invalidation
So looking at one of your views, what I might do is
<% if params[:sort] %>
<%= page_entries_info(#communities, :entry_name => 'community').html_safe %>
<%= paginate #communities, :window => 4 %>
<% #communities.each do |community| %>
<% cache(community, :suffix => "community_index") do
<%= render 'communities/community', :community => community %>
<% end %>
<% end %>
<% end %>
What I have done is cache the rendering of each community record and paging becomes irrelavent
Related
In my customer controller the update method code is like bellow:
def update
#customer= Customer.find(params[:id])
if #customer.update_attributes(customer_params)
redirect_to customers_path
else
render 'edit'
end
end
In my view in customers index page I am planning to add a "link_to" link, if it is clicked, then that particular customers field "doc_delete" should be updated with value "TRUE".
<td><%= link_to "[Update", *************what is here ?******** method: :put %></td>
You can pass hidden params through button_to:
<%= button_to "Update", user, method: :put, params: { doc_delete: true } %>
This will create a micro-form, much like what Marwen alluded to. Whilst quite inefficient, it will be the best way to send data to your update action.
--
Another, more efficient, way would be to define a custom route/action:
#config/routes.rb
resources :customers do
patch :doc_delete, on: :member #-> url.com/users/:id/doc_delete
end
#app/controllers/customers_controller.rb
class CustomersController < ApplicationController
def doc_delete
#customer = Customer.find params[:id]
redirect_to customers_path if #customer.update doc_delete: true
end
end
#app/views/customers/index.html.erb
<% #customers.each do |customer| %>
<%= link_to "Update", customer_doc_delete_path(customer) %>
<% end %>
You will need a form to do that for you
<% unless customer.doc_delete? %>
<%= form_for customer do |f| %>
<%= f.hidden_field_tag :doc_delete, true %>
<%= f.submit %>
<% end %>
<% end %>
Where to insert this form?
Well if you are rendering you costumers using:
<%=render #costumers %>
then you will add the form in the /customers/_customer.html.erb
If you are looping them manually:
<% #customers.each do |customer| %>
<%=customer.full_name %>
## Here you can add the form
<% end %>
An another way, you can use Ajax.
#app/views/customers/index.html.erb
<% #customers.each do |customer| %>
<% if !customer.doc_delete == true %>
<%= link_to "Update", customer_doc_delete_path(customer), remote: true %>
<% else %>
<%= Updated %>
<% end %>
<% end %>
#config/routes.rb
resources :customers do
patch :doc_delete, on: :member #-> url.com/customers/:id/doc_delete
end
#app/controllers/customers_controller.rb
class CustomersController < ApplicationController
def doc_delete
#customer = Customer.find params[:id]
if #customer.update doc_delete: true
respond_to do | format |
format.js {render :nothing => true}
end
end
end
end
In my index.html
<td>
<%= hidden_field_tag 'delete_present', :value => "present" %>
<%=link_to "[update]", customer_path(customer, :doc_delete => true), :method => :put, :confirm => "Are you sure?" %>
</td>
In my customer controller
def update
if params[:doc_delete].present?
#customer= Customer.find(params[:id])
#customer.doc_delete=true
#customer.save
redirect_to customers_path
else
#customer= Customer.find(params[:id])
if #customer.update_attributes(customer_params)
redirect_to customers_path
else
render 'edit'
end
end
end
I want to build a multiple choice quiz in Ruby on Rails, but although I've got it sort of working I still want to improve my navigation.
As it currently stands I have two buttons: One for submitting the quiz_guess (which is an attribute of Answer) and another for selecting the next :question. I would like to combine these two buttons into a single one so I don't have to first update the attributes and then select the next question.
How do you go about doing that?
My questions controller:
def quiz_guess
#survey = Survey.first
Answer.update(params[:answers].keys, params[:answers].values)
if #survey.save
flash[:notice] = "Guess saved successfully."
redirect_to(:action => 'show_quiz', :id => #survey.next, :survey_id => #survey.id)
else
render 'show'
end
end
def show_quiz
#answer = Answer.find(params[:id]) #answer
#answers = Answer.where(:survey_id => #survey.id)
#question = Question.find(params[:id])
#questions = Question.where(:survey_id => #survey.id)
#surveys = Survey.all
#survey = Survey.find(params[:id])
end
My show_quiz view:
<%= form_tag quiz_guess_questions_path, :method => :put do %>
<% for question in #survey.questions do %>
<li><%= question.content %></li>
<% for answer in question.answers do %>
<li>
<%= fields_for "answers[]", answer do |f| %>
<%= answer.content %>
<%= f.check_box :guess, :checked => false %>
<% end %>
</li>
<% end %>
<% end %>
<%= submit_tag "Guess" %>
<% end %>
<%= link_to t('.back', :default => t("helpers.links.back")), surveys_path, :survey_id => #survey.id, :class => 'btn btn-default' %>
<%= link_to t('Previous question', :default => t("helpers.links.previous")), {:action => 'show', :id => #survey.previous, :survey_id => #survey.id}, :class => 'btn btn-default' %>
<%= link_to t('Next question', :default => t("helpers.links.next")), {:action => 'show', :id => #survey.next, :survey_id => #survey.id}, :class => 'btn btn-default' %>
My model:
def next
Survey.limit(1).order("id ASC").where("id > ?", id).first
end
def previous
Survey.limit(1).order("id DESC").where("id < ?", id).last
end
I'd like to improve your next(and previous) functions. First, you don't need to have limit(1) while you have .first or .last at the end. .first and .last each guarantees only 1 record to be returned.
Problem is when you call #survey.next on your last record, it will point to nothing. Here you have two options. First, you can use a check, and if next returns nil, disable the next button.
Second, make the order cyclic. Clicking next on the last survey takes to the first survey, and vice verse. Here's how:
def next
Survey.order("id ASC").where("id > ?", id).first || Survey.first
end
def previous
Survey.order("id DESC").where("id < ?", id).last || Survey.last
end
What I am trying to do is add a simple search to a 'show' view in my rails app. I've watched the rails cast about simple search and it's given me some good direction but I can't seem to get it to work with my situation. I just have a list of FAQs and want to have a search filter for the 'question(s)'.
Here's my views/guests/show.html.erb
<%= form_tag faq_search_guests_path, method: :get, remote: true, :id => "faq_search_form" do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag "Search", :name => nil %>
</p>
<% end %>
<div id="faq-search-test"></div>
<% #faqs.each do |f| %>
<h4>Question</h4>
<%= f.question %>
<h4>Answer</h4>
<%= f.answer %>
<% end %>
Here's the controller/guests_controller.rb method that originally sets #faqs
def show
#guest = Guest.find(params[:id])
#hotel = Hotel.find(params[:hotel_id])
#faqs = Faq.where(hotel_id: #hotel.id)
#template = Template.where( hotel: #hotel.id )
#visit = Visit.where("hotel_id = ? AND guest_id = ?", #hotel.id, #guest.id).last
#visit = Visit.find(params[:visit_id]) if params[:visit_id] && (#visit.hotel_id == #hotel.id)
unless current_user.hotels.include?(#hotel)
render :file => "public/401.html", :status => :unauthorized
end
end
Here's my controllers/guests_controller.rb custom method my form is calling
def faq_search
#faq_result = Faq.search(params[:search])
render :nothing => true
end
Here's my routes.rb for that
resources :guests do
get 'faq_search', :on => :collection
end
resources :faqs
Here's my models/faq.rb
def self.search(search)
if search
where('question LIKE ?', '%#{search}%')
else
scoped
end
end
So from here, I am having trouble displaying #faq_result in show.html.erb. I guess I have no idea how to access an instance variable from a custom controller method from a view.
Any help would be much appreiciated :)!
I'm trying to send pdf to user. For example if user is in /products?category_id=3 he has to get report of products whose category id is 3 not all reports. I couldn't find a way to pass parameter. How can i do this ?
products_controller
def index
#products Product.where("category_id = ?", params[:category_id)
end
def reporter
kit = PDFKit.new(render_to_string(:action => "products/index", :layout => "report"))
send_data(kit.to_pdf, :type => :pdf)
end
My view is like this
<% #products.each do |product| %>
<%= product.name %>
<% end %>
<%= link_to "Download(PDF)", reporter_path, :method => :post %>
I am new to rails so go easy. I have created a blog. I have successfully implemented comments and attached them to each post. Now...I would like to display, in the sidebar, a list of the most recent comments from across all posts. I think there are two things involved here, an update to the comment_controller.rb, and then the call from the actual page. Here is the comments controller code.
class CommentsController < ApplicationController
def create
#post = Post.find(params[:post_id])
#comment = #post.comments.create!(params[:comment])
respond_to do |format|
format.html { redirect_to #post}
format.js
end
end
end
If you want to display all the comments from any post, in most recent order, you could do:
#comments = Comment.find(:all, :order => 'created_at DESC', :limit => 10)
And in the view you can do:
<% #comments.each do |comment| -%>
<p>
<%= comment.text %> on the post <%= comment.post.title %>
</p>
<% end -%>
I'm posting a separate answer since code apparently doesn't format well at all in comments.
I'm guessing the problem you're having with the previous answer is that you're putting
#comments = Comment.find(:all, :order => 'created_at DESC', :limit => 10)
in one of your controller methods. However, you want #comments to be available to a layout file, so you'd have to put that on every controller method for every controller in order for that to work. Although putting logic in views is frowned upon, I think it would be acceptable to do the following in your layout file:
<% Comment.find(:all, :order => 'created_at DESC', :limit => 10).each do |comment| -%>
<p>
<%= comment.text %> on the post <%= comment.post.title %>
</p>
<% end -%>
To get some of the logic out of the view though we can move it into the Comment model
class Comment < ActiveRecord::Base
named_scope :recent, :order => ["created_at DESC"], :limit => 10
Now you can do this in your view:
<% Comment.recent.each do |comment| -%>
<p>
<%= comment.text %> on the post <%= comment.post.title %>
</p>
<% end -%>
This makes for a nice fat model and skinny controller
I tend to use a helper for this:
# in app/helpers/application_helper.rb:
def sidebar_comments(force_refresh = false)
#sidebar_comments = nil if force_refresh
#sidebar_comments ||= Comment.find(:all, :order => 'created_at DESC', :limit => 10)
# or ||= Comment.recent.limited(10) if you are using nifty named scopes
end
# in app/views/layouts/application.html.erb:
<div id='sidebar'>
<ul id='recent_comments'>
<% sidebar_comments.each do |c| %>
<li class='comment'>
<blockquote cite="<%= comment_path(c) -%>"><%= c.text -%></blockquote>
</li>
<% end %>
</ul>
</div>