I have the following form for release, with fields for tracks being accepted as nested attributes within my release model.
<%= form_for(#release) do |f| %>
<%= f.hidden_field :user_id, :value => current_user.id, :class => "text" %>
<%= f.text_field :title, :class => "text" %>
<%= f.fields_for :tracks do |builder| %>
<%= render 'track_fields', :f => builder %>
<% end %>
<% end %>
My release model contains:
accepts_nested_attributes_for :tracks, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => :true
accepts_nested_attributes_for :releases_tracks
before_save :order_tracks
before_update :order_tracks
def order_tracks
releases_tracks.each { |t| t.position = track_attributes.position }
tracks.each { |t| t.user_id = user_id}
tracks.each { |t| t.label_id = label_id}
end
def track_attributes=(track_attributes)
track_attributes.each do |attributes|
tracks.build(attributes)
artists_tracks.build(attributes)
end
end
Everything works well, except the line below where i'm trying to take the position value entered in the fields_for part of the form. I can access values from the parent form, user_id for example, but how do I access the child values?
releases_tracks.each { |t| t.position = track_attributes.position }
Thanks all!
(Note: I don't want to use acts_as_list for this)
try to use:
releases_tracks.each { |t| t.position = track_attributes[:position] } or
releases_tracks.each { |t| t.position = track_attributes["position"] }
Related
I'm writing a quiz app with rails 5. I have got a multi-step form for question building.
Models:
class Mcq < ApplicationRecord
attr_accessor :option_count
has_many :options, dependent: :destroy
belongs_to :quiz
accepts_nested_attributes_for :options
validates :question_text, presence: true
end
class Option < ApplicationRecord
belongs_to :mcq, optional: true
validates :option_text, presence: true
end
Schema:
create_table "mcqs", force: :cascade do |t|
t.string "question_text"
t.boolean "required"
t.boolean "multiselect"
t.integer "quiz_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "options", force: :cascade do |t|
t.string "option_text"
t.integer "mcq_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The first page is for question setup and has the following fields:
Option Count
Required (Yes / No)
No of options that can be selected (Single / Multiple)
The second page is for options and has the following fields:
Question Text
Nested Form for Options
Controller:
class McqsController < ApplicationController
def new
session[:current_step] ||= 'setup'
session[:mcq_params] ||= {}
#current_step = session[:current_step]
#quiz = Quiz.find(params[:quiz_id])
#mcq = Mcq.new(session[:mcq_params])
if session[:current_step] == 'options'
#option_count = session[:mcq_params]['option_count']
#option_count.times { #mcq.options.build }
end
end
def create
if params[:previous_button]
session[:current_step] = 'setup'
redirect_to new_quiz_mcq_path
elsif session[:current_step] == 'setup'
save_session(params[:mcq])
redirect_to new_quiz_mcq_path
elsif session[:current_step] == 'options'
#mcq = Mcq.new(whitelisted_mcq_params)
#mcq.quiz_id = params[:quiz_id]
#quiz = Quiz.find(params[:quiz_id])
if #mcq.save
session[:current_step] = session[:mcq_params] = nil
redirect_to quiz_new_question_path(#mcq.quiz_id)
else
#current_step = session[:current_step]
render :new
end
end
end
private
def whitelisted_mcq_params
params.require(:mcq)
.permit(:question_text, :multiselect, :required, options_attributes: [:option_text])
end
def save_session(mcq_params)
session[:mcq_params][:option_count] = mcq_params[:option_count].to_i
session[:mcq_params][:required] = mcq_params[:required]
session[:mcq_params][:multiselect] = mcq_params[:multiselect]
session[:current_step] = 'options'
end
end
The above solution works, but the code is messy and difficult to understand. I came across this railscasts episode which does something similar in a cleaner way. I've updated my code as follows:
class Mcq < ApplicationRecord
has_many :options, dependent: :destroy
belongs_to :quiz
attr_writer :current_step
attr_accessor :option_count
accepts_nested_attributes_for :options
validates :question_text, presence: true
def current_step
#current_step || steps.first
end
def steps
%w[setup options]
end
def next_step
self.current_step = steps[steps.index(current_step)+1]
end
def previous_step
self.current_step = steps[steps.index(current_step)-1]
end
def last_step?
current_step == steps.last
end
end
class McqsController < ApplicationController
def new
session[:mcq_params] ||= {}
#quiz = Quiz.find(params[:quiz_id])
#mcq = Mcq.new(session[:mcq_params])
#mcq.current_step = session[:mcq_step]
end
def create
#quiz = Quiz.find(params[:quiz_id])
session[:mcq_params].deep_merge!(params[:mcq]) if params[:mcq]
#mcq = Mcq.new(session[:mcq_params])
#option_count = session[:mcq_params]['option_count']
#option_count.times { #mcq.options.build }
#mcq.quiz_id = params[:quiz_id]
#mcq.current_step = session[:mcq_step]
if params[:previous_button]
#mcq.previous_step
elsif #mcq.last_step?
#mcq.save if #mcq.valid?
else
#mcq.next_step
end
session[:mcq_step] = #mcq.current_step
if #mcq.new_record?
render "new"
else
session[:mcq_step] = session[:mcq_params] = nil
redirect_to edit_quiz_path(#mcq.quiz_id)
end
end
end
But each time the second page is shown, the no of fields for options doubles or in case of invalid entry only the field for question_text is shown. How do I show the options correctly? Should I just go with my first solution? I'm new to rails and don't know much about the best practices.
Edited :
new.html.erb
<div class="sub-heading">Add a Multiple Choice Question:</div>
<%= render "mcq_#{#mcq.current_step}", quiz: #quiz, mcq: #mcq %>
_mcq_setup.html.erb
<div class="form-container">
<%= form_for [quiz, mcq] do |f| %>
<div class="form-row">
<div class="response-count">How many options should the question have?</div>
<%= f.select(:option_count, (2..5)) %>
</div>
<div class="form-row">
<div class="response-count">How many options can be selected?</div>
<div class="option">
<%= f.radio_button :multiselect, 'false', checked: true %>
<%= f.label :multiselect, 'Just One', value: 'false' %>
</div>
<div class="option">
<%= f.radio_button :multiselect, 'true' %>
<%= f.label :multiselect, 'Multiple', value: 'true' %>
</div>
</div>
<div class="form-row">
<div class="response-count">Is the question required?</div>
<div class="option">
<%= f.radio_button :required, 'true', checked: true %>
<%= f.label :required, 'Yes', value: 'true' %>
</div>
<div class="option">
<%= f.radio_button :required, 'false' %>
<%= f.label :required, 'No', value: 'false' %>
</div>
</div>
<%= f.submit "Continue to the Next Step" %>
<% end %>
</div>
_mcq_options.html.erb
<%= form_for [quiz, mcq] do |f| %>
<%= f.label :question_text, 'What is your question?' %>
<%= f.text_field :question_text %>
<%= f.fields_for :options do |option_fields| %>
<%= option_fields.label :option_text, "Option #{option_fields.options[:child_index] + 1}:" %>
<%= option_fields.text_field :option_text %>
<% end %>
<%= f.hidden_field :multiselect %>
<%= f.hidden_field :required %>
<%= f.submit "Add Question" %>
<%= f.submit "Back to previous step", name: 'previous_button' %>
<% end %>
You may look in direction of state_machine. By using that you can use your steps as states of state machine and use its ability to define validations that only active for given states (look here in state :first_gear, :second_gear do) so fields that required on second step will be not required on first. Also, it'll allow you to avoid complex checks for the step you currently on (because the state will be persisted in a model) and will be pretty easy to extend with more steps in a future.
I'm trying to associate a model Thing with another Thing on my things/new form. Each Thing has_many :things through a join table :related_things.
When I submit the form, I get this error:
NoMethodError in ThingsController#create
undefined method `each' for "1":String
Where did I go wrong with my code?
Thing model: I put asterisks around the line with the error message.
class Thing < ActiveRecord::Base
has_many :related_things
has_many :things, :through => :related_things
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "30x30!" }, :default_url => "/images/:style/missing.png"
validates_attachment_content_type :avatar, :content_type => /\Aimage\/.*\Z/
def related_things
related_thing_ids = RelatedThing.
where("thing_a_id = ? OR thing_b_id = ?", self.id, self.id).
map { |r| [r.thing_a_id, r.thing_b_id] }.
flatten - [self.id]
Thing.where(id: related_thing_ids)
end
def related_thing_ids=(ids)
***ids.each do |id|***
record = RelatedThing.where(thing_a_id: self.id, thing_b_id: id).first
record ||= RelatedThing.where(thing_a_id: id, thing_b_id: self.id).first
record ||= RelatedThing.create!(thing_a_id: self.id, thing_b_id: id)
end
end
end
RelatedThing model:
class RelatedThing < ActiveRecord::Base
has_many :things
end
Things controller:
class ThingsController < ApplicationController
def show
#thing = Thing.find(params[:id])
#related_thing = RelatedThing.all
#thing.things.build
end
def new
#thing = Thing.new
#things = Thing.all
end
def create
#thing = Thing.new(thing_params)
if #thing.save
redirect_to #thing
else
render 'new'
end
end
private
def thing_params
params.require(:thing).permit(:name, :avatar, :related_thing_ids)
end
end
Things/new.html.erb:
<h1>Add Something!</h1>
<p>
<%= form_for #thing, :url => things_path, :html => { :multipart => true } do |f| %>
<%= f.text_field :name, :placeholder => "Name of the thing" %>
<br>
<%= f.label :related_things %>
<%= f.collection_select :related_thing_ids, Thing.all, :id, :name %>
<br>
<%= f.label :display_picture %>
<%= f.file_field :avatar %>
<br>
<%= f.submit "Submit", class: "btn btn-primary" %>
<% end %>
</p>
Schema.rb:
ActiveRecord::Schema.define(version: 20141016190146) do
create_table "related_things", force: true do |t|
t.integer "thing_a_id"
t.integer "thing_b_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "things", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
end
end
I'm using Rails 4.0.10.
Try Array(ids).each ..., which convert any object in an array and respond to :each
~ (main) > Array(nil)
=> []
~ (main) > Array([])
=> []
~ (main) > Array('')
=> [""]
~ (main) > Array(1)
=> [1]
See Kernel#Array
I have an events model that has an attribute called location that is user defined. I want to create a list of those locations sorted by their counts. How do I grab all the values of an attribute?
**EDIT**
Events Controller
def index
#tags = Event.tag_counts.order('count DESC').limit(12)
//code
end
How I listed my other attribute tags w/ acts_as_taggable
<div class="sbody sbody-2">
<ul>
<% #tags.each do |tag| %>
<li>
<%= link_to(:controller => "events", :action => "index", :search => tag.name) do %>
<i class="icon-tag icon-white"></i> <%= tag.name.titleize %>
<% end %>
</li>
<% end %>
</ul>
</div>
Event Model
class Event < ActiveRecord::Base
belongs_to :member
attr_accessible :blurb, :details, :category, :tags, :video, :website, :name, :avatar, :banner, :tag_list, :location, :address,
:city, :zipcode, :state, :country, :start_date, :end_date, :start_time, :end_time
validates :location, presence: true,
length: {
maximum: 40,
message: 'must not be more than 40 characters.',
minimum: 2,
message: 'must be longer than 2 characters.'
}
end
Schema
class CreateEvents < ActiveRecord::Migration
def change
create_table :events do |t|
t.references :member
t.text :category
t.text :tags
t.text :website
t.text :video
t.text :details
t.text :blurb
t.text :name
t.timestamps
end
add_index :events, :member_id
add_attachment :events, :banner
add_attachment :events, :avatar
end
end
locations_count = Hash.new{0} // Create Hash to store location counts (initialize counts to 0)
Event.all.each { |event| locations_count[event.location] += 1 } // Iterate through each Event and increment the count for its location
locations_count.sort_by { |key, value| value } // Sort location counts by the value
I have a form on index.html.erb (User views):
<%= form_tag( '', :method => :get ) do %>
<% #company = Position.all.map { |p| [ p.company, p.company ] } %>
<%= select_tag "company", options_for_select((#company), params[:position_id]), { :include_blank => true, :reject_if => #company.blank? } %>
<% #industry = Position.all.map { |p| [ p.industry, p.industry ] } %>
<%= select_tag "industry", options_for_select((#industry), params[:position_id]), { :include_blank => true, :reject_if => #industry.blank? } %>
<%= submit_tag 'Filter', class: 'btn btn-large btn-primary' %>
<% end %>
and a controller (User controller):
def index
if params[:company] && params[:industry]
#users = User.companies(params[:company]).industries(params[:industry])
elsif params[:company]
#users = User.companies(params[:company])
elsif params[:industry]
#users = User.companies(params[:industry])
else
#users = User.all
end
end
A User has many companies and industries through positions:
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :first_name, :last_name, :position_ids
has_many :positions
has_many :companies, :through => :positions
has_many :industries, :through => :positions
has_one :current_position, :class_name => "Position", :conditions => { :is_current => true }
scope :companies, lambda { |*company| {:include => :positions, :conditions => ["positions.company = ?", company]} }
scope :industries, lambda { |*industry| {:include => :positions, :conditions => ["positions.industry = ?", industry]} }
end
Despite the if statement in my user controller, I cannot get my view to ignore blank entries in either the company or industry field. For instance, a blank company and "Internet" industry filter returns this url:
...users?utf8=%E2%9C%93&company=&industry=Internet&commit=Filter
how do I modify my code to ignore a blank company field so that the url excludes 'company=&' entirely? In this case, I get the returned results that I want:
...users?utf8=%E2%9C%93&industry=Internet&commit=Filter
thanks!
What you need to do is check to see if the parameters are blank.
if !params[:company].blank? && !params[:industry].blank?
#users = User.companies(params[:company]).industries(params[:industry])
elsif !params[:company].blank? && params[:industry].blank?
#users = User.companies(params[:company])
elsif !params[:industry].blank? && params[:company].blank?
#users = User.companies(params[:industry])
else
#users = User.all
end
When you submit the form, the variables are being set. Just that, they have blank values. So you need to check if they are blank instead of whether they exist.
After getting my question solved by Matteo Alessani in Rails - Id can't be found in Forms, I noticed that my form isn't saving the fields I pass.
I will copy here all the piece of code I have from the other question:
Routes:
resources :honors
Model:
class Honor < ActiveRecord::Base
belongs_to :person, :class_name => 'Person', :foreign_key => "person_id"
belongs_to :honored, :class_name => 'Person', :foreign_key => "honored_id"
belongs_to :group, :class_name => 'Group', :foreign_key => "group_id"
Controller:
def new
#person = Person.find(params[:person])
#honored = Person.find(params[:honored])
#group = Group.find(params[:group_id])
#honor = Honor.new
end
def create
#person = Person.find(current_person)
#honor = Honor.save(:group_id => params[:honor][:group],
:person_id => params[:honor][:person],
:honored_id => params[:honor][:honored])
if #honor.valid?
flash[:success] = "Honor created."
redirect_to (:back)
else
redirect_to (:back)
end
end
In the view:
<% #asked_groupmembership.each do |agm| %>
<%= link_to "Create Honor", new_honor_path(:group_id => #group.id,
:person => current_person.id, :honored => agm.member.id) %>
My Forms:
<% form_for #honor do |f| %>
<%= f.hidden_field :group_id, :value => #group.id %>
<%= f.hidden_field :person, :value => current_person.id %>
<%= f.hidden_field :honored, :value => #honored.id %>
<div class="field">
<%= f.label :texto %><br />
<%= f.text_field :texto %>
</div>
And the error is that I can get the ID's from group and person and the honored one, but nothing that I type in the forms (my attributes are in portuguese so I won't translate):
INSERT INTO "honors" ("group_id", "person_id", "honor_id", "texto", "nota",
"nivel_habilidade", "comprometimento", "tempo_demora",
"criatividade", "organicazao", "comunicacao", "trabalho_grupo", "created_at",
"updated_at") VALUES (39, 2, 44, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, '2011-05-26 12:58:56.433510', '2011-05-26 12:58:56.433510')
RETURNING "id".
Note: the Parameters in log are with the values.
Thanks!
You have mistake in controller
def create
#person = Person.find(current_person)
#honor = Honor.new(params[:honor])
if #honor.save
flash[:success] = "Honor created."
redirect_to (:back)
else
redirect_to (:back)
end
end