building nested resources for a form - ruby-on-rails-4

I have a class User which has sub classes IndividualUser and BusinessUser (through STI). IndividualUser and BusinessUser have a many-to-many association and are linked through a join table called Employees.
Also, a User can have an Account.
I'm trying to set up a nested form, and am struggling to create the required objects.
My models:
class User
has_one :account
end
class IndividualUser < User
has_many :business_users, :through => :employees
has_many :employees
end
class BusinessUser < User
has_many :individual_users, :through => :employees
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :business_user
belongs_to :individual_user
end
class Account
belongs_to :user
end
Now, in my controller, I am able to create a new account for an individual user:
# GET /individual_users/new
def new
#individual_user = IndividualUser.new
#individual_user.build_account
end
This works fine.
However, when I try to create a business_user, then individual_users, and then an account for each individual_user:
# GET /business_users/new
def new
#business_user = BusinessUser.new
#business_user.employees.build()
#business_user.employees.each do |employee|
#individual_user = employee.build_individual_user
#individual_user.build_account
end
end
I get the following error:
undefined method `build_account' for nil:NilClass
It appears as though #individual_user is not being created.
I'm not sure why I'm getting this error. Any ideas?
What's really strange, is that if I instead move the code to the view, it appears to work. i.e. in my view:
<% #business_user.employees.each do |employee| %>
<% employee.individual_user.build_scoot_account %>
<% employee.individual_user.scoot_account.id = 2 %>
<%= employee.individual_user.scoot_account.id %>
<% end %>
This displays "2".

Related

Need Simple Has_Many :through controller code

I'm learning to use Rails has_many :through association. Most of what I'm reading provides only how to set up the models, but not how to set up the controller actions. My app is very basic for the purposes of learning this topic. I have a form that lists some vertical industries. When a "vertical" is created, there is an option to select from a list of apps that apply to that vertical (check boxes). When the vertical gets created, the associations between the vertical and the selected apps should be established.
I have 3 models:
class App < ActiveRecord::Base
has_many :solutions
has_many :verticals, through: :solutions
end
class Vertical < ActiveRecord::Base
has_many :solutions
has_many :apps, through: :solutions
end
class Solution < ActiveRecord::Base
belongs_to :app
belongs_to :vertical
end
Here is my form:
<%= simple_form_for(#vertical) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<%= f.input :name %>
<%= f.input :description %>
<%= f.association :apps, as: :check_boxes %>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
Here is my verticals_controller create action:
def create
#vertical = Vertical.new(vertical_params)
#solutions = #vertical.apps.build(params[:app])
<respond_to code omitted for brevity>
end
def vertical_params
params.require(:vertical).permit(:name, :description, apps_attributes: [ :name, :description, :developer, :mpp, :partner, :website, :app_id[] ])
end
I am able to create the associations from the rails console this way:
vertical = Vertical.first
app = App.first
vertical.apps << app
But I don't think this is the right way to do it in the controller, nor do I understand how to get the app params that were selected in the form. I'm looking for some basic, clean code examples to follow that adhere to Rails best practices. Also, if you can point me to any recent tutorials that address controller code would be great. Thx.
I was able to get the associations to be created by making the following changes:
In my Create action in my controller:
def create
#vertical = Vertical.new(vertical_params)
#solutions = #vertical.apps.build
<respond_to code omitted for brevity>
end
In my secure parameters I have the following:
def vertical_params
params.require(:vertical).permit(:name, :description, app_ids:[])
end
I am not certain this is done to Rails best practices or not.

How to raise error when validation on nested attributes fails

Here are 2 models: customer and address. A customer has_one address.
class Customer < ActiveRecord::Base
has_one :address
accepted_nested_attributes_for :address, :allow_destroy => true
end
class Address < ActiveRecord::Base
belongs_to :customer
validates :add_line, :presence => true
end
<% simple_form_for #customer do |f| %>
.....
<%=f.simple_fields_for :address do |builder| %>
<%=render ('address', f: builder) %>
<% end %>
<%end %>
address view
<%=f.input :add_line %>
address is nested attribute in customer. The problem we are having is that if address is modified wrongly (ex, a nil add_line) within customer view, there is no error (#customer.update_attributes in customer controller) popping up. Is there a way setting up the nested attributes in such a way nil add_line will fail the update?
Two things caught my eye with your original post:
One,remember that you need a belongs_to :customer in the Address model.
Two, you need to add a validation in the Customer model
class Customer < ActiveRecord::Base
has_one :address
accepted_nested_attributes_for :address, allow_destroy: true, reject_if: :address_invalid
private
def address_invalid(attributes)
# add custom validation code here ...
end
end

Rails: How to use find_or_create_by in my nested model

Models: I have a Company model that has_many Tasks. Each Task has_one Employee.
Task model:
class Task < ActiveRecord::Base
belongs_to :company
has_one :employee, dependent: :destroy
accepts_nested_attributes_for :employee
validates_associated :noteholder
end
Employee model:
class Employee < ActiveRecord::Base
belongs_to :task
validates :name, presence: true, uniqueness: { case_sensitive: false }
end
Form: I created a nested form for Task that accepts_nested_attributes_for the Employee model. The form has an Employee name field with an autocomplete function that loads all Company.employees.
_form.html.erb form:
<%= simple_form_for(#task) do |f| %>
<%= f.simple_fields_for :employee do |employee| %>
<%= employee.input :name, input_html: { data: { autocomplete_source: #employees.pluck(:name).to_json } } %>
<% end %>
<%= f.button :submit, 'Save' %>
<% end %>
This saves a new record for each employee, regardless of whether or not an employee with that name exists
Desired behaviour:
If the user fills in a new Employee the model should create a new record, whereas if it would provide an existing Employee name, it should not.
My attempt: I thought the find_or_create_by method would be useful here, but have had no success with implementing it.
Question: How do I correctly set up my model sothat the Employee model only saves a new employee if the employee name does not yet exist?

Rails4 - Association has_many through - Reader_id

I would like with Rails 4 to do a simple association beetween 3 models but i can't succeed to do that.
First model:Reader,Second model:Comments Third model : Books
The relation beetween the 3 models are:
class Book < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :book
belongs_to :reader
class Reader < ActiveRecord::Base
has_many :comments
has_many :books, through: :comments
I have no problem for the relation Book has_many :comments but i have some problem with the relation Reader has_many :comments.
Field from CommentsTable : username, message and reader_id (for the has_many association).
Field from ReaderTable : name , email.
I have a file _form.html.erb in view:
<%= form_for [#book, #comment] do |f| %>
<%= f.hidden_field :book_id %>
<%= f.text_field :reader_id, :placeholder => "reader id" %>
<br />
<%= f.text_area :message %>
<br />
<%= f.submit %>
<% end %>
And an other file _comment.html.erb:
<% if comment.reader_id != nil %>
<li><strong><%= link_to comment.reader.name, comment.reader %></strong></li>
<p><%= comment.message %></p>
<% end %>
Like you see i have a form with reader_id and message. When the user will enter this 2 informations, it will appears the reader.name (thanks to reader_id) and the comment.message.
But i don't want that the user enter the integer "reader_id" (it has no sense) but directly the string "reader.name".
How can i do that, knowing that after i will do a link_to on the reader.name to comment.reader ?
Please help me.
Thank you.
I'm guessing the reader is the user. If it is, then you'll probably want to have a whole authentication system (maybe think about Devise) and just assign the comment to the current user within the controller. You'll also want to assign the book in the controller.
EDIT:
Devise will make available a current_user method in your controller. So your create method in your comments controller should look like this:
def create
comment = current_user.comments.new(comment_params)
end
You don't need to assign the reader.name to comments. That's what the association is for. You can do comment.reader.name, or better yet, use delegate (look that up) and do comment.reader_name.

Rails - use collection_select in a simple_form for a has_many_through association

I need help with a create form for a has_many_through association which also specifies a class name:
class Sponsor < ActiveRecord::Base
has_many :member_contacts
has_many :contacts, through: member_contacts, class_name: :member
accepts_nested_attributes_for :address, :contacts
end
class Contact < ActiveRecord::Base
has_many :member_contacts
has_many :sponsors, through: member_contacts
end
class MemberContact < ActiveRecord::Base
belongs_to :contact
belongs_to :sponsor
end
sponsors_controller.rb
def create
#sponsor = Sponsor.create(sponsor_params)
#sponsor.contacts.build
end
def sponsor_params
params.require(:sponsor).permit(:name,:website,:email,
:contact_first_name, :contact_surname, contacts_attributes: [], address_attributes: [:flat, :street, :postal_code, :city])
end
sponsor/_form.html.haml
= simple_form_for #sponsor do |f|
= f.association :contacts, collection: Member.all, label_method: :full_name
This is failing with the error 'unpermitted params, contact_ids', because
"contact_ids"=>["", "4", "5", "6"]
is being passed in the params hash. In the form I'd like a drop down list of all the members and the ability to select multiple members which will be saved against the sponsor as contacts.
How do I set up the contacts_attributes in sponsor_params in the controller and the collection_select simple_form helper in the view?
To get the form working, I added a foreign key to the sponsor class
has_many :contacts, through: member_contacts, class_name: 'Member', foreign_key 'member_id'
changed the strong params in the controller
def sponsor_params
params.require(:sponsor).permit(:name,:website,:email, contact_first_name, :contact_surname, contact_ids: [], address_attributes: [:flat, :street, :postal_code, :city])
end
and removed the association in the view, using collection_select
= f.collection_select :contact_ids, Member.all, :id, :full_name,
{ selected: #sponsor.contacts.map(&:id) }, { multiple: true }
You can set like this:
params.require(:sponsor).permit(:name,:website,:email, contact_ids: []...)
Note that permit(:contact_ids) will fail, but permit(contact_ids: []) works.