Collection_check_box usage in RoR - ruby-on-rails

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

Related

Rails Multiple checkboxes with associated Text fields

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

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")

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

Rails: Show all associated records on a has_many through association

I'm building a guestlist app and I have defined both Guest (name) and List models - guests can have many lists and lists can have many guests. Both are associated in a has_many through association (after reading that HABTM associations aren't a good idea).
Here are my models:
class Guest < ActiveRecord::Base
has_many :lists, through: :checklists
end
class List < ActiveRecord::Base
has_many :guests, through: :checklists
end
class Checklist < ActiveRecord::Base
belongs_to :list
belongs_to :guest
end
EDIT - my lists controller for show:
def show
#list = List.find(params[:id])
end
On the List show view, I want to display the all of the guest names that are tied to that list through the checklist table. I can figure out if I need a do loop or an array...this is a bit beyond my current skill.
I've tried things like the following:
<%= #list.checklist.guest.name %>
I'm clearly missing some key bit of code and concept here.
Thanks in advance.
You need to iterate over guests like this:
<% #list.guests.each do |guest| %> # For each guest in list.guests
<%= guest.name %> # print guest.name
<% end %>
It should be something like this
<% #list.guests.each do |guest| %>
<%= guest.name %>
<% end %>

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