Rails Multiple checkboxes with associated Text fields - ruby-on-rails

I'm trying to create a Product form that has multiple sizes and prices for each of those sizes.
They way I have it modelled is a has_many :through relationship.
The associative table contains an extra field for price such that it will now hold the product_id, size_id, and price.
I'm not sure how I should go about creating my form or how Rails expects this to look. Any help would be much appreciated.
My Product is Cake :)
class Cake < ApplicationRecord
belongs_to :cake_type
has_many :cake_details
has_many :sizes, through: :cake_details
end
Size model
class Size < ApplicationRecord
has_many :cake_details
has_many :cakes, through: :cake_details
end
CakeDetail model
class CakeDetail < ApplicationRecord
belongs_to :cake
belongs_to :size
end
my migration
class CreateCakeDetails < ActiveRecord::Migration[5.1]
def change
create_table :cake_details do |t|
t.references :cake, foreign_key: true
t.references :size, foreign_key: true
t.decimal :price, :precision => 10, :scale => 2
t.timestamps
end
end
end
The only thing I'm stuck on is associating the form with the model.
i.e. for every size I want to have a text box with price associated with it.
This is currently how I'm approaching it but I have no idea how rails expects the id's of the text box to look or how I should structure this.
This is currently what I'm experimenting with in my form
<%= collection_check_boxes(:cake, :size_ids, Size.all, :id, :name) do |b| %>
<tr>
<td>
<%= b.label %>
</td>
<td>
<%= b.check_box %>
</td>
<td>
<%= form.text_field :cake_detail, id: b.label %>
</td>
</tr>
<% end %>

The way you define your business logic is normal
- A product has multiple sizes
- Each size has a price
The only thing I believe that it leads you to the problem is you are trying to create everything at the same time. Even Rails has nested_attributes which might solve your problem, but let's think once again.
Generally, Size records are fixed and was created beforehand. So that you don't have to create it at the same time with creating a Product.
Once you deal with this idea, your problem becomes much easier:
You had a list of Size: M, L, XL, XXL ... that was created beforehand
( You may create them via db/seeds.rb )
You want to create Product along ProductDetail with prices,
and link the ProductDetail with Size
Now you can use Rails's nested_attributes for the relation Product -> ProductDetail
Your model
# app/models/cake.rb
class Cake < ApplicationRecord
belongs_to :cake_type
has_many :cake_details
has_many :sizes, through: :cake_details
attr_accessor :is_enable
accepts_nested_attributes_for :cake_details, reject_if: :is_not_good_detail?
private
def is_not_good_detail?(attributed)
return true if attributed[:is_enable].to_i != 1
# Check if CakeDetail is good or not
end
end
Your controller
# app/controllers/cakes_controller.rb
class CakesController < ApplicationController
def new
#cake = Cake.new
# Build `cake_details`, so that you can render them at view
Size.each do |size|
#cake.cake_details.build(size_id: size.id, price: 0)
end
end
def create
# Create your Cake + CakeDetail
end
private
def cake_params
# permit your params here
end
end
Your view
# app/views/cakes/_form.html.erb
<%= form_for #cake do |f| %>
<%= f.fields_for :cakes_detail do |field| %>
<%= field.check_box :is_enable %>
<%= field.hidden_field :size_id %>
<%= field.text_field :price %>
<% end>
<% end %>
My code is completely not tested, and you still have a lot of things to do, but it should be the right way to solve your problem, tho.
You can consider the checklist to make it done:
Display name of size. Ex: XL, XXL
Permit the right params
Reject the invalid CakeDetail attribute set.
Avoid duplicate of size for a product when updating
<< Update >>>
Since the check_box only produces 0 and 1 value, so using it for size_id is incorrect. We can solve it by:
add an attr_accessor (ex: is_enable) for CakeDetail and use it for the check_box
size_id become a hidden field
Reject attributes if is_enable != 1
You can found here a working example yeuem1vannam/product-size

Related

Rails Invoicing App

