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.
Related
I have a ruby form that includes the following code
<%= form.label(:emitter_type, class: "form-label") %>
<%= form.select(:emitter_type, BoilerPlant.emitter_type , include_blank: true) %>
<% if boiler_plant.emitter_type != "Other" %>
<div id="emitter_other", style="display:none;">
<% else %>
<div id="emitter_other">
<% end %>
<%= form.label(:emitter_type_other, class: "form-label") %>
<%= form.text_field(:emitter_type_other , autocomplete: "off", autocapitalize: "on", class: "form-input") %>
</div>
BoilerPlant.emitter_type Is the following call to a function in my model that produces the array of options I use in my dropdown:
def self.emitter_type
["Baseboard", "Fan Coil", "Heat Pump", "Other"]
end
I use javascript to make the text field emitter_type_other appear when "Other" is selected. Below is the javascript I use to set the value of my select field to the value I type in the text field (ignore the odd naming conventions).
var toggleRevertValues = document.getElementById("plant_submit_button");
var deleteEmitterType = document.getElementById("boiler_plant_emitter_type");
var deleteEmitterOther = document.getElementById("boiler_plant_emitter_type_other");
toggleRevertValues.addEventListener("click", function(e) {
if ( deleteEmitterType.value == "Other" ) {
deleteEmitterType.value = deleteEmitterOther.value;
}
}
The dropdown populates with a value if I type in the exact name of one of the options that already exists in my array (such as "Baseboard"). However, if I type in a new option, it shows up as null in the database, and my dropdown comes up as blank on an invalid submit. Is there any way to change my code so it allows a new value for the select?
In order to add a new emitter_type when user selects "Other" and types a new emitter type is to create a couple of new models: EmitterType' and 'BoilerEmitterType. The second is just a join table.
class Boiler < ActiveRecord::Base
has_many :boiler_emitter_types, dependent: :destroy
has_many :emitter_types, through: :boiler_emitter_types
end
class BoilerEmitterType < ActiveRecord::Base
belongs_to :boiler
belongs_to :emitter_type
end
class EmitterType
has_many :boiler_emitter_types, dependent: :destroy
has_many :boilers, through: :boiler_emitter_types
end
Then in response to the form submission, you either assign an existing EmitterType to the Boiler instance, or create a new EmitterType and assign it to the Boiler instance.
ActiveRecord has a nice method that will do this for you:
# boilers_controller.rb
def update
#boiler = Boiler.find(boiler_params(:id))
#emitter_type = EmitterType.find_or_create_by(name: boiler_params(:emitter_type_name)
#boiler.emitter_types << #emitter_type
if #boiler.save
# do something
else
# do something else
end
end
private
def boiler_params
# create the 'strong parameters' here
end
note, we're looking for the emitter type by name, not by id, which would be the typical response from a select option.
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 have a rails app that generates quotes to rent out meeting rooms using forms. Each quote requires a number of inputs pulled from different models in the database (room, rate) and some other inputs specific to each quote (hours, extras) that are filled directly into the quote form.
With all that data I run some calculations in the model to get the prices of a particular meeting room for a specific day.
I'm trying to show the results of those calculations dynamically on the same page, next to the form.
For that, I have:
-> A form, with nested fields.
<%= form_for(#quote) do |f| %>
<%= f.text_field :hours, class: "form-control" calculation %>
<%= f.text_field :extras, class: "form-control calculation" %>
.
.
<%= f.fields_for :contact, #quote.contact do |contact_form| %>
<%= contact_form.text_field :email, class: "form-control"%>
<% end %>
.
.
<%= f.fields_for :room, #quote.room do |room_form| %>
<%= room_form.collection_select :room_id, Room.all, :id, :name, class: "form-control" %>
<% end %>
.
.
.
<%= f.submit class: "btn btn-default btn-primary" %>
<% end %>
-> Some logic in the Quote model
class Quote < ActiveRecord::Base
belongs_to :contact
belongs_to :client
belongs_to :room
has_one :booking
belongs_to :rate
accepts_nested_attributes_for :contact
accepts_nested_attributes_for :booking
validates :rate_id, presence: true
validates :contact_id, presence: true
validates :room_id, presence: true
def calculate_quote
(half_day_price +
full_day_price +
extended_day_price +
hours_price) /
calculate_discount
end
def calculate_discount
if self.rate
self.rate.discount_rate / 100
else
return 1
end
.
.
.
end
-> A method in the controller that should create a json response with the result of those calculations.
class QuotesController < ApplicationController
before_action :set_quote, only: [:show, :edit, :update, :destroy, :pricing]
def pricing
respond_to do |format|
format.json { render json: #quote, methods: [:calculate_quote, :half_day_price], only: [:id, :calculate_quote, :half_day_price, :full_day_price] }
end
end
.
.
.
-> And everything into params
def quote_params
params.require(:quote).permit(:half_days, :full_days, :extended_days, :extras, :rate, :discount, :rate_id, contact_attributes: [:id, :name, :email, :phone], booking_attributes: [:id, :start_date, :end_date, :start_time, :finish_time])
end
-> And a js file that should pick up data from that json:
$(function(){
$(".calculation").change(function(){
var rate = $("#quote_rate_id").val();
var room = $("#quote_room_room_id").val();
var half_days = $("#quote_half_days").val();
var full_days = $("#quote_full_days");
var extended_days = $("#quote_extended_days");
var extras = $("#quote_extras");
var discount = $("#quote_discount");
jQuery.getJSON(url? + "/pricing", { rate: rate, room: room, half_days: half_days, full_days: full_days, extended_days: extended_days, hours: hours, extras: extras, discount: discounti}, function(data){
$("#half-days-price").text(data.half_days);
$("full-days-price").text(data.full_days);
});
});
});
Following this approach I'm stuck trying to find the right routes and urls to have this form working under quotes/new while passing the id of the quote object in json before it's saved. And that makes me wonder, is this the right approach to begin with? Should I be following a different path?
I would appreciate some fresh ideas.
"When clicking the link, javascript we take an id from date attribute, and we add it in the form, and we display a modal window."
How to add id value from javascript in the form?
link:
= link_to _('Report this'), '#', class: 'report', data: { comment_id: comment.id, toggle: 'modal', target: "#report_reasons"}
javascript:
$(function() {
$(".comments").on("click", ".comment a.report", function(e) {
e.preventDefault();
var $this = $(this);
var commentId = $this.data("comment-id");
});
});
form:
= simple_form_for(Report.new, url: report_video_comment_path(video.id, "???"), remote: true) do |f|
routes:
resources :videos, except: [:index] do
resources :comments, only: [:index, :create, :destroy] do
member do
post 'report', to: 'reports#create'
end
end
end
controller:
class ReportsController < ApplicationController
before_filter :authenticate_user!
before_filter :find_comment, only: [:create]
def create
#report = #comment.reports.build(report_params)
#report.user = current_user
#report.save
respond_to do |format|
format.js
end
end
private
def find_comment
#comment = Comment.find(params[:id])
end
def report_params
params.require(:report).permit(:type_report, :message)
end
end
instead of "???", it is necessary to insert data-attr from javascript.
Thanks.
If the form submit path is being altered by the user doing something on the client-side without a page load (or an ajax re-load of just the form content), then yes, you will need to use JS to update the form path. Have a look at these posts:
Rails - change form action based on radio selection
Changing the action of a form with javascript/jquery
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