Rails creating a collection without an association - sql

What is the best and correct way to setup a simple_forms_for field that contains a collection for a select field but the values to be contained in the select need to be sourced from a model that does not have a direct association to the calling fields model?
For example I have a simple_forms_for form like follows:
<%= simple_form_for(#customer) do |f| %>
<%= f.error_notification %>
<fieldset>
<div class="form-inputs">
<%= f.input :code, required: true %>
<%= f.input :name, required: true %>
<%= f.input :location, required: true %>
<%= f.input :service_level %>
<%= f.input :golive_date, :as => :date_picker %>
<%= f.input :connection_type %>
<%= f.input :service_centre %>
<%= f.input :end_date, :as => :date_picker %>
<%= f.input :sla %>
<%= f.input :project_code %>
</div>
<div class="form-actions">
<%= f.button :submit, :class => "btn btn-primary" %>
</div>
</fieldset>
<% end %>
I want to make the :service_level field a selection field and add a collection to it, however the table that stores the lookup values is not associated with the Customer table for the form.
class Lookup < ActiveRecord::Base
validates_presence_of :name, :description
has_many :lookup_values
accepts_nested_attributes_for :lookup_values, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
class LookupValue < ActiveRecord::Base
belongs_to :lookup
end
class CreateLookups < ActiveRecord::Migration
def change
create_table :lookups do |t|
t.string :name
t.string :description
t.timestamps
end
end
end
class CreateLookupValues < ActiveRecord::Migration
def change
create_table :lookup_values do |t|
t.integer :lookup_id
t.string :name
t.string :value
t.timestamps
end
end
end
I basically want to be able to populate the values of the select using the following SQL query:
select v.name||' - '||v.value
from lookup_values v,
lookups l
where v.lookup_id = l.id
and l.name = 'Service level';
The actual value that is saved into the :service_level field needs to be the value of v.name.
All of the collections examples I have seen only appear to show how to create selects based on models that have an association between them, just wondering if there is an easy way to achieve this without an association.

Ok, well this is embarrassing...
Simple solution was to modify the _form.html.erb view file so that the :service_level field reads as:
<%= f.input :service_level, :collection => LookupValue.joins(:lookup).where(lookups: { :name => 'Service level' }).pluck(:name) %>
I probably need to make this more DRY when repeating for multiple lookup values in the form.
Any ideas how I can enhance this code to:
Remove the blank value that is listed in the select field drop down?
Modify the value text that displays in the select drop down to be
Name ||' - '||value. For example show the values in the format
"L1 - Level 1". The actual value selected and saved would need to
remain as "L1" (the :name value)

Related

Rails nested form on HABTM: how to prevent duplicate entry?

I have simple app with 3 tables in DB and 'many-to-many' relationships.
# Model Employee
class Employee < ActiveRecord::Base
has_and_belongs_to_many :phonenumbers
accepts_nested_attributes_for :phonenumbers, :allow_destroy => true
attr_accessible :last_name, :first_name, :middle_name, :phonenumbers_attributes
end
# Model Phonenumber
class Phonenumber < ActiveRecord::Base
has_and_belongs_to_many :employees
attr_accessible :number
accepts_nested_attributes_for :employees
end
I have 'employees_phonenumbers' join-table with 'employee_id' and 'phonenumber_id' columns.
# View
<%= form_for #employee, :url => { :action => :create } do |f| %>
<%= f.label "Last name" %>
<%= f.text_field :last_name %>
<%= f.label "First name" %>
<%= f.text_field :first_name %>
<%= f.label "Middle name" %>
<%= f.text_field :middle_name %>
<%= f.fields_for :phonenumbers do |phonenumber| %>
<%= phonenumber.label "Phone number" %>
<%= phonenumber.telephone_field :number %>
<% end %>
<%= f.submit "Create" %>
<% end %>
# Controller
def create
#employee = Employee.new(params[:employee])
#employee.save ? (redirect_to :action => :index) : (render "new")
end
Now if I create a user: 'John' with phone number '555', it's OK.
But if I want to create a user 'Larry' with the same phone number '555', there's a dublicate of '555' entry in the DB.
How do I prevent this?
UPDATE: My logic is: If there is number '555', then do not create a new one, use existing. If there is no such a number, then create a new one and use it.
in employee.rb:
before_save :get_phonenumbers
def get_phonenumbers
self.phonenumbers = self.phonenumbers.collect do |phonenumber|
Phonenumber.find_or_create_by_number(phonenumber.number)
end
end
I have found its working
You can use rails validation to check uniqueness of record.
In your model phonenumber.rb put following line,
validates_uniqueness_of :column_name
It will ensure that Phonenumber will have unique phone_numbers only.
Now in controller you can check phone_number from params and if number is already exist then we will delete nested attributes fron params so that Phonenumber record wont generate.
def create
#phone_number = Phonenumber.where(:number=>params[:employee][:phonenumber][:number])
if #phone_number.any?
params[:employee].delete(:phonenumber)
#employee = Employee.new(params[:employee])
if #employee.save?
#employee.phonenumber = #phone_number.first
redirect_to :action => :index
else
render "new"
end
else
#employee = Employee.new(params[:employee])
#employee.save ? (redirect_to :action => :index) : (render "new")
end
end

HABTM Checkboxes using simpleform gem

I have three models, and i am trying to save onto a third table using simple form gem and checkboxes.
class Work < ActiveRecord::Base
has_many :skills, through: :skillships
end
The second model is
class Skill < ActiveRecord::Base
has_many :works, through: :skillships
end
The third is
class Skillship < ActiveRecord::Base
belongs_to :work
belongs_to :skill
end
Using the Work model i am trying to save the data on the skillship table. Something similar to this http://railscasts.com/episodes/17-habtm-checkboxes-revised. Can you please help.
EDIT
The form
<%= simple_form_for(#work) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :title, :label => 'Project Title' %>
<%= f.input :excerpt, :as => :text %>
<fieldset>
<legend>Skills Used </legend>
Would like to check the skills i used here.
</fieldset>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
I tried..
<%= hidden_field_tag "work[skill_ids][]", nil %>
<% Skill.all.each do |skill| %>
<%= check_box_tag "work[skill_ids][]", skill.id, #work.skill_ids.include?(skill.id), id: dom_id(skill) %>
<%= label_tag dom_id(skill), skill.title %><br>
<% end %>
The reason i'm doing this it because work can have many skills used.
I was going about this the wrong way. A join table solved the problem.
rails g migration create_skills_works_table
Then
class CreateSkillsWorksTable < ActiveRecord::Migration
def self.up
create_table :skills_works, :id => false do |t|
t.references :skill
t.references :work
end
add_index :skills_works, [:skill_id, :work_id]
add_index :skills_works, [:work_id, :skill_id]
end
def self.down
drop_table :skills_works
end
end
Using simple form on the work view.
<fieldset>
<legend>Skills Used </legend>
<%= f.association :skills %>
</fieldset>

Rails nested form with multiple entries

I have a Sezzion model:
attr_accessible :description
has_many :session_instructors, :dependent => :destroy
has_many :instructors, :through => :session_instructors
accepts_nested_attributes_for :session_instructors
accepts_nested_attributes_for :instructors
Instructor model:
attr_accessible :bio
has_many :sezzions, :through => :session_instructors
has_many :session_instructors, :dependent => :destroy
SessionInstructor model:
attr_accessible :instructor_id, :sezzion_id
belongs_to :sezzion
belongs_to :instructor
Lastly, User model:
has_many :sezzions
has_many :instructors
I'm trying to create a form for Sezzion with nested form for SessionInstructor which has multiple select option for Instructors.
How can I do the following:
nested form for SessionInstructor
use multiple select option to get all the selected Instructor's instructor_id
hidden field to pass in the created/updated session_id with each select instructor
I have the following code as of now:
<%= form_for(#sezzion) do |f| %>
<%= f.label :description %>
<%= f.text_area :description %>
<%= f.label :instructors %>
<%= fields_for :session_instructors do |f| %>
<select multiple>
<% current_user.instructors.each do |instructor| %>
<option><%= instructor.name %></option>
<% end %>
</select>
<% end %>
<%= f.submit %>
<% end %>
Thank you so much!
This is something that seems ridiculously hard in Rails.
I think something like this might work:
<%= f.fields_for :session_instructors do |si| %>
<%= si.collection_select :instructor_ids, current_user.instructors, :id, :name, multiple: true>
<% end %>
This should create a form element which will set sezzion[session_instructors_attributes][instructor_ids].
Although I'm not sure if that's actually what you want. I've never tried this using a multi select. If it doesn't work, you could also try getting rid of the fields_for and just using f.collection_select. If you're willing to use a checkbox, I can show you how to do that for sure.
I hope that helps.
Edit:
Here's how I would usually do it with a check_box:
<%= f.fields_for :session_instructors do |si| %>
<%= si.hidden_field "instructor_ids[]" %>
<% current_user.instructors.each do |i| %>
<%= si.check_box "instructor_ids[]", i.id, i.sezzions.include?(#sezzion), id: "instructor_ids_#{i.id}" %>
<%= label_tag "instructor_ids_#{i.id}", i.name %>
<% end %>
<% end%>
There are a couple "gotchas!" with this method. When editing a model, if you deselect all checkboxes then it won't send the parameter at all. That's why the hidden_field is necessary. Also, you need to make sure each form element has a unique id field. Otherwise only the last entry is sent. That's why I manually set the value myself.
I copy pasted and then edited. Hopefully I got the syntax close enough where you can get it to work.
FINAL EDIT:
Per Sayanee's comment below, the answer was a bit simpler than I thought:
<%= f.collection_select :instructor_ids, current_user.instructors, :id, :name, {}, {:multiple => true} %>
#Sayanee, can you post how your instructors, sezzions table look like. Also for note, you can not get instructor_ids from Instructor object, hence you are getting "undefined method" error. With the current association that you shared, you can get instructor_ids from a Sezzion object. So you need to loop through current_user.sezzions in stead of current_user.instructors.
This is a way to implement fields_for nested form with html multiple_select in case of a has_many :through association. Solved it by doing something like this. The form view:
<%= form_for(#sezzion) do |f| %>
...
<%= fields_for :session_instructors do |g| %>
<%= g.label :instructor, "Instructees List (Ctrl+Click to select multiple)" %>:
<%= g.select(:instructor_id, Instructor.all.collect { |m| [m.name, m.id] }, {}, { :multiple => true, :size => 5 }) %>
<%= g.hidden_field :your_chosen_variable_id, value: your_chosen.id %>
<% end %>
...
<%= f.submit %>
<% end %>
Note:Since the #sezzion would not be saved at the time of generating the form you cannot pass that id (#sezzion.id) in place of your_chosen.id through the form. You could handle that save in the controller.
Make sure that your controller Initializes the Variables while generating the form: Your def new could look something like this:
def new
#sezzion = Sezzion.new
#sezzion.session_instructor.build
#sezzion.instructors.build
end
Now the create controller has to be able to accept the strong params required for the multiple select, so the sezzion_params method may look something like this:
def sezzion_params
params.require(:sezzion).permit(:description, :any_other_fields,
:session_instructors_attributes =>
[:instructor_id => [], :your_chosen_id => Integer])
end
In the create function, the first session_instructor variable is linked to the #sezzion instance variable through our new function. The other session_instructors in our multiple select must be built after the Sezzion instance is saved, if you want to pass in the created #sezzion.id with each select instructor. .
def create
#sezzion = Sezzion.new(sezzion_params)
#startcount=1 #The first element of the array passed back from the form with multiple select is blank
#sezzion.session_instructors.each do |m|
m.instructor_id = sezzion_params["session_instructors_attributes"]["0"]["instructor_id"][#startcount]
m.your_chosen_variable_id = your_chosen.id
#startcount +=1
end
if #sezzion.save
sezzion_params["session_instructors_attributes"]["0"]["instructor_id"].drop(#startcount).each do |m|
#sezzion.session_instructors.build(:instructor_id => sezzion_params["session_instructors_attributes"]["0"]["instructor_id"][#startcount],
:your_chosen_variable_id => your_chosen.id).save
#startcount += 1
end
flash[:success] = "Sezzion created!"
redirect_to root_url
else
flash[:danger] = "There were errors in your submission"
redirect_to new_sezzion_path
end
end

Hidden Field in Simple Nested Form Not Being Submitted

Environment: Rails 3.1.0, Ruby 1.9.2
I have Portfolio model which has_many Positions which has_one Asset.
This is the schema for the Position model:
create_table "positions", :force => true do |t|
t.integer "portfolio_id"
t.integer "asset_id"
t.decimal "holding_percentage"
end
When the user creates a portfolio he/she should enter the portfolio name and then add positions by adding stock tickers. Jquery does its stuff and shows the full name of the asset and also inserts the asset_id into the hidden field.
I am using both nested_form and simple_form as follows:
<%= simple_nested_form_for #portfolio do |f| %>
<%= f.input :name, :placeholder => 'Your portfolio name' %>
<%= f.fields_for :positions do |position_form| %>
<%= text_field_tag 'asset-ticker', nil, :class => 'asset-ticker' %>
<span class="asset-name"></span>
<%= position_form.text_field :holding_percentage, :class => 'asset-perc' %>
<%= position_form.hidden_field :asset_id, :class => 'asset-num', :as => :hidden %>
<%= position_form.link_to_remove "Remove this position", :class => 'asset-rem-link' %>
<% end %>
<p><%= f.link_to_add "Add a Position", :positions, :class => 'asset-add-link' %></p>
<%= f.button :submit %>
<% end %>
The problem is that the asset_id value in the hidden field is not being submitted. The parameters look as follows:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"hmvoGHF9GzpPsohQQ2MwhWk4FzhVVrf+IqoChHgftEs=",
"portfolio"=>{"name"=>"needhelpnow",
"positions_attributes"=>
{"new_1316730954406"=>{"holding_percentage"=>"11", "asset_id"=>"", "_destroy"=>"false"},
"new_1316730961085"=>{"holding_percentage"=>"22", "asset_id"=>"", "_destroy"=>"false"},
"new_1316730971587"=>{"holding_percentage"=>"33", "asset_id"=>"", "_destroy"=>"false"}}},
"commit"=>"Create Portfolio"}
It turns out that the problem was that I was writing in the value of the hidden field with:
('.asset-num').html(data.id)
Instead of:
('.asset-num').val(data.id)

Formtastic nested form field not building has_one association?

Given a User who can possibly be an Artist:
class User < ActiveRecord::Base
has_one :artist
end
I've got a User & Artist nested form (using Formtastic gem):
<h1>Artist registration</h1>
<% #user.build_artist unless #user.artist %>
<%= semantic_form_for #user, :url => create_artist_path do |f| %>
<%= f.inputs :username %>
<%= f.semantic_fields_for :artist do |a| %>
<%= a.input :bio %>
<% end %>
<%= f.buttons do %>
<%= f.commit_button 'Register as Artist' %>
<% end %>
<% end %>
The problem is the :artist fields are not rendered.
I've also tried f.inputs :for => :artist do |a|.
For some reason, using #user.build_artist does not display the artist's fields in the form. If I try #user.artist = Artist.new I get an error, because it tries to save the Artist and validation fails.
How should I initialize the Artist model so I get the benefit of formtastic generators in a nested form? (Note that #user here is not a :new_record?)
Did you remember to set accepts_nested_attributes_for :artist in user.rb?