I want to create an invoice in rails. Invoice can have items and each item will have quantity, tax & price. It's a typical invoice we see everyday.
In order to create an invoice what is the best approach.
What is the common model for invoice and items?
I know Items will be a separate model. But how can we have one view for invoice, which creates both the invoice and items added to it?
What I mean is, Inside a new invoice page, there will be list of the clients, and list of the items , But here i'm not sure how to make the association when i create invoice. Is there any good example that i can follow ?
Please I'd appreciate some Help. Or even just a walk through of the steps i need to follow in order to accomplish that...
Here's my basic ERD
Quite a broad question, here's what I'd do:
#app/models/invoice.rb
class Invoice < ActiveRecord::Base
belongs_to :user
has_many :line_items
has_many :items, through: :line_items
accepts_nested_attributes_for :line_items
end
#app/models/line_item.rb
class LineItem < ActiveRecord::Base
belongs_to :invoice
belongs_to :item
end
#app/models/item.rb
class Item < ActiveRecord::Base
belongs_to :company
has_many :line_items
has_many :invoices, through: :line_items
end
--
#app/models/user.rb
class User < ActiveRecord::Base
has_many :invoices
end
This will be the base level "invoice" association structure - your clients/users can be built on top of it.
Your routes etc can be as follows:
#config/routes.rb
resources :invoices
#app/controllers/invoices_controller.rb
class InvoicesController < ApplicationController
def new
#invoice = current_user.invoices.new
#invoice.line_items.build
end
def create
#invoice = current_user.invoices.new invoice_params
#invoice.save
end
end
Then your view will be something like this:
#app/views/invoices/new.html.erb
<%= form_for #invoice do |f| %>
<%= f.fields_for :line_items do |l| %>
<%= f.text_field :quantity %>
<%= f.collection_select :product_id, Product.all, :id, :name %>
<% end %>
<%= f.submit %>
<% end %>
This would create the corresponding #invoice, with which you'll be able to call as follows:
#user.invoices.first
Apart from this, I don't have anywhere enough specific information to help specifically
May I recommend using the payday gem? I have created invoice models in the past applications and I'll tell you what, it can get pretty tricky sometimes depending on the type of application you're building. But the reason I like using this gem besides the convenience factor is that it can also render your invoices as a customizable PDF.
It makes adding items to the invoice a breeze as well, for example from their GitHub page:
invoice = Payday::Invoice.new(:invoice_number => 12)
invoice.line_items << Payday::LineItem.new(:price => 20, :quantity => 5, :description => "Pants")
invoice.line_items << Payday::LineItem.new(:price => 10, :quantity => 3, :description => "Shirts")
invoice.line_items << Payday::LineItem.new(:price => 5, :quantity => 200, :description => "Hats")
invoice.render_pdf_to_file("/path/to_file.pdf")

Collection_check_box usage in RoR

