Storing Array of Checkbox Selections - ruby-on-rails

I have an attribute called "features" in my application. In my form, "features" consists of a list of check boxes. The idea here is that users can check off which "features" apply to their post, and that list of features gets saved into the record.
I see the array being saved in my console ("features"=>{"Private bathroom"=>"1", "Elevator"=>"0", "Complimentary breakfast"=>"1", "Great view"=>"1", "Cable TV"=>"0", "Fireplace"=>"0", "Other (see description)"=>"0", "Sweet location"=>"0"}).
However... When I view the record, features returns nil. It doesn't seem to be saving the features array.
Code provided below. Any idea what I'm doing wrong here?
models/accommodation.rb
class Accommodation < ActiveRecord::Base
validates_presence_of :title, :description, :thing, :location
attr_accessible :photo_attributes, :title, :description, :thing, :borough, :location, :spaces, :price, :features
has_one :photo
has_many :requests
belongs_to :user
accepts_nested_attributes_for :photo, :allow_destroy => true
end
controllers/accommodation_controller.rb
class AccommodationsController < ApplicationController
before_filter :auth, :except => :show
uses_tiny_mce ( :options => {
:theme => 'advanced',
:theme_advanced_toolbar_location => 'top',
:theme_advanced_toolbar_align => 'left',
:theme_advanced_buttons1 => 'bold,italic,underline,image,bullist,numlist,separator,undo,redo',
:theme_advanced_buttons2 => '',
:theme_advanced_buttons3 => ''
})
def show
#accommodation = Accommodation.find(params[:id])
end
def new
#accommodation = current_user.accommodations.build
#accommodation.build_photo
end
def create
#accommodation = current_user.accommodations.build(params[:accommodation])
if #accommodation.save
flash[:notice] = "Successfully created your accommodation."
redirect_to #accommodation
else
render :new
end
end
def edit
#accommodation = Accommodation.find(params[:id])
end
def update
#accommodation = Accommodation.find(params[:id])
if #accommodation.update_attributes(params[:accommodation])
flash[:notice] = "Successfully updated accommodation."
redirect_to #accommodation
else
render :edit
end
end
def destroy
#accommodation = Accommodation.find(params[:id])
#accommodation.destroy
flash[:notice] = "Successfully destroyed accommodation."
redirect_to :inkeep
end
private
def auth
if current_user
if params[:action] != 'new' && params[:action] != 'create'
#accommodation = Accommodation.find(params[:id])
if #accommodation.user_id != current_user.id
flash[:notice] = "You don't own this accommodation!"
render :action => 'show'
end
end
return true
else
flash[:error] = "Please login first."
redirect_to :controller => 'sessions', :action => 'new'
end
end
end
views/accommodations/_form.html.erb
<%= form_for #accommodation, :html => {:multipart => true} do |f| %>
<%= f.error_messages %>
<p>
Title<br />
<%= f.text_field :title, :size => 60 %>
</p>
<p>
Description<br />
<%= f.text_area :description, :rows => 17, :cols => 75, :class => "mceEditor" %>
</p>
[...snip...]
<p>
<i>Featuring...</i>
<%= fields_for :features do |feature_fields| %>
<table>
<tr>
<td><%= feature_fields.check_box 'Private bathroom' %> Private bathroom</td>
<td><%= feature_fields.check_box 'Cable TV' %> Cable TV</td>
<td><%= feature_fields.check_box 'Complimentary breakfast' %> Complimentary breakfast</td>
</tr>
<tr>
<td><%= feature_fields.check_box 'Elevator' %> Elevator</td>
<td><%= feature_fields.check_box 'Fireplace' %> Fireplace</td>
<td><%= feature_fields.check_box 'Great view' %> Great view</td>
</tr>
<tr>
<td><%= feature_fields.check_box 'Sweet location' %> Sweet location</td>
<td><%= feature_fields.check_box 'Other (see description)' %> Other (see description)</td>
</tr>
</table>
<% end %>
</p>
[...snip...]
<% end %>

First, is the features array inside of your the accommodation hash in the params hash?
Second, there is no db column type which accepts an array, so you need to put
serialize :features
in the model. This will store the array as yaml in the db. You can also specify the data type as an argument to serialize() (probably Array in this case), but it's not always necessary.

