Rails calculated value not working on checkboxes - ruby-on-rails

The extra costs column in the Reservation model doesn't get calculated and saved on creating a new reservation. It's getting calculated and saved when the reservation is edited (even without changing any values in the form). It seems the checkboxes values are not being received by the calculate method or something.
Reservation has_many :bookings, has_many :extras, :through => :bookings
Booking belongs_to :extra, belongs_to :reservation
Extra has_many :bookings, has_many :reservations, :through => :bookings
before_save :calculate_extras_cost
def calculate_extras_cost
self.extras_cost = self.extras.sum(:daily_rate) * total_days
end
<%=hidden_field_tag "reservation[extra_ids][]", nil %>
<%Extra.all.each do |extra|%>
<%= check_box_tag "reservation[extra_ids][]", extra.id, #reservation.extra_ids.include?(extra.id), id: dom_id(extra)%>
<% end %>

Use the form collection helpers instead of creating the inputs manually:
<%= form_for(#reservation) do |f| %>
<%= f.collection_check_boxes(:extra_ids, Extra.all, :id, :name) %>
<% end %>
Also make sure you are whitelisting the :extra_ids property.
One other thing to bear in mind when using callbacks is that the parent record must be inserted into the database before the child records! That means you cannot use self.extras.sum(:daily_rate) in the callback because it relies on the child records being in the database.
You could use self.extras.map(&:daily_rate).sum to sum the values of the associated models from memory in Ruby. Another option would be to use association callbacks.

Related

undefined method `concert_id' for #<FollowUp::ActiveRecord_Associations_CollectionProxy:0x00007f8d481b3dc0> Did you mean? concat

I have a model of follow_ups and volunteers as:
class FollowUp < ApplicationRecord
belongs_to :volunteer
belongs_to :member
belongs_to :concert
end
class Volunteer < ApplicationRecord
enum type: [:admin, :regular]
has_many :follow_ups, dependent: :delete_all
has_many :members, through: :follow_ups
end
Now I wanted to print follow_ups by all volunteers.
It was working fine when I tried in rails console i.e Volunteer.first.follow_ups
I want to show those value in the form, what I tried is:
Volunteer.all.each do |volunteer|
volunteer.follow_ups.concert_id
end
The has_many relation denotes a one-to-many association. Instead of returning a single object, it returns a collection of follow_ups.
That said, you can't do volunteer.follow_ups.concert_id, because follow_ups is an Active Record collection. Make an iteration instead:
volunteer.follow_ups.each { |follow_up| puts follow_up.concert_id }
The Ruby on Rails documentation has great content about Active Record Associations.
To collect such information you should use:
volunteer.follow_ups.pluck(:concert_id)
Edit:
It's very important to note that using pluck is more efficient than using iterators like map and each due to saving server RAM and request time. Then you can print to rails logger:
volunteer.follow_ups.pluck(:concert_id).each{|ci| Rails.logger.info ci}
Edit2
Referring to your text
I want to show those value in the form
If I understand you, you want to show concert_id of each follow_up in the volunteer form. in this case you should add
accepts_nested_attributes_for :follow_ups in your volunteer.rb
then:
<%= form_for #volunteer do |f| %>
<%= f.fields_for :follow_ups do |form_builder| %>
<%= label_tag "custom_label", "follow up id : #{form_builder.object.id}, concert_id : #{form_builder.object.concert_id}%>
<% end %>
<% end %>
The fields_for helper will iterate through all follow_ups , then you can get the object for each follow_up using object which allow you to deal with object directly and get your concert_id attribute from it.

Efficient way to count associated objects in Rails 4

I am looking for a way to show a count of how many images there are for a category but obtained through a has_many association. I have been reading a little on counter_cache but as yet no joy on an implementation
class Category < ActiveRecord::Base
has_many :image_categories
has_many :images, through: :image_categories
end
class ImageCategory < ActiveRecord::Base
# Holds image_id and category_id to allow multiple categories to be saved per image, as opposed to storing an array of objects in one DB column
belongs_to :image
belongs_to :category
end
class Image < ActiveRecord::Base
# Categories
has_many :image_categories, dependent: :destroy
has_many :categories, through: :image_categories
end
Controller
#categories = Category.all
View
<% #categories.each do |c| %>
<li>
<%= link_to '#', data: { :filter => '.' + c.name.delete(' ') } do %>
<%= c.name %> (<%= #count here %>)
<% end %>
</li>
<% end %>
A couple important things to consider with counter_cache:
Certain Rails methods can update the database while bypassing callbacks (for instance update_column, update_all, increment, decrement, delete_all, etc.) and can cause inconsistent values for a counter cache. Same applies to any database changes outside of Rails.
Creating/deleting a child model always requires updating the parent. To ensure consistency of the counter cache Rails uses an additional DB transaction during this update. This usually isn't a problem but can cause database deadlocks if your child model is created/deleted frequently, or if the parent model is updated frequently. (http://building.wanelo.com/2014/06/20/counter-cache-a-story-of-counting.html)
These problems will be exacerbated since you're using a counter cache across a join table.
If you want to do an efficient dynamic count, that's always up to date, then you can use a custom select with a grouped join:
#categories = Category.select("categories.*, COUNT(DISTINCT images.id) AS images_count").joins(:images).group("categories.id")
<% #categories.find_each do |c| %>
<li>
<%= link_to '#', data: { :filter => '.' + c.name.delete(' ') } do %>
<%= c.name %> (<%= c.images_count # <- dynamic count column %>)
<% end %>
</li>
<% end %>
The cost of this grouped join should be very small provided your foreign keys are indexed, and I'd strongly consider taking this approach if you need images_count to always be consistent with the true value, or if images are frequently being created or destroyed. This approach may also be easier to maintain in the long run.
Since you are looking for an efficient way, i would suggest using counter_cache
Here is how your models should look like:
class Category < ActiveRecord::Base
has_many :image_categories
has_many :images, through: :image_categories
end
class ImageCategory < ActiveRecord::Base
# Holds image_id and category_id to allow multiple categories to be saved per image, as opposed to storing an array of objects in one DB column
belongs_to :image, counter_cache: :category_count
belongs_to :category, counter_cache: :image_count
end
class Image < ActiveRecord::Base
# Categories
has_many :image_categories, dependent: :destroy
has_many :categories, through: :image_categories
end
You'll need to add image_count field to your categories table and category_count in images table.
Once you are done adding the counters and fields, you'd need to reset the counters so that the fields are updated with the correct count values for the records already present in your db.
Category.find_each { |category| Category.reset_counters(category.id, :images) }
Image.find_each { |image| Image.reset_counters(image.id, :categories) }

Rails has_many relationship with prefilled views

I have a pretty basic Rails 4 app, and am using Cocoon's nested forms to manage the has_many... :through model association.
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
# ... etc
end
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
# ... etc
end
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluation, reject_if: :all_blank
# ... etc
end
When I use Cocoon in the View, I want to use the New Assessment view to pre-fill all the Student records in order to create a new Evaluation for each one. I don't want to have to do some hacky logic on the controller side to add some new records manually, so how would I structure the incoming request? With Cocoon I see that requests have some number in the space where the id would go (I've replaced these with ?? below).
{"utf8"=>"✓", "authenticity_token"=>"whatever", "assessment"=>{"description"=>"quiz 3", "date(3i)"=>"24", "date(2i)"=>"10", "date(1i)"=>"2015", "assessments_attributes"=>{"??"=>{"student_id"=>"2", "grade" => "A"}, "??"=>{"student_id"=>"1", "grade" => "B"}, "??"=>{"student_id"=>"3", "grade"=>"C"}}, }}, "commit"=>"Create Assessment"}
I see in the Coccoon source code that this is somehow generated but I can't figure out how it works with the Rails engine to make this into a new record without an ID.
What algorithm should I use (or rules should I follow) to fill in the id above to make a new record?
"??"
Never a good sign in your params.
With Cocoon I see that requests have some number in the space where the id would go
That ID is nothing more than the next ID in the fields_for array that Rails creates. It's not your record's id (more explained below).
From your setup, here's what I'd do:
#app/models/student.rb
class Student < ActiveRecord::Base
has_many :evaluations
has_many :assessments, through: :evaluations
end
#app/models/evaluation.rb
class Evaluation < ActiveRecord::Base
belongs_to :student
belongs_to :assessment
end
#app/models/assessment.rb
class Assessment < ActiveRecord::Base
has_many :evaluations
has_many :students, through: :evaluations
accepts_nested_attributes_for :evaluations, reject_if: :all_blank
end
This will allow you to do the following:
#app/controllers/assessments_controller.rb
class AssessmentsController < ApplicationController
def new
#assessment = Assessment.new
#students = Student.all
#students.each do
#assessment.evaluations.build
end
end
end
Allowing you:
#app/views/assessments/new.html.erb
<%= form_for #assessment do |f| %>
<%= f.fields_for :evaluations, #students do |e| %>
<%= e.hidden_field :student_id %>
<%= e.text_field :grade %>
<% end %>
<%= f.submit %>
<% end %>
As far as I can tell, this will provide the functionality you need.
Remember that each evaluation can connect with existing students, meaning that if you pull #students = Student.all, it will populate the fields_for accordingly.
If you wanted to add new students through your form, it's a slightly different ballgame.
Cocoon
You should also be clear about the role of Cocoon.
You seem like an experienced dev so I'll cut to the chase - Cocoon is front-end, what you're asking is back-end.
Specifically, Cocoon is meant to give you the ability to add a number of fields_for associated fields to a form. This was discussed in this Railscast...
Technically, Cocoon is just a way to create new fields_for records for a form. It's only required if you want to dynamically "add" fields (the RailsCast will tell you more).
Thus, if you wanted to just have a "static" array of associative data fields (which is I think what you're asking), you'll be able to use fields_for as submitted in both Max and my answers.
Thanks to #rich-peck I was able to figure out exactly what I wanted to do. I'm leaving his answer as accepted because it was basically how I got to my own. :)
assessments/new.html.haml (just raw, no fancy formatting)
= form_for #assessment do |f|
= f.fields_for :evaluations do |ff|
.meaningless-div
= ff.object.student.name
= ff.hidden_field :student_id, value: ff.object.student_id
= ff.label :comment
= ff.text_field :comment
%br/
assessments_controller.rb
def new
#assessment = Assessment.new
#students = Student.all
#students.each do |student|
#assessment.evaluations.build(student: student)
end
end

How can I synchronize a has_many association using accepts_nested_attributes_for by foreign_key instead of ID?

I would like to synchronize a has_many association by foreign key. It seems I have to write custom code to do this. Is there any Rails / Active Record magic / Gem to achieve this? Specifically, I'd like to synchronize a join-table where the pairs of foreign keys should be unique.
class Food < ActiveRecord::Base
has_many :food_tags, :dependent=>:destroy, :inverse_of => :food
accepts_nested_attributes_for :food_tags, :allow_destroy => true
end
class FoodTag < ActiveRecord::Base
belongs_to :tag, :inverse_of=>:food_tags
belongs_to :food, :inverse_of=>:food_tags
end
class Tag < ActiveRecord::Base
has_many :food_tags, :dependent=>:destroy, :inverse_of=>:tag
has_many :foods, :through=>:food_tags
end
For my form with nested attributes (or my JSON API), I'd really like to omit the FoodTag id and use the tag_id to synchronize when updating a food.
I want to submit this on update to show that the tag is set or cleared
# this one is set
food[food_tag_attributes][0][tag_id] = 2114
food[food_tag_attributes][0][_destroy] = false
# this one is cleared
food[food_tag_attributes][1][tag_id] = 2116
food[food_tag_attributes][1][_destroy] = true
Instead, I have to submit this for update:
# this one is set
food[food_tag_attributes][0][id] = 109293
food[food_tag_attributes][0][tag_id] = 2114
food[food_tag_attributes][0][_destroy] = false
# this one is cleared
food[food_tag_attributes][0][id] = 109294
food[food_tag_attributes][1][tag_id] = 2116
food[food_tag_attributes][1][_destroy] = true
This pushes a burden to the client to know the IDs of the food tag records instead of just being able to Set or Clear tags based on the tag id.
Can this be done easily? I'm sure I could write a before_save filter on Food, but it seems like there should be a reasonably generic solution.
There is an option called index: for fields_for in the view helper. You can set the index as your foreign_key. Then instead of sequential or some arbitrary numbers, your foreign_key will be used as the key to refer to your object.
EDIT:
<%= form_for #person do |person_form| %>
<%= person_form.text_field :name %>
<% #person.addresses.each do |address| %>
<%= person_form.fields_for address, **index**: address.id do |address_form|%>
<%= address_form.text_field :city %>
<% end %>
<% end %>
<% end %>

How can I create dynamic form field to store/update hash sets in Rails?

In my reservations table I have a rooms (text) field to store hash values such (1 => 3) where 1 is roomtype and 3 corresponds to the amount of rooms booked by the same agent.
My Reservation model
serialize reserved_rooms, Hash
Here is my nested resource
resources :hotels do
resources :roomtypes, :reservations
end
RoomType stores a single room type which belongs to Hotel model. Though I can enlist roomtypes within my reservation form I do not know how I can create a dynamic hash via form to create/update this hash.
I have this but I am looking for a way to create a dynamic hash "key, value" set. Meaning, if Hotel model has two RoomType my hash would be {12 = > 5, 15 => 1} (keys corresponds to the roomtype_ids while values are the amount}
<%= f.fields_for ([:roomtypes, #hotel]) do |ff| %>
<% #hotel.roomtypes.each do |roomtype| %>
<%= ff.label roomtype.name %>
<%= f.select :reserved_rooms, ((0..50).map {|i| [i,i] }), :include_blank => "" %>
<% end %>
<% end %>
What I want is what this website has in the availability section (nr. of rooms):
specs: rails 4.1, ruby 2.1
Note: If you think there is a design problem with this approach (storing reserved_room in a serialized field) I can follow another path by creating another table to store the data.
Might need tweaking but i used similar code with check-boxes and it worked!
<% #hotel.roomtypes.each do |roomtype| %>
<%= f.label roomtype.name %>
<%= f.select :"reserved_rooms[roomtype.id]", ((0..50).map {|i| [i,i] }), :include_blank => "" %>
<% end %>
This gets messy enough that I would probably consider going with a separate models as you mentioned. I would simply do:
class Hotel < ActiveRecord::Base
has_many :room_types
has_many :rooms, :through => :room_types
end
class RoomType < ActiveRecord::Base
has_many :rooms
end
class Room < ActiveRecord::Base
has_many :reservations
belongs_to :room_type
end
class Reservation < ActiveRecord::Base
belongs_to :room
belongs_to :agent
end
class Agent < ActiveRecord::Base
has_many :reservations
end
Then just use a generic form to submit the # Rooms integer, and let your controller handle making multiple reservations...? Maybe I'm not understanding your objective well enough...
Rails 4 has a new feature called Store you would love. You can easily use it to store a hash set which is not predefined. You can define an accessor for it and it is recommended you declare the database column used for the serialized store as a text, so there's plenty of room. The original example:
class User < ActiveRecord::Base
store :settings, accessors: [ :color, :homepage ], coder: JSON
end
u = User.new(color: 'black', homepage: '37signals.com')
u.color # Accessor stored attribute
u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
# There is no difference between strings and symbols for accessing custom attributes
u.settings[:country] # => 'Denmark'
u.settings['country'] # => 'Denmark'

Resources