nested attribute object is created three times instead of one - ruby-on-rails

I have three models, booking, room and travellers.
Booking has many rooms
room has many travellers
Since I'm doing a multi-step wizard booking and rooms gets created first, the travellers are created later in the update action.
This is the log for the update action: http://pastie.org/private/it7onlg8bnurqkgv6mptrq
And this is the relevant methods and actions for creating the travellers:
The view action
def step3
#booking = Booking.find_by_random_url_key(params[:id])
#variant = #booking.variant
#booking.rooms.collect {|room| room.number_of_persons.times {room.travellers.build} if room.travellers.blank?}
render :partial => "bookings/partials/step3", :layout => "booking"
end
room.number_of_persons method just return an int.
Relevant part of the update action
..
elsif #booking.update_attributes(params[:booking]) && #booking.aasm_state == "step3"
redirect_to booking_step4_url(#booking)
#booking.next!
..
next! is just a aasm transition
If I do a create in the console
Room.last.travellers.create(:forename => "john", :country_name => "Germany")
Only one object is created and even if I go back in the view and submit again he correctly updates the created object and does not create new ones.

#booking.rooms.collect {|room| room.number_of_persons.times {room.travellers.build} if room.travellers.blank?}
This looks wrong to me. You're initiating travellers but not storing them and collecting an array but not doing anything with it. Also, I understand you want to create a number of travellers but you condition on travellers being blank.
#travellers = #booking.rooms.collect { |room| room.number_of_persons.times { room.travellers.build } }

I got this to work by doing a create in the model instead of doing it through the forms.
So basically after the rooms are created I do:
self.rooms.map {|r| r.number_of_persons.times {r.travellers.create}}

Related

How do I enter a record for every user where condition is true in Rails

So I have app that has political candidates.
When a new political candidate is entered, I want to enter a notification into the notifications table for every user that's state is equal to the state of the new candidate being entered.
Ultimately, I want to enter in records to the notification table for every single user where that condition is met.
I know I'm way off, but here's where I'm at now. I'm trying to loop through each user and then enter this record when that condition is true.
def create
#candidate = Candidate.new(candidate_params)
if #candidate.save
User.each do |u|
if Candidate.state == User.state
#notification = Notification.new(:message => 'Message', :user_id => U.id)
#notification.save
else
end
end
else
render('new')
end
end
The candidate is created with this code, but the notifications aren't working. Basically I have two users where their state equals "Arizona" and I would expect if I create a new candidate where the state is "Arizona" that I should get two record into notifications, one with each user ID.
I think you got a bit mixed up between classes and instances. Here's the relevant bit:
#candidate = Candidate.new(candidate_params)
...
User.each do |u|
if Candidate.state == User.state
...
end
end
In your code Candidate is a class, and #candidate holds the recently created instance of a Candidate. Likewise, User is a class and u holds a User instance (on each loop iteration). Your comparison should actually use the instances rather than the classes:
if #candidate.state == u.state
Having sorted that, it's worth noting that your code has a couple of other errors -- User.each won't work. You need to specify a selector to get a list of User objects to loop through. One way would be to call User.all.each (which looking at your code is probably what you were trying). That pulls all User objects. But, since users can be from anywhere, if you do that you will cycle through a lot of users you don't need to.
Since all you need is users whose state matches the new candidate, you can use the where() method to pre-filter the list you are looping through. That way you don't need the if at all.
#candidate = Candidate.new(candidate_params)
...
User.where(state: #candidate.state).each do |u|
#notification = Notification.new(message: 'Message', user: u)
#notification.save
end
The other problem in your code is in the line to create a notification. You use U.id but the loop variable is lower case u. As an added tip, you don't need to set the object ID specifically. If you just pass the User object (as in the code above), Rails is smart enough to figure out the rest.
For performance don't iterate all users, you can search users that match the candidate's state then create notification for each user.
def create
#candidate = Candidate.new(candidate_params)
if #candidate.save
users = User.where(state: #condidate.state)
users.each { |user| #notification = Notification.create(:message => 'Message', :user_id =user.id } if users
else
render 'new'
end
end

redirecting back to different object

I have a problem for redirecting back after an action.
My condition is this:
Client, Volunteer, and staff has many next of kin. After creating a new next of kin, I want to redirect back to edit page of a particular client/volunteer/staff.
my current solution is this.
For the link to add
<%= link_to new_admin_people_next_of_kin_path(source: source,
source_id: source_id),
class: 'js-btn-add btn btn-success btn-sm' do %>
Add New Next of Kin
<% end %>
where
source = :client/:staff/:volunteer
source_id = id(primary key) or the staff/volunteer/client
my new method
def new
#person = Person.new
#person.source = params[:source]
#person.source_id = params[:source_id]
end
later I will pass source and source_id as hidden parameter.
my people_controller create method(because next of kin is a person)
def create
if params[:source] == 'client'
#client = Client.find(params[:source_id])
#pnok = #client.people_next_of_kin.build
elsif params[:source] == 'volunteer'
#volunteer = Volunteer.find(params[:source_id])
#pnok = #volunteer.people_next_of_kin.build
elsif params[:source] == 'staff'
#staff = Staff.find(params[:source_id])
#pnok = #staff.people_next_of_kin.build
else
#pnok = PeopleNextOfKin.new
end
#person = #pnok.build_next_of_kin
Person.transaction do
#person.update_attributes(create_params)
#person.save(validate: false)
end
end
as you can see, it's not really clean and hardcoded. I have read on polymorphic path, but I can't really find a way to use that for my solution as I need to build a new next of kin first and I cannot pass in an object in link_to or redirect_to, and then there's also a problem whereby the next of kin is not saved yet in database, so I cannot use person.find.
any solution?
It's a little tough to see what you are after from your example. I would guess that you want to redirect back to the staff / volunteer / client page and there would be a Parent / Child relationship with a NOK.
However, it's unclear what your models look like. For example, you might be using single table inheritance and polymorphism because these are all "People", or you might have the relationships in your models. I think the solution depends on which path you take.
For example, you might use something like this:
def Client
has_many :noks
end
If you had that, you could build the empty :nok record and then redirect back to the :client, and let the Rails / ActiveRecord internals manage the relationship. For example, the Client #show page may have places to list all Next of Kins that enumerates all of the kin.
Summary: I think you are trying to do too much in your controller without using models the way that RoR supports.

Ruby on Rails 4 fields_for number of repetitions

I would like to display a form with four nested fieldsets for associated objects. The only way I've found is to override the initialize method and define four associations:
RUBY
def initialize(attributes = {})
super
4.times { items << Item.new }
end
and then display nested fields normally:
HAML
= f.fields_for :items do |item|
= render 'item_fields', f: item
This is not working when I try to edit objects that already exist and have fewer number of associated items.
Any help will be appreciated.
MORE INFO:
Order has_many items
OrderSet has_many orders
Orders are added through the cocoon gem (there is at least one order in each set)
There should always be four items for each order. But when there are less items I don't want to save empty records, instead I would like to just display remaining items as empty.
The initialize is not the place as it is executed every time a new Order instance is created, this means: also when retrieving an existing order from the database.
Imho the view is also not the optimal place.
I would solve this in the controller:
def new
#order = Order.new
4.times { #order.items.build }
end
and then you can just leave your model/view as they were originally.
If you always want to show 4 nested items, you can do something similar in the edit action (to fill up to 4)
def edit
#order = Order.find(params[:id])
(#order.items.length...4).each { #order.items.build }
end
In my personal opinion this is cleaner then doing it in the view.
[EDIT: apparently it is a double nested form]
So, in your comment you clarified that it is a double-nested form, in that case, I would use the :wrap_object option as follows (it gets a bit hard to write a decent example here, without you giving more details, so I keep it short and hope it is clear). I am guessing you have a form for "something", with a link_to_add_association for :orders, and that order needs to have several (4) items, so you could do something like:
= link_to_add_association('add order', f, :orders,
:wrap_object => Proc.new { |order| 4.times { order.items.build}; order })
Before your f.fields_for in your view, or even in your controller, you can check the length of .items() and create new objects as required:
(o.items.length...4).each { f.object.items << Item.new}
= f.fields_for :items do |item|
= render 'item_fields', f: item

howto create multiple records in a single form with same values

I am looking for a clean solution to create multiple database records from a single form, that all have the same values specified in the form. Only the ID should obviously be different.
I need this function to let the user create 100+ records at once as a kind of templating.
So ideally in the form the user can type a number for the count of records she/he would like to create with the filled in values.
Use an iterator. Example:
def create_many
count = params[:count].to_i
# count within reasonable limits, check if object will validate
if (1..100) === count && Object.new(params[:object]).valid?
count.times { Object.create(params[:object]) } # <= the iterator
redirect_to my_custom_view # <= custom 'show' view
else
render :text => "Couldn't do it." # <= failure message
end
end
This example expects two parameters, :object which contains resource attributes, and :count which specifies how many records to create.
You need a custom show view to handle getting and displaying all of the newly created records.

How to show flash message of newly created resources?

Lets say I have a Product model and my controller allows the creation of 5 new products all at once. What I do right now is render back to the same page but what I want to do is also render in the flash which products have been created. So it would say:
Successfully created: Milk, Soup, Cheese, Bread, Candy
instead of
Successfully created Products
How would this be done?
Something like this should work:
redirect_to :back, notice: "Succesfully created: #{#created_products.map(&:name).join(', ')}"
Assuming #created_products is an array of the products you just created and they each have an attribute called name.
Oh and of course you need this in your html:
<div class="notice"><%= notice %></div>
Just do this in your controller. You probably have a create action where you actually create these objects, and they problem have a name attribute or something, right? So when you create the objects, save them in an array, then use the map and join methods to put them together. Something like this:
def create
successful = []
# loop through the parameters
obj = MyModel.new(...)
if obj.save
successful << obj
end
# end loop
flash[:notice] = "Succesfully created: #{successful.map(&:name).join(', ')}"
redirect_to my_model_path
end
Map runs the method you pass in, so name, on each of the memebrs of the array and returns an array whose contents are the results of that method: in other words, you get an array of all their names. Join puts them together separated by whatever string you put in. So you'd get something like "Milk, cheese".

Resources