Create multiple objects on a button click in Rails - ruby-on-rails

I have template_sections and sections and on a button click in my view I want
a) get all template_sections with a specific template_id, send them to the sections_controller
or
b) the section_controller method transform should get the template_sections there
and
iterate over them and create a new section object for each one of them, where I would set is_template = 0 and template_id = nil and save them.
View:
<div class="col-2 deploy-template">
<%= button_to 'Deploy Template', transform_sections_path, method: :post, *{call controller's method or send objects from here to transform method}*, remote: true, type: 'button', class: "btn btn-secondary btn-sm ml-0 mr-4" %>
</div>
Controller:
def new
#section = Section.new
if params[:is_template]
date = (Time.at(params['day'].to_i*86400-1)).strftime("%Y-%m-%d")
#section.starts_at_datetime = (date + ' ' + params['time']).in_time_zone rescue nil
else
#section.starts_at_datetime = (params['date'] + ' ' + params['time']).in_time_zone rescue nil
end
respond_to do |format|
format.js
end
end
def create
#section = Section.new(section_params)
#section.account_id = session_account_id
#section.is_template = true if params[:section][:is_template] == 'true'
#section.template_id = params[:section][:template_id]
respond_to do |format|
if #section.save
format.js
flash.now[:success] = "saved successfuly"
create_event #section, "create", "Create"
else
flash.now[:danger] = "#{#section.errors.full_messages}"
format.js { render 'layouts/notifications' }
end
end
end
def transform
template_sections.each do |template_section|
#save each template_section with is_template = 0 and template_id = nil
end

What you can do is pass your template sections to your controller through the params hash like so:
<%= button_to 'Deploy Template', method: :post, transform_sections_path(template_sections: [YOUR ARRAY VARIABLE HERE]), remote: true, type: 'button', class: "btn btn-secondary btn-sm ml-0 mr-4" %>
Your template sections should then be available through params[:template_sections] inside of your controller for you to do whatever you feel like doing, (I am assuming post: template_sections_path points to the transform action in your controller).
def transform
template_sections = params[:template_sections]
template_sections.each do |template_section|
#save each template_section with is_template = 0 and template_id = nil
end
I don't know what your TemplateSection model looks like so I will refrain from writing controller code that won't work, but hopefully once you have the data in your controller you will be able to work through it, and if not I am happy to help with a little bit more information.
As an additional note, "method: :post", and "type: button" seem redundant when using a button_to tag as those are the defaults, you can remove them unless you have a specific reason for having them.

Related

Items controller index method executes twice losing local or instance variable settings

Update: I have posted this to github jquery-datatables-rails as it appears to be an issue with that gem or, more likely, my use of it. I am posting the updated copy here as well with more of a description to ask for help. Note that, while the respond_to block is executed twice, the datatable code is only executed on the second execution of the block.
I am trying to pass a variable named my_view to the Items Controller index method so that I can selectively display the results. The current code works, sort of... In the end, the view is not maintained and any action in the resultant table, such as scrolling, causes the table to revert to showing all items instead of the desired selection.
The basic problem is that the index method is always executed twice any time a button is clicked. The first time, the my_view variable is correctly set. The second time, it is always nil. The jquery-datatables-rails gem is executed on both passes and is probably involved. It almost seems like I get two different transactions. I've even tried instance variables and they are also nil during the second pass. I don't understand how or why this happens.
To "fix" this problem, I set session[:my_view] during the first pass and then do not set it again on the second pass. During the ItemsDatatable.new execution, I then must clear session[:my_view] or it would be retained for the next transaction giving incorrect results. However, this results in the problem that I stated in that any change to the view, such as scrolling, causes a reversion to showing all items instead of the desired selection.
Curiously, I don't seem to have any other controller methods with this sort of anomaly, or at least haven't noticed it. Can someone tell me why the index method is executed twice, and how I might resolve this issue? Thank you!
EDIT UPDATE: I have one other controller in the admin namespace that uses respond_to with HTML/JSON options. It also is executed twice, so it seems that may (?) be related. Still seems odd behavior to lose all variables and execute it the second time, but I don't know. Is it related and how might I resolve it? Thanks...
Here are my buttons:
<%= link_to 'My Items', items_path(my_view: current_associate.id), class: 'btn btn-primary kc-wide' %>
<%= link_to 'All Items', items_path(my_view: "all"), class: 'btn btn-primary kc-wide' %>
And the Items Controller with the index method:
class ItemsController < ApplicationController
def index
session[:my_view] ||= params[:my_view]
respond_to do |format|
format.html
format.json { render json: ItemsDatatable.new(view_context) }
end
end
end
Related routes, just in case?
POST /items_index(.:format) items#index
items GET /items(.:format) items#index
POST /items(.:format) items#create
new_item GET /items/new(.:format) items#new
edit_item GET /items/:id/edit(.:format) items#edit
item GET /items/:id(.:format) items#show
PATCH /items/:id(.:format) items#update
PUT /items/:id(.:format) items#update
DELETE /items/:id(.:format) items#destroy
The Rails datatable code is:
class ItemsDatatable < ApplicationController
before_action :check_if_associate
delegate :params, :h, :link_to, :edit_item_path, :new_item_path, :location, to: :#view
def check_params(params)
# When using the .json suffix in URI, need to stub params so that I can see that JSON information
params[:draw] = 1 if params[:draw].blank?
params[:columns] = Array.new(1, {data: 0, name: '', searchable: true, orderable: true, search: {value: '', regex: false}}) if params[:columns].blank?
params[:order] = Array.new(1, {column: 0, dir: 'asc'}) if params[:order].blank?
params[:start] = 0 if params[:start].blank?
params[:length] = 10 if params[:length].blank?
params[:search] = {value: '', regex: false} if params[:search].blank?
end
def initialize(view)
#view = view
check_params(params)
#view
end
def as_json(options = {})
{
draw: params[:draw].to_i,
recordsTotal: Item.count,
recordsFiltered: items.total_entries,
data: data
}
end
private
def data
todays_date = Time.zone.now.to_date
items.map do |item|
status = item.status
# If current associate has it, show where it is.
if item.checkedout?(#view.current_associate)
status = "#{item.status}-#{item.lastloc}"
end
# If it's checked out but someone else has it, show who has it.
if item.not_available? and !item.checkedout?(#view.current_associate)
status = "#{item.status}-#{item.location}"
end
# Override the above if in return status, just showing that.
status = item.status == "Rtn" ? "Rtn" : status
odometer = item.odometer.to_s.gsub(/(\d)(?=(\d{3})+(?!\d))/, "\\1,")
msrp = item.msrp.to_s.gsub(/(\d)(?=(\d{3})+(?!\d))/, "\\1,")
age_date = item.age_date.nil? ? todays_date : item.age_date.to_date
[
link_to(item.stock_number, edit_item_path(item)),
ERB::Util.h(item.year),
ERB::Util.h(item.make),
ERB::Util.h(item.model),
ERB::Util.h(item.color),
ERB::Util.h(status),
ERB::Util.h(odometer),
ERB::Util.h(msrp),
ERB::Util.h((todays_date - age_date).to_i)
]
end
end
def items
#items ||= fetch_items
end
def fetch_items
items = Item.order("#{sort_column} #{sort_direction}")
items = items.includes(:item_location, item_location: [:locator])
items = items.includes(:key, key: [:key_location])
items = items.page(page).per_page(per_page)
unless #view.session[:my_view].blank? || #view.session[:my_view] == "all"
associate = Associate.find(#view.session[:my_view]).name
associate = associate.gsub(/'/, "''") # .gsub(/'/, "\\\\\'")
items = items.where("clshadow = \'#{associate}\'")
end
#view.session[:my_view] = nil
if params[:search][:value].present?
items = items.where("stock_number ilike :search or yrshadow ilike :search or mkshadow ilike :search or mdshadow ilike :search or coshadow ilike :search or status ilike :search", search: "%#{params[:search][:value]}%")
end
items
end
def page
params[:start].to_i/per_page + 1
end
def per_page
params[:length].to_i > 0 ? params[:length].to_i : 10
end
def sort_column
columns = %w[stock_number yrshadow mkshadow mdshadow coshadow status odometer msrp age_date]
columns[params[:order][0][:column].to_i]
end
def sort_direction
params[:order][0][:dir] == "desc" ? "desc" : "asc"
end
end
Related JavaScript code:
var itemstable = $('#itemstable').DataTable({
responsive: true,
autoWidth: false,
pagingType: 'full',
jQueryUI: true,
processing: true,
serverSide: true,
ajax: {
url: 'items_index.json',
type: 'POST',
contentType: 'application/json',
dataType: 'json',
data: function(d) {
return JSON.stringify(d);
}
},
columns: [null, null, null, null, null, null,
{className: 'dt-right'},
{className: 'dt-right'},
{className: 'dt-right'}
]
});
And the view:
<div class="span9">
<p>
<table id="itemstable" class="display dt-responsive no-wrap table-striped" width="80%">
<thead>
<tr>
<th class="all">Stock No.</th>
<th class="all">Year</th>
<th class="all">Make</th>
<th class="min-tablet">Model</th>
<th class="min-tablet">Color</th>
<th class="all">Status</th>
<th class="desktop">Mileage</th>
<th class="desktop">MSRP</th>
<th class="desktop">Aged</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<%= link_to 'My Items', items_path(my_view: current_associate.id),
class: 'btn btn-primary kc-wide' %>
<%= link_to 'All Items', items_path(my_view: "all"),
class: 'btn btn-primary kc-wide' %>
<%= link_to 'Recent Items', x_logs_path,
class: 'btn btn-primary kc-wide' %>
<%= link_to 'Home', '/', class: 'btn btn-primary kc-wide' %>
</div>
Try this:-
def index
session[:my_view] = params[:my_view] unless request.xhr?
respond_to do |format|
format.html
format.json { render json: ItemsDatatable.new(view_context) }
end
end
It appears that the gem executes independently, without transferring control to the supplied code, during the first pass in order to set up the parameter list with all the desired variables. It then appears to force a reload to initiate the process with the parameter list as desired. I am assuming this from reviewing the variables during the first and second passes. During this process, it forgets any other variables, though I don't think that it should do that.
In any case, I have to use session variables to maintain status during this reload. I changed the controller index as follows, and then eliminated the clearing of session[:my_view] in my datatables controller.
def index
unless params[:my_view].blank?
session[:requested_view] = params[:my_view]
end
session[:my_view] = session[:requested_view]
respond_to do |format|
format.html
format.json { render json: CarsDatatable.new(view_context) }
end
end

Implementing ajax request to create/edit rails object

I am trying to create a add/edit credit card form within my edit user page. To do so I am trying to implement an ajax call to the edit and create functions in my customers controller.
This is the code I have for the update button within the modal window:
<%= button_tag "Update", :class =>"btn submit-button", :type => 'button', :onclick => "onUpdateCard('#{current_user.id}');"%>
This is the function that it calls:
function onUpdateCard(id) {
this.id = id;
// disable the submit button to prevent repeated clicks
$('.submit-button').attr("disabled", "disabled");
var card_number = document.getElementById('card_number').value;
var card_code = document.getElementById('card_code').value;
var card_month = document.getElementById('card_month').value;
var card_year = document.getElementById('card_year').value;
var response = Stripe.createToken({
number: $('#card_number').val(),
cvc: $('#card_code').val(),
exp_month: $('#card_month').val(),
exp_year: $('#card_year').val()
}, stripeResponseHandler);
// allow the form to submit with the default action
return false;
};
function stripeResponseHandler(status, response) {
if (response.error) {
$(".payment-errors").text(response.error.message);
$(".submit-button").removeAttr("disabled");
} else {
var token = response['id'];
var new_url = "/users/" + this.id + "/customers/new";
var edit_url = "/users/" + this.id + "/customers/1/edit";
$.ajax({
type:'GET',
url: edit_url,
data: {'stripe_card_token': token}
});
}
return false;
};
And in the controller there is the edit function:
def edit
#user = current_user
#customer = #user.customer
stripe_customer = Stripe::Customer.retrieve(#customer.stripe_customer_token)
stripe_customer.card = params[:stripe_card_token]
stripe_customer.save
end
Can you help me figure out how to handle the ajax correctly? I'm not sure how to debug this properly...
Here I'm suggesting the alternative to handle update request using AJAX.
I'm not improving or correcting your code but giving you a way to handle AJAX requests in Rails 3.
a. view
Whatever information you wants to update in Database using AJAX call you will pass through a form. So for making a AJAX request you need to add :remote => true in your form. Rails provides this helper.
<%= form_for #customer, :url => admin_customers_path, :method => :post, :remote => true, :html => { :id => "customer-form" } do |form|-%>
<%= render :partial => 'admin/customers/form', :object => form %>
<%= form.submit 'Update' %>
<% end %>
In the _form.html.erb you can add textfield or other this whatever you wants to add in your edit form
b. controller
Because of " :remote => true " you form submission will make a JS request so in update action after saving the data of customer
control will for to format.js and then it will look for update.js.erb in views.
def update
if #customer.update_attributes(params[:customer])
respond_to do |format|
format.html {
flash[:success] = "customer's info was updated Successfully."
redirect_to customers_path
}
format.js
end
else
respond_to do |format|
format.html {
flash[:error] = #customer.errors.present? ? #customer.errors.full_messages.join('<br />') : "Oops! There is some problem with category update."
render :edit
}
format.js
end
end
end
c. update.js.erb
You can do stuffs after successful update. Suppose you want to highlight some div then you can do like this.
$('.target-div').effect("highlight", {}, 2500);

Really having difficulty with New tokens on token input (from different model)

Im using jquery-tokeninput, but a fork of it which allows the User to add new custom tokens (Tag) for each Resource.
Example here (Scroll down to the tag box and type a few letters. you can type ones that dont exist): http://www.tomsguide.fr/solutions/nouveau_sujet.htm
The current return value from the fork I'm using is this (new value in quotes):
16,42,'Subway',37,'McDonald\'s',734
I'm having extreme difficulty trying to handle this in Rails. This sums it up perfectly.
This is what I have so far, and its not working, probably for a lot of reasons I'm not seeing, but the main reason is that I need to create new Tag instances but not save them, that way I can somehow pass them back into the token input, and save the new Tags along with the new Resource when they submit the form. When you use Tag.new though, it doesnt create an ID.
resource.rb
attr_accessor :tokens_list
# CUSTOM TOKENS
def tag_tokens=(tokens)
self.tokens_list = tokens.split(",")
if new_custom_tokens?
self.tokens_list.each do |token|
tokens_list << token if token.include? "'"
end
end
self.tag_ids = self.tokens_list
end
def new_custom_tokens?
self.tokens_list.each do |token|
return true if token.include? "'"
end
false
end
resources_controller.rb
def create
#title = "Submit Resource"
#resource = Resource.new(params[:resource])
assign_to_global_user?
# CUSTOM TOKENS
if #resource.new_custom_tokens?
custom_token_time_restriction
# Create Tag.new
end
if #resource.valid?
#resource.save
flash[:notice] = "Your link has been successfully submitted."
redirect_to root_url
else
render :action => :new
end
end
def assign_to_global_user?
if user_signed_in?
#resource.user_id = current_user.id
else
#resource.user_id = User.find_by_username("Global_User").id
end
end
private
# CUSTOM TOKENS
def custom_token_time_restriction
limit = 7 # days
if (#resource.user_id != User.global_user_id) and (Time.now - limit.days > User.find(#resource.user_id).created_at)
# TODO: Check if they are anonymous or their account is newer than 7 days
else
flash[:notice] = "You be Logged in to add new tags, and your account must be older than #{limit} days."
render :action => :new
end
end
new.html.erb (for resource#new)
<div class="field">
<%= f.label :tags %>
<%= f.text_field :tag_tokens, "data-pre" => #resource.tags.to_json(:only => [:id, :name]), :class => :tagbox %>
</div>
I had the same problem. This is what I have done:
This is the function where I return tokens of search in json format.
tags = TagMaster.where("name LIKE ?", "%#{params[:q]}%").limit(10)
if tags == []
list << {"id" => "0","name"=>new_tag.rstrip}
else
tags.each { |tag| list << {"id" => tag.id.to_s, "name" => tag.name }}
end
respond_to do |format|
format.json { render :json => list.to_json, :layout => false }
end
Now this will allow show you whatever you type in auto complete dropdown and on clicked it will show as a token.
Now you can't add any more custom tokens because any token that is not in database will return id 0 so only one custom token is allowed at this point of time.
For that problem I did following.
var k = jQuery.noConflict();
k("#project_tags").tokenInput("tag_list", {
hintText: "Enter Tags for your Project",
noResultsText: "No Such Tags",
searchingText: "Looking for your Tags",
preventDuplicates: true,
theme: "facebook",
onAdd: function (item) {
if (item.id == '0') {
k.ajax({
url: '/add_project_tag',
data: { name: item.name },
success:function(data) {
k("#project_tags").tokenInput("add", {id: data, name: item.name});
k("#project_tags").tokenInput("remove", {id: '0' });
}
});
}
}
});
As you can see here i call add_project_tag where I store that custom tag into database and it returns id of that inserted tag. So now you simply add the same token with it's id and remove token with 0.
So now there won't be any token with 0 and you can add as many new token as you want.
Hope this helps. Throw your questions if any more complications.

Rails checkbox AJAX call, don't want to render anything

I've got a little demo setup in which clicking a checkbox toggles an attribute via AJAX. It's working fine, but Rails REALLY wants to render something, so I've basically resorted to creating a blank toggle.js.erb file in my views.
Controller action in question:
def toggle
#task = Task.find(params[:id])
respond_to do |format|
format.js do
if (#task.status != true)
#task.status = true
else
#task.status = false
end
#task.save
render :layout => false
end
end
end
View in question:
<h1>Tasks</h1>
<ul style="list-style-type: none;">
<% #tasks.each do |task| %>
<li id="<%= dom_id(task) %>">
<%= check_box_tag(dom_id(task), value = nil, checked = task.status) %>
<%= task.action %> <%= link_to 'Edit', edit_task_path(task) %>
<%= link_to 'Delete', task, :confirm => 'Are you sure?', :method => :delete, :remote => true %>
</li>
<% end %>
</ul>
<%= link_to 'New Task', new_task_path %>
<script>
$$('input').each(function(el) {
el.observe('click', function(event) {
// Get the task ID
var elId = el.id.split("_")[1];
// Build the toggle action path
var togglePath = '/tasks/' + elId + '/toggle/';
// Create request, disable checkbox, send request,
// enable checkbox on completion
new Ajax.Request(togglePath, {
onCreate: function() {
el.disable();
},
onSuccess: function(response) {
},
onComplete: function() {
el.enable();
}
});
});
});
</script>
Without the blank toggle.js.erb file I've got in the views, Rails still gives me an error saying that it's trying to render something.
Ultimately, I'd like to both not have to have a blank toggle.js.erb file, and I'd like to get that Prototype stuff into my static JavaScript stuff and out of the view.
I'm pretty new to Rails, so there's probably an easier way to be doing this, but I'm kind of stuck here.
render :layout => false means that you want to render 'toggle' view without layout.
If you don't want render anything at all, you should use :nothing => true option
def toggle
#task = Task.find(params[:id])
#task.toggle! :status
# if it used only by AJAX call, you don't rly need for 'respond_to'
render :nothing => true
end
EDIT: In Rails4/5 you can use head :ok instead of render nothing: true, it's more preferable way to do this, but result is the same.

form_for, fields_for and two models

I have form that create two objects and save them to database.
I want to do next things:
save data in database (booth objects)
validate fields (I have validation in model)
and if validation fail, I want to populate fields with entered data
edit action for this form
Problems:
If I use #report I get:
Called id for nil, which would
mistakenly be 4 error
(can't find object). I have in controller, in encreate action #report = ReportMain.new and in action that render that view.
When I use :report_main (model name) it works, it save data to database, but I can't get fields populated when validation fails.
Questions:
What to do with this two models to make this to work (validation, populating fields, edit)?
Can you give me some advice if approach is wrong?
My view looks like this:
<%= form_for(#report, :url => {:action => 'encreate'}) do |f| %>
<%= render "shared/error_messages", :target => #report %>
<%= f.text_field(:amount) %>
<% fields_for #reporte do |r| %>
<%= r.check_box(:q_pripadnost) %>Pripadnost Q listi
<%= select_tag('nacinpakovanja',options_for_select([['Drveno bure', 'Drveno bure'], ['Kanister', 'Kanister'], ['Sanduk', 'Sanduk'], ['Kese', 'Kese'], ['Posude pod pritiskom', 'Posude pod pritiskom'], ['Kompozitno pakovanje', 'Kompozitno pakovanje'], ['Rasuto', 'Rasuto'], ['Ostalo', 'Ostalo']])) %>
<%= r.text_field(:ispitivanjebroj) %>
<%= r.text_field(:datumispitivanja) %>
<% end %>
<input id="datenow" name="datenow" size="30" type="text" value="<%= #date %>">
<div class="form-buttons">
<%= submit_tag("Unesi izvestaj") %>
</div>
<% end %>
encreate actin in ReportController:
def encreate
#report = ReportMain.new
#reporte = ReportE.new
#reportparam = params[:report_main]
#report.waste_id = params[:waste][:code]
#report.warehouse_id = Warehouse.find_by_user_id(current_user.id).id
#report.user_id = current_user.id
#report.company_id = current_user.company_id
#report.amount = #reportparam[:amount]
#report.isimport = false
#report.isfinished = false
#report.reportnumber = ReportMain.where(:company_id => current_user.company_id, :isimport => false).count.to_i+1
if #report.save
#reporte.report_main_id = #report.id
else
redirect_to(:action => 'exportnew')
return
end
#reporte.vrstaotpada = params[:vrstaotpada]
#reporte.nacinpakovanja = params[:nacinpakovanja]
#reporte.ispitivanjebroj = #reportparam[:ispitivanjebroj]
#reporte.datumispitivanja = #reportparam[:datumispitivanja]
#reporte.q_pripadnost = #reportparam[:q_pripadnost]
#reporte.datumpredaje = #date
if #reporte.save
redirect_to(:action => 'show', :id => #reporte.id)
else
redirect_to(:action => 'exportnew')
end
end
I think your problem in this case is that you use redirect_to instead of render. When you use redirect_to then you lose all the variables from your current action. I would probably do something like this in your encreate action:
if #reporte.save
render :show
else
render :exportnew
end
When you use render then it will use the variables from the current action but the view from the action you send to the render method. So when form_for is called with the #report variable, it is already populated with the values that was sent to encreate. Just make sure that you use the same variable names in the different actions but it looks like you do that already.

Resources