How to Pass an array from form to controller? - ruby-on-rails

I have been following Brandon Tilley's instructions on creating a private message system and want to modify the way the recipient of a private message is passed (from check-box to text-box).
In the view I have this:
<%= f.label :to %><br />
<%= f.text_field :to, placeholder: "Recip...(separated by commas)" %>
How can I accept input as a text input that passes an array of integers to the controller?
Extra Details:
Full View:
<h1>New Conversation</h1>
<%= form_for(#conversation) do |f| %>
<div>
<%= f.label :to %><br />
<%= f.text_field :to, placeholder: "Recip...(separated by commas)" %>
</div>
<br />
<%= f.fields_for :conversation do |c| %>
<div>
<%= c.label :subject %>
<%= c.text_field :subject %>
</div>
<%= c.fields_for :messages do |m| %>
<div>
<%= m.label :body %><br />
<%= m.text_area :body %>
</div>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
Within the controller I have this:
def create
redirect_to users_path unless current_user
#conversation = UserConversation.new(params[:user_conversation])
#conversation.user = current_user
#conversation.conversation.messages.first.user = current_user
...
Within the model I have this:
accepts_nested_attributes_for :conversation
delegate :subject, :to => :conversation
delegate :users, :to => :conversation
attr_accessor :to
*before_create :create_user_conversations*
private
def create_user_conversations
return if to.blank?
to.each do |recip|
UserConversation.create :user_id => recip, :conversation => conversation
end
end
end
Edit: New Model:
def to
to.map(&:user_id).join(", ") *stack too deep error*
end
def to=(user_ids)
#to = user_ids.split(",").map do |recip|
UserConversation.create :user_id => recip.strip, :conversation => conversation
end

rails helpers are not set up to handle arbitrary array input auto-magically.
you can use multiple checkboxes or parse your text input which is a commaa separated list of user names. In your model
def to=(string)
#to = User.where(:name => string.split(',')).select(:id).map(&:id)
end
for a nicer user experience you can use tagsinput jquery plugin and/or autocomplete.
also note that if you use a form to modify this same object, you need to regenerate the comma separated string as :value option for the text_field input to correctly prepopulate your edit form.
<%= text_field_tag "user_conversation[to]", (#conversation.to || []).join(',') %>

In the view:
<%= f.label :to %><br />
<%= f.text_field :to, placeholder: "Recip...(separated by commas)" %>
In the model:
attr_accessible :to
attr_accessor :to
before_create :create_user_conversations
private
to.split(",").each do |recip|
UserConversation.create :user_id => recip, :conversation => conversation
end

Related

Rails 5 trouble with saving a nested fields_for .. error message <xxx> must exist

I have the following models:
class Person < ApplicationRecord
has_many :interests, dependent: :destroy
accepts_nested_attributes_for :interests
validates_presence_of :email
validates_inclusion_of :gender, :in => %w(M F), message: "Gender can only be in M or F"
has_secure_password
def name
"#{first_name} #{last_name}"
end
def interests_concatenated
interests.map { |i| i.interest }.join(", ")
end
end
class Interest < ApplicationRecord
belongs_to :person
end
My controller is as follows:
class PeopleController < ApplicationController
def index
#person = Person.all
end
def new
#person = Person.new
#person.interests.build
end
def create
#person = Person.new(people_params)
if #person.save
session[:user_id] = #person.id
redirect_to(people_path)
else
flash = "Email or gender can't be blank!"
render 'new'
end
end
private
def people_params
params.require(:person).permit(:email, :first_name, :last_name, :gender, :password,:password_confirmation, interests_attributes: [:hobby])
end
end
My form is as follows:
<%= form_for #person do |f| %>
<p>
<%= f.label :email %> <br>
<%= f.text_field :email %>
</p>
<p>
<%= f.label :first_name %> <br>
<%= f.text_field :first_name %>
</p>
<p>
<%= f.label :last_name %> <br>
<%= f.text_field :last_name %>
</p>
<p>
<%= f.label :gender %> <br>
<%= f.label(:gender_male, "Male") %>
<%= f.radio_button(:gender, "M") %> <br>
<%= f.label(:gender_female, "Female") %>
<%= f.radio_button(:gender, "F") %> <br>
</p>
<p>
<%= f.label :password %> <br>
<%= f.password_field :password %>
</p>
<p>
<%= f.label :password_confirmation %> <br>
<%= f.password_field :password_confirmation %>
</p>
<p>
<%= f.fields_for :interests do |i| %>
<%= i.label :hobby %>
<%= i.text_field :hobby %>
<% end %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
Here is the byebug console log when I run it:
Very stumped why it's not working. Could it be something to do with the parameters?
Here is the log file when I submit the form:
Instead of:
#interests = #person.interests.new
try
#interests = #person.interests.build
new creates a fresh, clean, completely empty new object... but build is the special Rails association method that will fill it with appropriate defaults (like, eg the right person_id)
I found a working solution by adding this in my interests model:
class Interest < ApplicationRecord
belongs_to :person, **optional: true**
end
Since #person fails to save each time, the biggest clue was in the error message "Interest person must exist", I found this StackOverflow solution to be helpful. Also this blog post on why this is needed was helpful in shedding light on the issue.
Thanks to everyone that weighed in on it!

How do I save a form in double nested form?

I have three models: Event, Workout, and Round.
A person can create a event, that includes a workout, and configure number of sets and weights through round.
I am currently using cocoon gem to create a nested form. I am able to use the form and save Event and Workout, however, Round is not being saved.
class Event < ActiveRecord::Base
has_many :workouts, dependent: :destroy
has_many :rounds, :through => :workouts
belongs_to :user
accepts_nested_attributes_for :workouts, :allow_destroy => true
end
class Workout < ActiveRecord::Base
belongs_to :event
has_many :rounds, dependent: :destroy
accepts_nested_attributes_for :rounds, :allow_destroy => true
end
class Round < ActiveRecord::Base
belongs_to :workout
belongs_to :event
end
I currently have my routes set like this.
Rails.application.routes.draw do
devise_for :users
root 'static_pages#index'
resources :events do
resources :workouts do
resources :rounds
end
end
In my controller, this is how I have my new methods.
New Method for Event
def new
#event = current_user.events.new
end
New Method for Workout
def new
#workout = Workout.new
end
New Method for Round
def new
#round = Round.new
end
I currently have the form under Events' view folder. Under show.html.erb of Events view file, I am trying to display Rounds as well by
<% #workout.rounds.each do |round| %>
<%= round.weight %>
<% end %>
But I am getting undefined method for rounds. Is it not possible to display round in Event view?
Thanks for the help!
Edit 1:
Here is my nested forms.
At the top, I have form for Event.
<%= simple_form_for #event do |f| %>
<%= f.input :title %>
<h3>Work Outs</h3>
<div id="workouts">
<%= f.simple_fields_for :workouts do |workout| %>
<%= render 'workout_fields', f: workout %>
<% end %>
<div class="links">
<%= link_to_add_association 'add workout', f, :workouts %>
</div>
</div>
<div class="field">
<%= f.label :start_time %><br>
<%= f.datetime_select :start_time %>
</div>
<div class="field">
<%= f.label :end_time %><br>
<%= f.datetime_select :end_time %>
</div>
<div class="field">
<%= f.label :description %><br>
<%= f.text_area :description %>
</div>
<div class="actions">
<%= f.submit "Create new Workout" %>
</div>
<% end %>
<% end %>
</form>
Then there is a form for Workout
<div class="nested-fields">
<%= f.input :name %>
<div class="rounds">
<%= f.simple_fields_for :rounds, :wrapper => 'inline' do |round| %>
<%= render 'round_fields', f: round %>
<% end %>
<div class="links">
<%= link_to_add_association 'add round', f, :rounds, :render_option => { :wrapper => 'inline' } %>
</div>
</div>
<%= link_to_remove_association "remove workout", f %>
</div>
And finally, I have the form for Round
<div class="nested-fields">
<table class="table round-table">
<tr>
<td>
<% index = 0 %>
<%= f.simple_fields_for :rounds do |round| %>
<%= render 'set_fields', {f: round, index: index} %>
<% index = index + 1 %>
<% end %>
</td>
<td>Previous Weight</td>
<td><%= f.input_field :weight %></td>
<td><%= f.input_field :repetition %></td>
<td><%= link_to_remove_association "remove rounds", f %></td>
</tr>
</table>
</div>
I am able to create round on rails console and save them. But when I use the form on the web, I cannot save them.
EDIT 2
This is currently how I have the event_params and workout_params set-up.
def event_params
params.fetch(:event, {}).permit(:title, :description, :start_time, :end_time, :workouts_attributes => [:id, :name, :category, :_destroy])
end
Then the workout_params:
def workout_params
params.require(:workout).permit(:name, :category, :rounds_attributes => [:id, :weight, :set, :repetition, :_destroy])
end
I am confused why the form would save Event and Workout. But Round always returns an empty array.
Finally solved it!
I had to have an association in the params as well. Double nested association.
def event_params
params.fetch(:event, {}).permit(:title, :description, :start_time, :end_time, :workouts_attributes => [:id, :name, :category, :_destroy])
end
For my event_params, I only had :workouts_attributes. I thought having :rounds_attributes in workout_params would be okay. But I needed to have rounds_attributes in event_params as well.
Fixing it like below fixed the issue.
def event_params
params.fetch(:event, {}).permit(:title, :description, :start_time, :end_time, :workouts_attributes => [:id, :name, :category, :_destroy, :rounds_attributes => [:id, :weight, :set, :repetition, :_destroy]])
end
you already have rounds attribute in Events model (has_many :rounds, :through => :workouts), why not use it?
<% #event.rounds.each do |round|
<%= round.weight %>
<% end %>

Rails 4 trouble with accepts_nested_attributes_for

I am new to rails and I'm trying the accepts_nested_attributes_for function. I am creating an inventory system and the accepts_nested_attributes_for feature is being used to attach multiple order details to an order. An order must also be associated with store location.
The problem I'm having is the order is being created but no data is being passed to the order details table.
My views are below:
Orders View
<h1>Place An Order</h1>
<%= form_for ([#location, #order]) do |f| %>
<p>
<%= f.label :customer_id %><br />
<%= f.text_field :customer_id %>
</p>
<p>
<h3>Items</h3>
<%= f.fields_for :order_details do |builder| %>
<%= render 'order_detail_fields', :f => builder %>
<% end %>
</p>
<p><%= link_to_add_fields "Add Item", f, :order_details %></p>
<p>
<%= f.submit %>
</p>
<% end %>
Order_details_fields Partial
<p class="fields">
<%= f.label :item_id %><br />
<%= f.text_field :item_id %></br>
<%= f.label :quantity %></br>
<%= f.text_field :quantity %></br>
<%= f.label :cost %></br>
<%= f.text_field :cost %></br>
<%= f.label :discount %><br />
<%= f.text_field :discount %><br />
<%= f.hidden_field :_destroy %>
<%= link_to_function "remove", "remove_fields(this)" %>
</p>
Orders Controller
class OrdersController < ApplicationController
def index
#orders = Order.all
end
def show
#order = Order.find(params[:id])
end
def new
#order = Order.new
#location = Location.find(params[:location_id])
end
def create
#location = Location.find(params[:location_id])
#order = #location.orders.create(order_params)
##order = #order.order_details.create
if #order.save
redirect_to #order
else
render :action => 'new'
end
end
private
def order_params
params.require(:order).permit(:customer_id, order_detials_attributes: [:id, :item_id, :quantity, :cost, :discount])
end
end
Orders Model
class Order < ActiveRecord::Base
belongs_to :location
has_many :order_details, :dependent => :destroy
accepts_nested_attributes_for :order_details, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
Order Details Model
class OrderDetail < ActiveRecord::Base
belongs_to :order
end
Routes
resources :locations do
resources :orders
end
resources :orders do
resources :order_details
end
Any help with this would be greatly appreciated
Build
Looks like everything is right to me - the only problem being the issue #Pavan outlined, which is that when you use accepts_nested_attributes_for, you have to build the associative object, so it can be used in the form:
#app/controllers/orders_controller.rb
Class OrdersController < ApplicationController
def new
#location = Location.find parmas[:id]
#order = Order.find params[:id]
#order.order_details.build
end
end
Although this looks like the only issue you have, there may be other problems (validation on the OrderDetail model as an example (which you don't have)
The only issue with what both I and Pavan have recommended is if you don't build your associative data, the fields_for don't show on the form. If your fields are showing, it may be a different issue, which will be highlighted in the params hash

undefined method `[]' for nil:NilClass error in rails

I have 2 conotrollers and 3 models:
Models:
problem.rb
class Problem < ActiveRecord::Base
has_many :problemtags
has_many :tags, :through => :problemtags
end
tag.rb
class Tag < ActiveRecord::Base
validate :name, :presence => true
has_many :problemtags
has_many :problems, :through => :problemtags
end
problemtag.rb
class Problemtag < ActiveRecord::Base
belongs_to :problem
belongs_to :tag
end
problems_controller.rb
class ProblemsController < ApplicationController
def new
#all_tags = Tag.all
#new_problem = #problem.problemtags.build
end
def create
params[:tags][:id].each do |tag|
if !tag.empty?
#problem.problemtags.build(:tag_id => tag)
end
end
end
def problem_params
params.require(:problem).permit(:reporter_id, :status, :date_time, :trace_code)
end
tags_controller.rb
//tags_controller is generate with scaffold
And I have below code in problems view:
new.html.erb
<%= fields_for(#new_problem) do |f| %>
<div class="field">
<%= f.label "All Tags" %><br>
<%= collection_select(:tags, :id, #all_tags, :id, {}, {:multiple => true}) %>
</div>
<% end %>
when I run the project, the problem's view is show, but when I complete the textfields and select tags and then click on submit button, I get below error:
NoMethodError in ProblemsController#create
undefined method `[]' for nil:NilClass
Extracted source (around line #22):
#problem = #reporter.problems.build(problem_params)
params[:tags][:id].each do |tag|
if !tag.empty?
#problem.problemtags.build(:tag_id => tag)
end
I do not understand the problem. any one can describe the problem to me?
As stated by your answers, your issue is that you're not sending the right data to your controller (and consequently params[:tags] will be blank):
Form
You're firstly missing the form_builder object in your collection_select (so your tags will likely not be sent inside the correct params hash). Although this may be by design, you need to ensure you're passing the data properly:
<%= fields_for(#new_problem) do |f| %>
<div class="field">
<%= f.label "All Tags" %><br>
<%= f.collection_select(:tags, :id, #all_tags, :id, {}, {:multiple => true}) %>
</div>
<% end %>
Params
Secondly, we cannot see your form or params hash. This is vital, as your form needs to look like this:
<%= form_for #variable do |f| %>
<%= f.text_field :value_1 %>
<%= f.text_field :value_2 %>
<% end %>
This creates a params hash like this:
params { "variable" => { "name" => "Acme", "phone" => "12345", "address" => { "postcode" => "12345", "city" => "Carrot City" }}}
This will be the core reason why your controller will return the [] for nil:NilClass error - you'll be referencing params which don't exist. You'll need to call params[:variable][:tags] as an example
If you post back your params hash, it will be a big help
You could try using validate :tag_id, :presence => true to check for presence of the needed params.
I found 2 problems in my code:
in new.index.html(in problem view), the submit button is in the form_for and I write the field_for outside the form_for and when I click on submit button, the params hash of tags didn't create.
In collection_select, I forgot to add the name parameter of tag.
Correct new.html.erb code:
<%= form_for #problem do |f| %>
status: <%= f.text_field :status %><br/>
datetime: <%= f.datetime_select :date_time %><br/>
trace code: <%= f.text_field :trace_code %><br/>
<%= fields_for(#new_problem) do |f| %>
<div class="field">
<%= f.label "All Tags" %><br>
<%= collection_select(:tags, :id, #all_tags, :id,:name, {}, {:multiple => true}) %>
</div>
<% end %>
<%= f.submit %>
<% end %>
Thanks for all of the answers.

rails getting value from view to conroller

Having an issue getting a value from a form to the controller. I am using rails 4.0.
My view looks like this (new.html.erb)
<h1> POST A NEW LISTING </h>
<% if current_user.nil? %>
<h2>You must be logged in to view this page </h2>
<% else %>
<%= form_for [#user, #listing] do |f| %>
<%= f.label :title, 'Title' %> <br />
<%= f.text_field :title %>
<%= f.label :general_info, 'General Information' %> <br />
<%= f.text_area :general_info %>
<%= f.label :included, 'Included' %> <br />
<%= f.text_field :included %>
<%= f.label :length, 'Length' %> <br />
<%= f.text_field :length %>
<%= f.label :price, 'Price' %> <br />
<%= f.text_field :price %>
<%= fields_for #tagging do |u| %>
<%= u.label :tag, 'Tag' %> <br />
<%= u.text_field :tag %>
<% end %>
<%= f.submit "submit" %>
<% end %>
<% end %>
I am trying to add tags. I have 2 models to handle the tags:
models -> tag.rb
class Tag < ActiveRecord::Base
has_many :taggings
has_many :listings, through: :taggings
end
models -> tagging.rb
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :listing
end
tags keep track of the tag names themselves, while taggings keeps track of the connection to the listings.
When a user submits the form they will type in a string tag such as: "exampletag". I then need to search my tag model to get the tag_id of that specific tag. If it exists I need to put the tag_id and listing_id into taggings. Currently I have the listing_id correct, but I am having a problem even accessing the :tag symbol from the form.
This is what I have so far. Not that currently :tag_id is hardcoded in because I cant get #current_tag to return the information I need.
listings_conroller.rb #create
def create
#user = User.find(current_user.id)
#listing = #user.listings.build(listing_params)
#save before we get the listing ID
if #listing.save
#current_tag = Tag.where(:name => params[:tag])
#taggings = Tagging.new(:tag_id => 1, :listing_id => #listing.id)
if #taggings.save
flash[:success] = "Success"
redirect_to root_path
else
render :action => 'new'
end
else
render :action => 'new'
end
end
I thought that #current_tag = Tag.where(:name => params[:tag]) would return the correct listing but it seems to be returning null when I submit the form with a name which is in the database.
got it!
Since tags is nested under taggings I needed to access the param as:
params[:tagging][:tag]
instead of params[:tag]

Resources