I add the same problem today, it appears the form isn't properly built in the view.
Indeed, take a closer look at your params: params[:features] is outside params[:accomodation]
I simply added at the beginning of my create action:
params[:accomodation][:features] = params[:features]
And it works properly

What about your model? Do you have attr_accessible or attr_protected calls in there?

Related

Rails update attribute when dealing with through relationships

I’m trying to update an attribute on a show page, keeping it in the show page, that is connected through a different table. For example: manager logs into admin site and sees several resources, clicks on one and can see all users that have access to the resource (either approved or pending). They can currently delete a user’s access. What I’m trying to do is allow the manager to change the status of the request say from pending to approved.
So for my models I have the following:
class TrainingResource
has_many :user_training_resources, dependent: :destroy
end
class UserTrainingResource
belongs_to :user
belongs_to :training_resource
enum status: [:pending, :confirmed, :rejected]
end
class Users
has_many :user_training_resources, dependent: :destroy
has_many :training_resources, through: :user_training_resources
end
TrainingResourcesController
class Admin::TrainingResourcesController < Admin::ApplicationController
belongs_to_app :training_resources
add_breadcrumb 'Training Resources', :admin_training_resources_path
before_action :load_training_resource, only: [:show, :edit, :update, :destroy]
respond_to :html, :json
def index
#training_resources = TrainingResource.paginate(page: params[:page])
#training_resources = #training_resources.search(params[:search]) if params[:search]
respond_with(#training_resources)
end
def show
respond_with #training_resource
end
def new
#training_resource = TrainingResource.new
respond_with(#training_resource)
end
def create
#training_resource = TrainingResource.new(training_resource_params)
flash[:notice] = 'TrainingResource created successfully' if #training_resource.save
respond_with #training_resource, location: admin_training_resources_path
end
def edit
respond_with(#training_resource)
end
def update
flash[:notice] = 'TrainingResource updated successfully' if #training_resource.update(training_resource_params)
respond_with #training_resource, location: admin_training_resources_path
end
def destroy
flash[:notice] = 'TrainingResource deleted successfully' if #training_resource.destroy
respond_with #training_resource, location: admin_training_resources_path
end
private
def load_training_resource
#training_resource = TrainingResource.find_by!(id: params[:id])
end
def training_resource_params
params.require(:training_resource).permit(:name, :description, :total_subscriptions, :url)
end
end
UserTrainingResourcesController, which is pointing to the TrainingResourcesController
class Admin::UserTrainingResourcesController < Admin::ApplicationController
belongs_to_app :training_resources
add_breadcrumb 'Training Resources', :admin_training_resources_path
before_action :load_training_resource
respond_to :html, :json
def edit
respond_with #user_training_resource
end
def update
flash[:notice] = 'UserTrainingResource updated successfully' if #user_training_resource.update(user_training_resource_params)
respond_with #user_training_resource, location: admin_training_resources_path
end
def destroy
flash[:notice] = 'UserTrainingResource deleted successfully' if #user_training_resource.destroy
respond_with #user_training_resource, location: admin_training_resources_path
end
private
def load_training_resource
#user_training_resource = UserTrainingResource.find_by!(id: params[:id])
end
def user_training_resource_params
params.require(:user_training_resources).permit(
:training_resources_id, :status).merge(user_id: current_user_id)
end
end
Training Resource Show
<tbody>
<% #training_resource.users.each do |training| %>
<tr>
<td><%= training.full_name %></td>
<% utr = training.user_training_resources.where(training_resource: #training_resource).first %>
<td><%= utr.status.capitalize %>
<%= form_tag '/user_training_resource/edit', :method => :get do %>
<%= select_tag( :user_training_resources_id, options_for_select(['pending', 'confirmed', 'rejected']))%>
<%= submit_tag 'Edit Status', class: 'btn btn-default btn-sm' %>
<% end %>
<%= tb_form_for [:admin, #training_resource], :remote => true, :data => {:errors => :inline, :success => admin_user_training_resources_path} do |f| %>
<%= tb_form_errors(f.object, :base) %>
<%= f.tb_select :name, options_for_select(holder, :status) %>
<%= f.tb_save_buttons('User Training Resource', admin_user_training_resources_path) %>
<% end %>
</td>
<td class="table-actions">
<%= link_to 'Edit', edit_admin_user_training_resource_path(training), :class => 'btn btn-default btn-sm' %>
<%= link_to 'Delete', admin_user_training_resource_path(training), :method => :delete, :data => {:confirm => 'Are you sure you want to delete this?'}, :class => 'btn btn-danger btn-sm' %>
</td>
</tr>
<% end %>
</tbody>
User Training Resource Helper, Holder Method
def holder
TrainingResource.all.each(&:id)
end
The link to Edit I’ll end up taking out because it pushes the user to the UserTrainingResource edit page and I want to keep it on the current page. The top form with the select_tag isn’t actually reading in the current status and then the submit_tag is redirecting the page.
My latest attempt is the bottom form_for. This is pulling in the TrainingResource and not the UserTrainingResource data. When I change the tb_select to :status, options_for_select(holder, :status) I end up with undefined method `status’. It looks like it’s only aiming at TrainingResource. I’ve also tried:
Thought here that it would pull in the default status and then allow the options to change. Still have an issue with undefined method of ‘status’ and even then there’s the issues with save.
I’ve also tried:
In this case I end up with undefined method ‘map’. Which I've tried to pinpoint down with attempting pluralization on #training_resource and #user_training_resource but that turns up First argument in form cannot contain nil or be empty.
Edit:
Tried in the UserTrainingResourcesController:
def set_to_confirmed
#user_training_resource = UserTrainingResource.find(params[:user_training_resource])
end
Then in the show
<%= link_to 'Confirmed', {:controller => 'user_training_resources', :action => 'set_to_confirmed', :status => #training_resource.user_training_resource }, :class => 'btn btn-default btn-sm'%>
Although no errors on the page load when clicking on the link I get:
Requested URL: https://localhost:3000/admin/set_to_confirmed?status=%23%3CUserTrainingResource%3A%3AActiveRecord_Associations_CollectionProxy%3A0x00007f93ee8b0f90%3E
Latest attempt using the following:
<% #training_resource.spud_users.each do |training| %>
<tr>
<td><%= training.full_name %></td>
<% utr = training.user_training_resources.where(training_resource: #training_resource).first %>
<td>
<%= tb_form_for [:admin, #training_resource], :remote => true, :data => {:errors => :inline, :success => admin_training_resources_path} do |f| %>
<%= f.select(:user_training_resource_id, options_for_select(['pending', 'confirmed', 'rejected']), :selected => utr.status)%>
<%= f.tb_save_buttons('', admin_training_resources_path) %>
<% end %>
</td>
This will show only the options, which is good, but I need it to default to what's current in the database and the save isn't sticking.
If I get the point, you need to update the joining table UserTrainingResource changing the status column.
I probably would create three actions in Admin::UserTrainingResourcesController:
def set_to_pending
end
def set_to_confirmed
end
def set_to_rejected
end
Then I would add three link to each row in the table in TrainingResource#Show passing the necessary parameters to complete the action, like explained in this post: pass parameter by link_to ruby on rails

Rails 4, how to update a model field from a different controller?

I am trying to update an invoice fields, when checking out in the carts controller. These must be present when checking out, or it should fail. However, I can't get it to update, much less validate them.
Here is my code:
cart show view:
<div class = "row">
<div class = "col-lg-3 col-lg-offset-6 text-left">
<strong>Customer: </strong>
<%= collection_select(:invoice, :customer_id, #customers, :id, :full_name, {:prompt => 'Please Select'}, class: 'form-control') %>
</div>
<div class = "col-lg-3 ext-left">
<strong>Seller: </strong>
<%= collection_select(:invoice, :employee_id, #employees, :id, :full_name, {:prompt => 'Please Select'}, class: 'form-control') %>
</div>
<div class = "col-lg-12 text-right">
<%= form_tag carts_checkout_path, method: :post do |f| %>
<%= submit_tag 'Complete', class: 'btn btn-success' %>
<% end %>
</div>
</div>
carts controller:
class CartsController < ApplicationController
def show
#invoice = current_invoice
#invoice_products = current_invoice.invoice_products
#customers = Customer.all
#employees = Employee.all
end
def checkout
current_invoice.customer_id = params[:customer_id]
current_invoice.employee_id = params[:employee_id]
current_invoice.save
redirect_to current_invoice
end
end
current_invoice is the current session's invoice, related to the cart. It redirects correctly, but doesn't update.
in the invoices controller:
def invoice_params
params.require(:invoice).permit(:invoice_number, :customer_id, :invoice_date, :invoice_status_id, :employee_id, invoice_products_attributes: [:id, :invoice_id, :product_id, :price, :tax, :discount, :value])
end
Can anyone please help me in identifying where I am going wrong? Could it be my approach is not even valid?
Thanks in advance
The type of functionality you're after is considered "business logic" and should be implemented in the model and called from the controller.
You can define a method in a model:
class Invoice < ActiveRecord::Base
def update_invoice(cust_id, emp_id)
if self.update_attributes(:customer_id => cust_id], :employee_id = emp_id])
puts "Success!
else
puts "Failed to update record. Handle the error."
end
end
You can call my_method from carts_controller.rb like this:
def update
# all your regular update logic here
# replace the bit of code that saves the cart with something like this:
respond_to do |format|
if(current_invoice.update_invoice(params[:customer_id], params[:employee_id])
if(#cart.update(cart_params))
format.html { redirect_to #activity, notice: 'Activity was successfully updated.' }
format.json { render :show, status: :ok, location: #activity }
else
format.html { render :edit }
format.json { render json: #activity.errors, status: :unprocessable_entity }
end
end
end
Also, note the use of update_attributes rather than save. Bear in mind that update_attributes will return false if you run into any problems updating (e.g. one or more validations failed). Don't confuse update_attributes with the singular update_attribute which updates a single field and will not run validations.
Finally got it.
current_invoice.update_attributes(customer_id: params[:invoice][:customer_id], employee_id: params[:invoice][:employee_id])
Also in view, changed location of form_tag:
<div class = "row">
<%= form_tag carts_checkout_path, method: :post do |f| %>
<div class = "col-lg-3 col-lg-offset-6 text-left">
<strong>Cliente: </strong>
<%= collection_select(:invoice, :customer_id, #customers, :id, :full_name, {:prompt => 'Favor Seleccionar'}, class: 'form-control') %>
</div>
<div class = "col-lg-3 ext-left">
<strong>Vendedor: </strong>
<%= collection_select(:invoice, :employee_id, #employees, :id, :full_name, {:prompt => 'Favor Seleccionar'}, class: 'form-control') %>
</div>
<div class = "col-lg-12 text-right">
<%= submit_tag 'Completar', class: 'btn btn-success' %>
</div>
<% end %>
</div>
Could it be my approach is not even valid
Your approach is definitely valid, it's great that you're using sessions in this way.
I'd do it slightly differently:
#config/routes.rb
resource :cart, except: [:edit, :new, :create], path_names: { update: "checkout" }
This will give you the following paths:
#url.com/cart -> carts#show (here you can invoke a cart if one doesn't exist)
#url.com/cart/checkout #-> POST to "update" method in carts controller
#url.com/cart/ (method: :delete) -> DELETE to "destroy" cart (refresh)
--
#app/controllers/carts_controller.rb
class CartsController < ApplicationController
before_action :setup_cart
def show
#cart = current_cart #-> products called from this. I don't know how you're linking them
#customers = Customer.all
#employees = Employee.all
end
def update
#invoice = Invoice.find_or_create_by(id: current_card.id)
#invoice.update update_params
redirect_to cart_path
end
def destroy
current_cart = nil
redirect_to carts_path, notice: "Cart Cleared"
end
private
def setup_cart
current_cart ||= sessions[:cart]
end
def update_params
params.require(:cart).permit(:customer_id, :employee_id)
end
end
Now, to update the cart, you'll want to take note from MarsAtomic's answer. However it must be noted that naked params are not available in the model.
If you use update_attributes, or just plain update, you'll need to do the following:
#app/models/cart.rb
class Invoice < ActiveRecord::Base
has_many :products
belongs_to :employee
belongs_to :customer
#validations here
#callbacks here (what MarsAtomic refers to as business logic)
before_save :do_something, only: :update
private
def do_something
#something here
#params appended to current instance of object
#eg self.customer_id
end
end
I'd also go more succinct in your view:
#app/views/carts/show.html.erb
<div class = "row">
<%= form_tag cart_checkout_path, method: :patch do |f| %>
<% options = [["cliente", "customer"], ["vendedor", "employee"]] %>
<% options.each do |name, type| %>
<%= content_tag :strong, "#{name.titleize}:" %>
<%= collection_select :cart, eval(":#{type}_id"), instance_variable_get("##{type.pluralize}"), :id, :full_name, {:prompt => 'Favor Seleccionar'}, class: 'form-control') %>
<% end %>
<% content_tag :div, class: "col-lg-12 text-right" do %>
<%= submit_tag 'Completar', class: 'btn btn-success' %>
<% end %>
<% end %>
</div>

How to use fields_for for child update in 1-to-many in rails 3.1.3?

A user has many user_levels and a user_level belongs to a user. Here is the code for user_level for update:
<%= simple_form_for #user do |f| %>
<% #user.user_levels.each do |level| %>
<%= f.fields_for :user_levels, level do |builder| %>
<p><%= render :partial => 'user_levels', :locals => {:f => builder, :i_id => level.position} %></p>
<% end %>
<% end %>
<% end %>
The code above will display the current user_level with selected set to the current position. The problem is that the update was not saved for user_level.
Here is the user_levels partial
<div class="fields">
<%= f.input :position, :collection => return_position, :prompt => "Choose position",
:label => false, :include_blank => true, :selected => i_id %>
<%= link_to_remove_fields "remove", f %>
</div>
Here is the string posted to the server for params[:user]. There are currently two positions for the user and one gets deleted as update:
{"name"=>"test eng", "login"=>"tester12", "password"=>"password", "password_confirmation"=>"password", "user_type"=>"employee", "user_levels_attributes"=>{"0"=>{"id"=>"5"}, "1"=>{"position"=>"elec_eng", "_destroy"=>"false", "id"=>"6"}}}
Any solution for child update? Thanks so much
UPDATE:
def new
#user = User.new
end
def create
#user = User.new(params[:user])
#user.input_by_id = session[:user_id]
if #user.save!
redirect_to URI.escape("/view_handler?index=0&msg=saved!")
else
flash.now[:error] = 'not saved!'
render 'new'
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
#user.input_by_id = session[:user_id]
if #user.update_attributes(params[:user])
redirect_to URI.escape("/view_handler?index=0&msg=saved}")
else
flash.now[:error] = 'not saved!'
render 'edit'
end
end
user.rb:
has_many :user_levels, :dependent => :destroy
accepts_nested_attributes_for :user_levels, :reject_if => lambda { |a| a[:position].blank? }, :allow_destroy => true
validates_associated :user_levels
Try removing the line #user.user_levels.each do |level| and please post what issue pops up then.
And does the user model accept nested attributes for user levels ?

Rendering error messages for form_tag that creates multiple objects?

I'm not sure how to display the error messages for my form when using it in this form_tag scenario. My code below allows me to create 5 products at once on a form but unfortunately only renders the notice that "an error occurred...".
Here is my code:
Product.rb
class Product < ActiveRecord::Base
attr_accessible :price, :name, :purchase_date, :product_store, :in_category
belongs_to :user
belongs_to :store
attr_reader :product_store
validates_inclusion_of :in_category, :in => [true, false]
validates_presence_of :name, :price, :store_id, :user_id
validates_numericality_of :price
def product_store=(id)
self.store_id = id
end
end
Products_controller.rb
class ProductsController < ApplicationController
def new
#products = Array.new(5) { Product.new }
end
def create_multiple
#products = current_user.products.create(params[:products].map { |_k, p| p.merge params[:product] })
if #products.each(&:save)
redirect_to :back, :notice => "Success!"
else
redirect_to :back, :notice => "An error occured, please try again."
end
end
end
Form.html.erb
<%= form_tag create_multiple_products_path, :method => :post do %>
<%= error_messages_for #product %>
# the :purchase_date and :in_category are merged into all 5 Products.
<%= date_select("product", "purchase_date") %>
<%= label_tag :in_category, 'Add to Category?' %>
<%= radio_button("product", :in_category, 1) %>
<%= radio_button("product", :in_category, 0) %>
<% #products.each_with_index do |product, index| %>
<%= fields_for "products[#{index}]", product do |p| %>
<%= render "fields", :f => p %>
<% end %>
<% end %>
<%= submit_tag "Done" %>
<% end %>
Theirs 2 issues. 1. Getting the validations for the fields outside of the fields_for to show .2. And then the ones inside of the fields_for. How could I do this?
Thank you.
I've been trying to do much the same thing, with this:
<% #products.each_with_index do |product, index| %>
<% product.errors.full_messages.each do |value| %>
<li><%= value %></li>
<% end %>
However, this only shows errors for the first product with errors. You submit it, and if there is a subsequent product with errors, you are sent back to that page, and that next product with errors shows its errors, etc.
EDIT: Got it. It has to do with how I was validating. Instead of this:
if #products.all?(&:valid?)
do this:
#products.each(&:valid?) # run the validations
if #products.all? { |t| t.errors.empty? }

Ruby on rails nested form model

I'm trying to use rails nested form_for helper, but I am getting the following error:
BlogPage(#49859550) expected, got Array(#31117360)
Here are my model objects:
class Blog < ActiveRecord::Base
# Table Configuration
set_table_name "blog"
# Model Configuration
belongs_to :item
has_many :blog_pages
accepts_nested_attributes_for :blog_pages, :allow_destroy => true
end
class BlogPage < ActiveRecord::Base
# Table Configuration
set_table_name "blog_page"
# Model Configuration
belongs_to :blog
end
Here is the form I generated (left out unnecessary HTML):
<% form_for :blog, :url => { :action => :create } do |blog_form| %>
<%= blog_form.text_field :title, :style => "width: 400px" %>
<% blog_form.fields_for :blog_pages do |page_fields| %>
<% #blog.blog_pages.each do |page| %>
<%= page_fields.text_area :content, :style => "width: 100%",
:cols => "10", :rows => "20" %>
<% end %>
<% end %>
<% end %>
Here are the parameters that are sent to the controller:
{"commit"=>"Save",
"blog"=>{"blog_pages"=>{"content"=>"This is the new blog entries contents."},
"title"=>"This is a new blog entry.",
"complete"=>"1"},
"authenticity_token"=>"T1Pr1g9e2AjEMyjtMjLi/ocrDLXzlw6meWoLW5LvFzc="}
Here is the BlogsController with the create action that gets executed:
class BlogsController < ApplicationController
def new
#blog = Blog.new # This is the line where the error gets thrown.
# Set up a page for the new blog so the view is displayed properly.
#blog.blog_pages[0] = BlogPage.new
#blog.blog_pages[0].page_number = 1
respond_to do |format|
format.html # Goes to the new.html.erb view.
format.xml { render :xml => #blog }
format.js { render :layout => false}
end
end
def create
#blog = Blog.new(params[:blog])
respond_to do |format|
if #blog.save
render :action => :show
else
flash[:notice] = "Error occurred while saving the blog entry."
render :action => :new
end
end
end
end
If anyone can help me with this I would greatly appreciate it. I'm still pretty new to ruby and the rails framework and couldn't solve the problem on my own by googling.
Thanks.
Have you seen this?
http://media.pragprog.com/titles/fr_arr/multiple_models_one_form.pdf
Change your form to this:
<% form_for :blog, :url => { :action => :create } do |blog_form| %>
<%= blog_form.text_field :title, :style => "width: 400px" %>
<% blog_form.fields_for :blog_pages do |page_fields| %>
<%= page_fields.text_area :content, :style => "width: 100%",
:cols => "10", :rows => "20" %>
<% end %>
<% end %>
If you use fields_for it iterates over blog_pages automaticaly. However I'm not sure if this caused errors.

Resources