I'm a relative novice to rails. I have a process that runs and finishes with a status (five possible different statuses). The process is run per-building. (We collect data from buildings.) I'm trying to allow users to configure themselves to receive notification email with fine-grained control: Per-building and complettion status. I'm trying to use collection_check_boxes to create a table of checkboxes for the user to select, but I'm not even sure collection_check_boxes was designed for such a case. I would be very happy to hear a yes or no on that question to start with.
I have the following modles:
class Building < ActiveRecord::Base
self.primary_key = 'building_id'
has_many :etl_status
has_many :email_notifications
end
class BuildingUserPair < ActiveRecord::Base
self.primary_key = "building_user_pairs_id"
belongs_to :building
belongs_to :user
has_many :email_notification_settings
end
class EmailNotificationSetting < ActiveRecord::Base
self.primary_key = "email_notification_settings_id"
belongs_to :building_user_pair
end
class EtlResult < ActiveRecord::Base
self.primary_key = 'etl_results_id'
# table has 5 rows, with values 1 -5 with the statuses "HUNKY DORY", "MO BETTA", "LIMPING ALONG", "DISMAIL FAILURE" and "UNEXPECTEDLY IDLE"
end
class EtlResultNotification < ActiveRecord::Base
belongs_to :building
belongs_to :user
end
class EtlStatus < ActiveRecord::Base
self.primary_key = 'etl_status_id'
belongs_to :building
has_many :users, :through => :email_notifications
end
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#buildings = Building.active
#bup = []
#email_notifications_settings = []
#buildings.each do |bldg|
bup = BuildingUserPair.where(user_id: params[:id], building_id: bldg.building_id).first_or_create
#email_notifications_settings[bldg.building_id] =
EmailNotificationSetting.where(building_user_pairs_id: bup.building_user_pairs_id).all
end
end
my users/show.html.erb contains this:
<%= form_for #user do |f| %>
<% #buildings.each do |bldg| %>
<div class="row">
<div class="col-md-4">
<%= bldg.name %>
</div>
<%= f.fields_for :email_notification_settings do |ens| %>
<%= ens.collection_check_boxes( #email_notifications_settings[bldg.building_id],
EtlResult.all, :etl_result_id, :result_name) %>
</div>
<% end %>
<% end %>
<div class="form-buttons">
<%= submit_tag("Update", data: { buildings: #buildings}) %>
</div>
The etl_result_notification table has just two columns, besides
it's primary key, a building-user-pair column and then a number,
1-5 that is a foreign key to the Etl Results table. Thus the idea is that a new line gets
created for a checkbox, and if a checkbox is newly unchecked, the row in the table is deleted.
Like I said, not even sure if form_for and collection_check_boxes was even designed to do this.
My problem is that the checkboxes are not being properly initialized. They all come up unchecked.
I'm guessing I need to pass other paraemters to collection_check_boxes, but I can't think
what they should be.
TIA
I think that you are over complicating your question,
what you want to do is save a list of id that march to the lest of emails the user want to get.
Rubyist posted https://stackoverflow.com/a/23340368/1380867 witch is a good example of what you want to do.
you should create a serialize :email_notification_ids
#MODEL
class User < ActiveRecord::Base
serialize :email_notification_ids
end
Hope this helps
Happy Codding

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'

Rails: Sunspot text searching with model associations, using :through

How do I search with associations and through with sunspot?
class StaticController < ApplicationController
def search
#search = Sunspot.search Business, Service do
fulltext params[:q]
paginate :per_page => 10
order_by_geodist(:location, *Geocoder.coordinates(params[:loc]))
end
#biz = #search.results
end
class Business < ActiveRecord::Base
attr_accessible :name
has_many :services, :through => :professionals
searchable do
text :name #name in business column
# how to do I get the services?
end
end
class Service < ActiveRecord::Base
attr_accessible :service
belongs_to :professional
end
class Professional < ActiveRecord::Base
belongs_to :business
has_many :services, as: :servicable
end
In the view, I have this (lots of looping)
<%= #biz.each do |b| %>
<%= b.name %>
<!-- looping through professionals model -->
<% b.professionals.each do |prof| %>
<!-- looping through services model -->
<% prof.services.each do |s| %>
<%= s.service %>
<% end %>
<% end %>
<% end %>
This works if I search for a name that is within the business model, but what if I'm searching through a term that's in the Service model? It won't display correctly because my view is only coming from the business side. How do I make it so the business name will pop up if I search through Service model?
Thanks
You will need to make additional indexes for the associated models in the calling model to make this happen. For example:
class Business < ActiveRecord::Base
attr_accessible :name
has_many :services, :through => :professionals
searchable do
text :name #name in business column
text :services do # this one for full text search
services.map(&:service).compact.join(" ")
end
string :services , :multiple => true do #this one for exact searches
services.map(&:service).compact
end
end
end
After that you can do queries like:
Bussines.search do
with(:services, "some_service")
end.execute.results
Now you no longer have to do join on mysql tables to fetch data. You can just fetch data from the solr. This is one of biggest advantages of solr.
I hope this makes it clear. Fell free to drop a comment if you need more details.

Resources