Situation: Currently have a comment model that paginates under a micropost. I am trying to make the next button render comments that belong to the micropost.
Issue: I am unsure how to go about making a route/action inorder to bring these comments through.
Codes: I have some code that I will provide below, if anything isn't right please assist.
All help is much Appreciated.
References: Issue with Ajax Appending
User Controller
def show
#user = User.find(params[:id])
#micropost = Micropost.new
#comment = Comment.new
#comment = #micropost.comments.build(params[:comment])
#microposts = #user.microposts.order('created_at DESC').paginate(:per_page => 10, :page => params[:page])
end
Pagination JS
$("#CommentPagin").on('click', 'a', function(e){
// Get data from server - make sure url has params for per_page and page.
$.get($(this).attr('href'), function(data){
// refresh client with data
$("#cc").append(data);
});
});
Comment Section
<div id='comments'>
<% comments = micropost.comments.paginate(:per_page => 5, :page => params[:page]) %>
<div id="CommentPagin">
<span class="CommentArrowIcon"></span>
<%= will_paginate comments, :page_links => false , :class =>"pagination" %>
</div>
<%= render 'users/comments' %>
</div>
Comment Rendering Section
<div id="cc">
<% comments = micropost.comments.paginate(:per_page => 5, :page => params[:page]) %>
<%= render comments %>
</div>
UPDATE
User Model
class User < ActiveRecord::Base
has_many :microposts
has_many :comments
end
Micropost Model
class Micropost < ActiveRecord::Base
belongs_to :user
has_many :comments
accepts_nested_attributes_for :comments
end
Comment Model
class Comment < ActiveRecord::Base
attr_accessible :content, :user_id, :micropost_id
belongs_to :micropost, :counter_cache => true
belongs_to :user
belongs_to :school
end
Routes.rb
kit::Application.routes.draw do
resources :pages
resources :application
resources :schools
resources :microposts
resources :comments
resources :users
resources :sessions
resources :password_resets
resources :relationships, only: [:create, :destroy]
resources :users do
member do
get :following, :followers
end
end
resources :microposts do
member do
post :vote_up, :unvote
end
end
resources :microposts do
member do
post :upview
end
end
resources :microposts do
resources :comments
end
resources :schools do
collection do
get :mostrecent
get :mostdiscussed
get :highestrated
get :viewcount
end
end
match "/users/:id/personalstream" => "users#personalstream"
match "/schools/:id/mostrecent" => "schools#mostrecent"
match "/schools/:id/mostdiscussed" => "schools#mostdiscussed"
match "/schools/:id/viewcount" => "schools#viewcount"
match "/schools/:id/highestrated" => "schools#highestrated"
match "/schools/:id", :to => 'schools#show', :as => "school"
match "/microposts/:id/comments" => "microposts#comments"
match "/microposts/:id/upview" => "microposts#upview"
match "/microposts/:id/vote_up" => "microposts#vote_up"
match "/microposts/:id/unvote" => "microposts#unvote"
get "log_out" => "sessions#destroy", :as => "log_out"
get "sign_in" => "sessions#new", :as => "sign_in"
get "sign_up" => "users#new", :as => "sign_up"
get "home" => "users#home", :as => "home"
root to: "schools#index"
end
add a new action to the microposts controller:
app/controllers/microposts_controller.rb
def comments
#micropost = Micropost.find(params[:id])
#comments = #micropost.comments
# we dont need all the html head stuff
render :layout => false
end
write a view (app/views/microposts/comments.html.erb) where you display all the #comments as you want
and add a new member to you microposts resource get :comments
now you can try in the browser /microposts/(add a micropost id here)/comments
this should deliver you all the comments for the user and format them as you wish.
the last part is the simplest: when the user clicks on the div, make a request to this site via ajax and attach the answer to the div where you want to display the comments. the code could look like this:
$("#CommentPagin").click( function(){
$("#CommentPagin").load( "<%= micropost_comments_path( #user ) %>" );
});
hope you got an idea. report back if its not working
Related
Goal
I would like to set up an order form, where users can order one product. After having filled out the product_category, a user can select
the product belonging to the product_category
the quantity of each option belonging to the product_category.
Current state
The way I currently set up my code, causes issues when the form has to be built up again:
when a validation issue is triggered, (1) product_category, (2) product and (3) options are empty, but also the reservation still saves, thereby creating situation where a reservation is saved twice.
=> I know it's because I first save the reservation in the controller and then the options, but I don't know how to solve this (e.g. it is saved when the validation is triggered and when the user has filled in the form properly afterwards).
Code
models
class Order < ApplicationRecord
belongs_to :store
belongs_to :product
has_many :order_options, dependent: :destroy
has_many :options, through: :order_options
accepts_nested_attributes_for :order_options
end
class OrderOption < ApplicationRecord
belongs_to :option
belongs_to :order
accepts_nested_attributes_for :option
end
class Option < ApplicationRecord
belongs_to :product_category
has_many :order_options, dependent: :destroy
has_many :orders, through: :order_options
end
class ProductCategory < ApplicationRecord
belongs_to :store
has_many :products, dependent: :destroy
accepts_nested_attributes_for :products, allow_destroy: true
has_many :options, dependent: :destroy
accepts_nested_attributes_for :options, allow_destroy: true
end
order_controller
class OrdersController < ApplicationController
# skip_before_action :authenticate_user!
def new
#user = current_user
#store = Store.find(params[:store_id])
#order = Order.new
#order.build_order_contact
#product_category_list = #store.product_categories
#all_options = #store.options
#products = []
#options = []
if params[:product_category].present?
#products = ProductCategory.find(params[:product_category]).products
#options = ProductCategory.find(params[:product_category]).options
else
end
if request.xhr?
respond_to do |format|
format.json {
render json: {products: #products, options: #options}
}
format.js
end
end
authorize #order
end
def create
#user = current_user
#store = Store.find(params[:store_id])
#order = Order.new(order_params)
#order.store = #store
authorize #order
if #order.save
params[:order_options_attributes].each do |order_option|
if #option = Option.find_by(id: order_option[:option_id])
#option_quantity = order_option[:option_quantity]
#order.options << #option
order_option = #order.order_options.where(option: #option)
order_option.update(option_quantity: #option_quantity)
end
end
redirect_to store_path(#store)
else
#product_category_list = #store.product_categories
render 'new'
end
end
views/orders/new.js
$("#product_options").html("<%= escape_javascript(render partial: 'option_fields', collection: #options) %>");
$("#dynamic-products").empty();
<% #products.each do |pro| %>
$("#dynamic-products").append('<option value="<%= pro.id %>"><%= pro.name %></option>')
<% end %>
views/orders/new.html.erb
<%= simple_form_for [#store, #order] do |f|%>
<%= f.simple_fields_for :products do |product| %>
<%= product.input :product_category, collection: #product_category_list, prompt: "Select type of product", label:false,
input_html:{
id: "product_category"
}%>
<%= f.association :product, collection: #products, input_html:{
value: #products.object_id,
id: "dynamic-products"
} %>
<div class="product_category-options" id="product_options">
</div>
<% end %>
<% end %>
<script >
// dynamic products and options for change category
$(document).on("change", "#product_category", function(){
var product_category = $(this).val();
$.ajax({
url: "/stores/<%= #store.id %>/orders/new",
method: "GET",
// dataType: "json",
dataType: "script",
data: {product_category: product_category},
error: function (xhr, status, error) {
console.error('AJAX Error: ' + status + error);
},
success: function (response) {
}
});
});
// dynamic products and option for releading form (e.g. new)
$(document).ready(function(){
var product_category = $("#product_category").val();
$.ajax({
url: "/stores/<%= #store.id %>/orders/new",
method: "GET",
dataType: "json",
data: {product_category: product_category},
error: function (xhr, status, error) {
console.error('AJAX Error: ' + status + error);
},
success: function (response) {
}
});
});
</script>
views/orders/_option_fields.html.erb
<div class="product_option order-form-quantity-row border-bottom col col-sm-10">
<div class="product_option_name order-form-quantity-name">
<strong> <%= option_fields.name %></strong>
</div>
<div class="order-form-input">
<%= hidden_field_tag("order_options_attributes[]option_id", option_fields.id ) %>
<%= select_tag("order_options_attributes[]option_quantity", options_for_select((0..9)), {class:'form-control col col-sm-12'} ) %>
</div>
</div>
This is very over-complicated and missguided. All you really need is something like:
<%= simple_form_for([#store, #order]) do |f| %>
<% f.simple_fields_for(:order_options) do |ff| %>
<%= ff.association :option %>
<%= ff.input :option_quantity %>
<% end %>
# ...
<% end %>
class OrdersController
# Use callbacks to DRY your code
before_action :set_store, only: [:new, :create, :index]
def new
#order = #store.order.new
# seed the record to create the inputs
5.times { #order.order_options.build }
authorize #order
end
def create
#order = #store.orders.new(order_params) do |order|
order.user = current_user
end
if #order.save
redirect_to #order.store
else
render :new
end
end
def set_store
#store = Store.find(params[:store_id])
end
def order_params
params.require(:order)
.permit(:foo, :bar,
order_options_attributes: [:option_id, :option_quantity]
)
end
end
You don't need to accept nested attributes for the option unless you are letting users create them on the fly which does not seem like a good idea since you already have 100 levels too much of complexity in a single component.
You also don't need to ever do params[:order_options_attributes].each do |order_option| and iterate through the nested attributes. Really DON'T ever do this as it defeats the whole purpose of using nested attributes in the first place.
When you use the order_options_attributes= setter created by accepts_nested_attributes Rails will handle assigning the attributes to new instances of order_options and will do it before the record is saved. When you call save it will persist everything at once in a transaction which avoids most of the issues you have.
You can use validates_associated to trigger the validations on the order_options before saving.
If you then want to to use AJAX to spruce it up feel free. But you should really start by just setting up something simple and synchronous so that you understand how nested attributes work.
In general this code seems to be suffering from going to fast. Start by setting up just the basics (ie just creating an order for a product). Test it - refactor - and then add more features. If you try to do everything at once you usually end up with a dumpster fire.
I can't figure out why my 'Uphold(Like)' button doesn't work.
My app is a kind of Q&A web site, named 'KnowledgeSprout'.
And I appended 'like' feature, named 'uphold', like StackOverFlow's useful button.
The code are below.
It look like counting up is working, but the image can't turn to red dynamically.
UpholdController.rb
class UpholdsController < ApplicationController
def create
#uphold = Uphold.create(user_id: current_user.id, knowledge_sprout_id: param
s[:knowledge_sprout_id], answer_id: params[:answer_id], review_id: params[:revie
w_id], product_review_id: params[:product_review_id])
#knowledge_sprouts = KnowledgeSprout.find(params[:knowledge_sprout_id])
end
end
And KnowledgeSproutController.rb
class KnowledgeSproutsController < ApplicationController
def show
#knowledge_sprout = KnowledgeSprout.find_by(id: params[:id])
#q = Restaurant.ransack(params[:q])
if params[:q].present?
#restaurants = #q.result(distinct: true).page(params[:page])
end
#answers = Answer.where(ks_id: params[:id])
#answer = Answer.new
ahoy.track "ks_show", id: params[:id]
end
end
Here's knowledege_sprout.html.slim
.upholds
= render partial: "uphold", locals: {knowledge_sprout: #knowledge_sprout}
_uphold.html.slim
- if user_signed_in?
- if #knowledge_sprout.uphold_user(current_user.id)
= image_tag("icon_red_heart.png")
span
= #knowledge_sprout.upholds_count
- else
= button_to knowledge_sprout_upholds_path(#knowledge_sprout.id), id: "uphold-button", remote: true do
= image_tag("icon_heart.png")
span
= #knowledge_sprout.upholds_count
- else
- if #knowledge_sprout.uphold_user(current_user.id)
= image_tag("icon_red_heart.png")
span
= #knowledge_sprout.upholds_count
- else
= image_tag("icon_heart.png")
span
= #knowledge_sprout.upholds_count
JS
$("#uphold-button").on("ajax:success", (e, data, status, xhr) ->
$("#uphold-button").html("= j(render partial: 'uphold', locals: { knowledge_sprout: #knowledge_sprout})")
).on "ajax:error", (e, xhr, status, error) ->
$("#uphold-button").append "<p>ERROR</p>"
Here's Models.
class KnowledgeSprout < ActiveRecord::Base
belongs_to :user
has_many :upholds, dependent: :destroy
def uphold_user(user_id)
upholds.find_by(user_id: user_id)
end
attr_accessor :file
end
class Uphold < ActiveRecord::Base
belongs_to :knowledge_sprout, counter_cache: :upholds_count
belongs_to :answers, counter_cache: :upholds_count
belongs_to :reviews, counter_cache: :upholds_count
belongs_to :product_reviews, counter_cache: :upholds_count
belongs_to :user
end
Also here's rake routes results.
knowledge_sprout_upholds POST (/:locale)/knowledge_sprouts/:knowledge_sprout_id/upholds(.:format) upholds#create {:locale=>/en|ja/}
knowledge_sprout_uphold DELETE (/:locale)/knowledge_sprouts/:knowledge_sprout_id/upholds/:id(.:format) upholds#destroy {:locale=>/en|ja/}
GET (/:locale)/knowledge_sprouts(.:format) knowledge_sprouts#index {:locale=>/en|ja/}
POST (/:locale)/knowledge_sprouts(.:format) knowledge_sprouts#create {:locale=>/en|ja/}
GET (/:locale)/knowledge_sprouts/new(.:format) knowledge_sprouts#new {:locale=>/en|ja/}
GET (/:locale)/knowledge_sprouts/:id/edit(.:format) knowledge_sprouts#edit {:locale=>/en|ja/}
GET (/:locale)/knowledge_sprouts/:id(.:format) knowledge_sprouts#show {:locale=>/en|ja/}
PATCH (/:locale)/knowledge_sprouts/:id(.:format) knowledge_sprouts#update {:locale=>/en|ja/}
PUT (/:locale)/knowledge_sprouts/:id(.:format) knowledge_sprouts#update {:locale=>/en|ja/}
DELETE (/:locale)/knowledge_sprouts/:id(.:format) knowledge_sprouts#destroy {:locale=>/en|ja/}
Any idea? Help me pls...
Thanks in advance.
I've spent almost my entire day trying to figure out how to solve this dilemma but unfortunately a majority of the solutions I've found are related to an outdated Rails version that still allowed "render" to be used in assets.
Here's what I'm trying to do:
My view finds each "Trip" entry and displays each as a thumbnail on the page. When the user clicks on each thumbnail, I would like the additional details (and also associations, each trip has a has_many: weeks) to be rendered in the Div below those thumbnails (replacing the previous content).
I can't seem to get Ajax to work and after several hours of attempting finally learned that "render" can't be used in assets. I would sincerely appreciate any help and along with potential solutions if someone could provide a possible reference guide for Ajax with Rails 4 that would be fantastic because I can't seem to find one.
Here's my code:
View - index.html.erb
<div>
<ul class="thumbnails col-md-offset-2">
<% Trip.find_each do |trip| %>
<li class="col-md-3" style="list-style-type:none;">
<%= link_to image_tag("http://placehold.it/350x350", :border => 0), :class => 'thumbnail', :url=>'/trips/selected_trip/params', :remote => true %>
<h3><%= trip.name %></h3>
</li>
<% end %>
</ul>
<div id="selected_trip">
</div>
</div>
Controller - trips.controller.rb
class TripsController < ApplicationController
before_filter :authenticate_user!
after_action :verify_authorized
def index
#trips = Trip.all
authorize Trip
end
def new
#trip = Trip.new
authorize Trip
end
def create
#trip = Trip.new(trip_params)
authorize Trip
if #trip.save
flash[:notice] = "New trip has been created."
redirect_to #trip
else
#Fill me in
end
end
def edit
#trip = Trip.find(params[:id])
authorize Trip
end
def update
#trip = Trip.find(params[:id])
authorize Trip
#trip.update(trip_params)
flash[:notice] = "Trip has been updated."
redirect_to #trip
end
def show
#trip = Trip.find(params[:id])
authorize Trip
end
def destroy
#trip = Trip.find(params[:id])
authorize Trip
#trip.destroy
flash[:notice] = "Trip has been deleted."
redirect_to trips_path
end
def selected_trip
#trip = Trip.find(params[:id])
#trip.name
respond_to do |format|
format.js
end
end
end
private
def trip_params
params.require(:trip).permit(:name, :description, :status)
end
end
Javascript - trips.js.erb (I know this method doesn't work anymore with render not being available in assets)
$('#selected_trip').html("<%= escape_javascript(render :partial => 'selected_trip', :content_type => 'text/html'%>")
Partial - _selected_trip.html.erb
<p>Success!</p> <!-- Just for testing, will replace with actual content -->
Thanks,
Nate
Edit 11:10PM (it works)-
I've changed my controller to:
def selected_trip
#trip = Trip.find(params[:id])
authorize Trip
render :partial => 'selected_trip', :content_type => 'text/html'
end
and my view to:
<div>
<ul class="thumbnails col-md-offset-2">
<% Trip.find_each do |trip| %>
<li class="col-md-3" style="list-style-type:none;" data-tripID="<%= trip.id %>">
<%= link_to image_tag("http://placehold.it/350x350", :border => 0), selected_trip_trip_path(trip), :class => 'thumbnail', :remote => true %>
<h3><%= trip.name %></h3>
</li>
<% end %>
</ul>
<div id="selected_trip">
</div>
</div>
</div>
<script>
$('a.thumbnail').on('ajax:success', function(evt, data) {
var target = $('#selected_trip');
$(target).html(data);
});
</script>
If you want to avoid rendering from the assets, you can try doing it this way.
I am assuming you know where you need to put the listener in order to catch the AJAX call, and also you can figure out, where you want to place the results when the AJAX comes back with a success status. Then, you want to do something like this:
$('whatever_container_they_can_click').on('ajax:success', function(evt, data) {
var target = $('#selected_trip'); // Find where is the destination
$(target).html(data);
});
Change your controller action to:
def selected_trip
#trip = Trip.find(params[:id])
render :partial => 'selected_trip', :content_type => 'text/html'
end
I currently have a comment model that posts under a micropost and both are displayed on the same page. The issue is that both are displayed on the same page and both are paginated and I am trying to go for the facebook approach to microposting. Here is the issue below:
The links for both pagination turns into this href="/users/2?page=2" rather than href="/users/2/micropost?page=2" or href="/users/2/comment?page=2". I am unsure how to go about solving this problem. Here are some of my code. All suggestions are much appreciated!
Micropost Render HTML
<table class="microposts">
<% if microposts.any? %>
<%= render microposts %>
<%= will_paginate microposts, :page_links => false %>
<% else %>
<div class="EmptyContainer"><span class='Empty'>Add a thread!</span></div>
<% end %>
</table>
Comment Section HTML
<div id='CommentContainer-<%= micropost.id%>' class='CommentContainer Condensed2'>
<div class='Comment'>
<%= render :partial => "comments/form", :locals => { :micropost => micropost } %>
</div>
<div id='comments'>
<% comments = micropost.comments.paginate(:per_page => 5, :page => params[:page]) %>
<%= render comments %>
<%= will_paginate comments, :class =>"pagination" %>
</div>
</div>
User Controller for the Show Page
def show
#user = User.find(params[:id])
#comment = Comment.find(params[:id])
#micropost = Micropost.new
#comment = Comment.new
#comment = #micropost.comments.build(params[:comment])
#comments = #micropost.comments.paginate(:page => params[:page], :per_page => 5)
#microposts = #user.microposts.order('created_at DESC').paginate(:per_page => 10, :page => params[:page])
respond_to do |format|
format.html
format.js
end
end
Problem lies within will_paginate way of creating urls for each page (it doesn't have anything to do with jQuery).
By design, will_paginate try its best to guess what's the base url for the page user is on (internally it's using controller/action to do that). That base url is then combined with any extra params passed to will_paginate helper using :params and succesive page numbers.
For now (will_paginate 3.0.3), in order to overwrite this default behavior, you need to write your custom LinkRenderer class. Below there's example of such class - it makes use of new, extra option :base_link_url that can be passed to will_paginate view helper. Passed string is then used as a base when creating pagination links. If :base_link_url option is not passed, it will fallback to default behavior.
Put following class somewhere rails can find it on load (/lib for example, provided you've added /lib to your autoload paths in application.rb):
# custom_link_renderer.rb
class CustomLinkRenderer < WillPaginate::ActionView::LinkRenderer
def prepare(collection, options, template)
#base_link_url = options.delete :base_link_url
#base_link_url_has_qs = #base_link_url.index('?') != nil if #base_link_url
super
end
protected
def url(page)
if #base_link_url.blank?
super
else
#base_url_params ||= begin
merge_optional_params(default_url_params)
end
url_params = #base_url_params.dup
add_current_page_param(url_params, page)
query_s = []
url_params.each_pair {|key,val| query_s.push("#{key}=#{val}")}
if query_s.size > 0
#base_link_url+(#base_link_url_has_qs ? '&' : '?')+query_s.join('&')
else
#base_link_url
end
end
end
end
Usage:
# in your view
will_paginate collection, :renderer => CustomLinkRenderer, :base_link_url => '/anything/you/want'
And now back to your case. By this time you probably see the solution - you can have two will_paginate widgets on one page with different base urls by passing different :base_link_url options for those two.
I was trying out Rails again, this time the 3 version, but I got stuck while writing tests for an action that I only call remotely.
A concrete example:
Controller
class PeopleController < ApplicationController
def index
#person = Person.new
end
def create
#person = Person.new(params[:person])
#person.save
end
end
View (index.html.erb)
<div id="subscription">
<%= form_for(#person, :url => { :action => "create" }, :remote => true) do |f| %>
<%= f.text_field :email %>
<%= f.submit "Subscribe" %>
<% end %>
</div>
View (create.js.erb)
<% if #person.errors.full_messages.empty? %>
$("#subscription").prepend('<p class="notice confirmation">Thanks for your subscription =)</p>');
<% else %>
$("#subscription").prepend('<p class="notice error"><%= #person.errors.full_messages.last %></p>');
<% end %>
How can I test that remote form submission? I would just like to find out if the notice messages are being presented correctly. But if I try to do just
test "create adds a new person" do
assert_difference 'Person.count' do
post :create, :people => {:email => 'test#test.com'}
end
assert_response :success
end
It will say that the "create" action is missing a template.
How do you guys usually test remote calls?
Could you just use the 'xhr' function instead of the 'post' function? An example can be found at http://weblogs.java.net/blog/2008/01/04/testing-rails-applications, if you search for 'xhr'. But even then, I'm curious, even with a remote call, don't you need to return SOMETHING? Even just an OK header?