I would like to structure a music web-application's database to use the following structure.
Song(Attributes: name, year, etc(any other relevant attributes))
has many Performances
has many Performers (through Performances)
has many Artists (through Performers)
Performance(Foreign key:Song_id, Attributes: enum version: [:official, :remix, :cover])
belongs to Song
has many Performers
has many Artists (through Performers)
Performers (Foreign keys: Performance_id, Artist_id, Attributes: enum role: [:main_artist, :collaborating_artist, :featuring_artist, :versus ])
has one Song (through Performers)
belongs to Performances
belongs to Artist
Artist(Attributes: name, DOB, etc.(any other relevant attributes))
has many Performers
has many Performances (through Performers)
has many Songs (through Performances)
So that upon saving a Song, i can have all the rest of the other tables populated too, that is via the associations, however, so far i had the following implementations:
Song model:
class Song < ActiveRecord::Base
#Added to make the db more logical and ended up complicated
has_many :performances
has_many :performers, :through => :performances
has_many :artists, :through => :performers
accepts_nested_attributes_for :artists, allow_destroy: true
accepts_nested_attributes_for :performers, allow_destroy: true
accepts_nested_attributes_for :performances, allow_destroy: true
validates :name, :presence => true, uniqueness: true
validates :year, :presence => true
end
Performance model:
class Performance < ActiveRecord::Base
enum version: [:official, :remix, :cover]
belongs_to :song
has_many :performers
has_many :artists, :through => :performers
end
Performer model:
class Performer < ActiveRecord::Base
enum role: [:main_artist, :collaborating_artist, :featuring_artist, :versus]
belongs_to :artist
belongs_to :performance
has_one :song, :through => :performers #this upsets the back hairs!
end
Artist model:
class Artist < ActiveRecord::Base
#Added to make the db more logical and ended up complicated
has_many :performers
has_many :performances, :through => :performers
has_many :songs, :through => :performances
validates :name, :presence => true, uniqueness: true
end
Song controller
class SongsController < ApplicationController
before_action :find_song, only: [:show, :edit, :update, :destroy]
def index
...
end
def new
#song = Song.new
#song.performers.build
#song.performances.build
#genres = Genre.all #.map{|c| [ c.name, c.id ] }
#countries = Country.all.map{|c| [ c.name, c.id ] }
end
def create
#song = Song.new(song_params)
if #song.save
redirect_to #song, notice: 'Successfully added a song.'
else
render 'new'
end
end
def show
...
end
def update
if #song.update(song_params)
redirect_to #song, notice: 'Successfully updated the song.'
else
render 'edit', notice: 'Unable to save the changes'
end
end
def edit
...
end
def destroy
...
end
private
def song_params
params.require(:song).permit(:name, :year, artists_attributes: [ :id, :name, :DOB], performances_attributes: [:id, :version, :song_id], performers_attributes: [:id, :performance_id, :artist_id])
end
def find_song
#song = Song.find(params[:id])
end
end
new.html.erb
<div class="content">
<%= form_for #song, html: { multipart: true } do |f| %>
<% if #song.errors.any? %>
<div>
<%= #song.errors.count %>
Prevented this song from saving
<ul>
<% #song.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name, class: "input-field" %>
</div>
<div class="song-version">
<div class="field">
<%= f.fields_for :performances do |performances_for_form| %>
<%= render 'performances_fields', f: performances_for_form %>
<% end %>
</div>
</div>
<div class="performers-fields">
<h2>Artist(s)</h2>
<div class="field">
<%= f.fields_for :performers do |performers_for_form| %>
<%= render 'performers_fields', f: performers_for_form %>
<% end %>
</div>
</div>
<div class"btn">
<%= f.submit %>
</div>
<% end %>
</div>
Performance partial
<fieldset>
<div class="field">
<%= f.label :version %>
<%= f.select :version, Performance.versions.keys %>
</div>
</fieldset>
Performer partial
<fieldset>
<h2>Artists Details</h2>
<div class="field">
<%= f.fields_for :artists do |artists_for_form| %>
<%= render 'artist_fields', f: artists_for_form %>
<% end %>
</div>
<div class="field">
<%= f.label :artists_role %>
<%= f.select :role, Performer.roles.keys %>
</div>
</fieldset>
Artist partial
<fieldset>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :DOB %>
<%= f.datetime_select :DOB, :ampm => true, :minute_step => 15 %>
</div>
</fieldset>
When I fill out all the data on the form, upon saving, only the Song's attributes and Performances attributes are saved to the database, while the Performers & Artists are never saved, neither is the connection established, my questions are:
Is this structure logically correct? If not what is it that is being incorrectly done.
Is the new, create methods of songs_controller wrongly implemented and thus, the reason for not being able to save the data appropriately?
What right and or best practices could be employed developing this type of web-application with this kind of database structure?
Or is there any other better or succinct database structure that would better be followed rather than this?
Related
I have the tables Task and Item. I have a form for Item where I record all the possible items that my Tasks may have, which is working fine. Then I have the form for Task where all the Items are displayed alongside a field to put a cost value for each item. This will result in a join between Task and Item: TaskItem (this table contains task_id, item_id and cost).
When I submit the form, it's saving the Task but not the TaskItems associated. I don't see what I'm missing as I searched a lot for this problem and nothing seems to work. Please, see the code below.
Model:
class Task < ApplicationRecord
has_many :task_items
has_many :items, :through => :task_items
accepts_nested_attributes_for :task_items, :allow_destroy => true
end
class Item < ApplicationRecord
has_many :task_items
has_many :tasks, :through => :task_items
end
class TaskItem < ApplicationRecord
belongs_to :task
belongs_to :item
accepts_nested_attributes_for :item, :allow_destroy => true
end
Controller:
def new
#items = Item.all
#task = Task.new
#task.task_items.build
end
def create
#task = Task.new(task_params)
#task.save
redirect_to action: "index"
end
private def task_params
params.require(:task).permit(:id, :title, task_items_attributes: [:id, :item_id, :cost])
end
My view:
<%= form_for :task, url:tasks_path do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field(:title, {:class => 'form-control'}) %><br>
</p>
<% #items.each do |item| %>
<% #task_items = TaskItem.new %>
<%= f.fields_for :task_items do |ti| %>
<%= ti.label item.description %>
<%= ti.text_field :cost %>
<%= ti.hidden_field :item_id, value: item.id %>
<% end %>
<% end %>
<p>
<%= f.submit({:class => 'btn btn-primary'}) %>
</p>
You need to add inverse_of option to the has_many method in class Task:
class Task < ApplicationRecord
has_many :task_items, inverse_of: :task
has_many :items, through: :task_items
accepts_nested_attributes_for :task_items, :allow_destroy => true
end
This is due to the when creating a new TaskItem instance, it requires that the Task instance already exists in database to be able to grab the id fo the Task instance. Using this option, it skips the validation.
You can read this post about inverse_of option and its use cases.
fields_for has an option to specify the object which is going to store the information. This combined with building each of the TaskItem from the has_many collection should ensure that all the relationship are set correctly.
View Code:
<%= form_for #task do |f| %>
<p>
<%= f.label :title %><br>
<%= f.text_field(:title, {:class => 'form-control'}) %><br>
</p>
<% #items.each do |item| %>
<% task_item = #task.task_items.build %>
<%= f.fields_for :task_items, task_item do |ti| %>
<%= ti.label item.description %>
<%= ti.text_field :cost %>
<%= ti.hidden_field :item_id, value: item.id %>
<% end %>
<% end %>
<p>
<%= f.submit({:class => 'btn btn-primary'}) %>
</p>
<% end %>
Controller Code:
def index
end
def new
#items = Item.all
#task = Task.new
end
def create
#task = Task.new(task_params)
#task.save
redirect_to action: "index"
end
private
def task_params
params.require(:task).permit(:id, :title, task_items_attributes: [:id, :item_id, :cost])
end
I have three models -> locations, products and stocks.
Stocks is a join table of locations and products.
When creating a new product i used fields_for to show locations and even though i got it to work, for some reason now it does not seem to work anymore and it gives me the above error.
<div class="input-field">
<%= f.label :product_name %>
<%= f.text_field :name, autofocus: true %>
</div>
<div class="input-field">
<%= f.label :price %>
<%= f.text_field :price, autofocus: true %>
</div>
<% if !#edit %>
<%= f.fields_for :stocks do |ff| %>
<div class="input-field margin-top x-4">
<%= ff.collection_select :location_id, Location.all, :id, :structured_location , {:prompt => "Please Select Locations for Product"}, {multiple: true} %>
<%= ff.label :locations %>
</div>
<div class="input-field">
<%= ff.label :quantity %>
<%= ff.text_field :quantity %>
</div>
<div class="input-field">
<%= ff.label :threshold_quantity %>
<%= ff.text_field :threshold_quantity %>
</div>
<% end %>
<% else %>
<%= collection_select :product, :location_ids, Location.all, :id, :structured_location , {:prompt => "Please Select Locations for Product"}, {multiple: true} %>
<% end %>
<div class="row margin-top x-4">
<div class="col s12 center-align">
<%= f.submit "#{current_page?(new_product_path) ? "Create Product" : "Update Product"}", class: "btn wave-effect pink darken-1 btn-large" %>
</div>
</div>
controller
class ProductsController < ApplicationController
helper_method :sort_column, :sort_direction
def index
#products = Product.order(sort_column + " " + sort_direction)
end
def new
#product = Product.new
#product.stocks.build
end
def create
#product = Product.new(product_params)
if #product.save!
flash[:notice] = "Successfully saved..."
redirect_to products_path
else
flash[:alert] = "Something went wrong, please check the values you entered"
redirect_to :back
end
end
private
def product_params
params.require(:product).permit(:name,:price, location_ids: [], stocks_attributes: [:id, :quantity, :threshold_quantity, location_id: []])
end
end
product model
class Product < ApplicationRecord
has_many :stocks, dependent: :destroy
has_many :locations, :through => :stocks
accepts_nested_attributes_for :stocks
end
parameters in rails console
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"l1BFhrdyB2QMO5k3+60GNiPphFfF+DXDGPbUU3V2Op2aekObjgIe13k8uoedmDIEZgIeXPZUeS/0VxQXkKa1Uw==",
"product"=>{"name"=>"Soap", "price"=>"10", "location_ids"=>["", "1",
"2"], "stocks_attributes"=>{"0"=>{"quantity"=>"100",
"threshold_quantity"=>"100"}}}, "commit"=>"Create Product"}
After hours of searching i stumbled upon this post
BigBinary
it seems that Rails 5 made belongs_to relationship IDs required by default thats why i had validations failed and i couldn't find anything about it.
simply adding optional: true in my stock model worked!
class Stock < ApplicationRecord
belongs_to :location, optional: true
belongs_to :product, optional: true
accepts_nested_attributes_for :product, allow_destroy: true
accepts_nested_attributes_for :location, allow_destroy: true
end
I'm trying to implement a has_many :through many to many form, but I'm having an issue submitting to the database. I have no field validations, which tells me it's complaining about the structure of the parameters hash more than anything.
The error is:
ActiveRecord::RecordInvalid (Validation failed: Expense expense categories expense must exist):
The parameter hash looks like this:
Parameters: {"utf8"=>"✓", "expense"=>{"date"=>"2006/12/12", "amount"=>"234", "check_number"=>"234", "debit"=>"0", "notes"=>"234", "expense_expense_categories_attributes"=>{"1464029611137"=>{"amount"=>"234", "expense_category_id"=>"1", "_destroy"=>"false"}}}, "commit"=>"Create Expense"}
One thing I notice is that it's not adding the :expense_id value into the junction table. This should be done by the accepts_nested_attributes_for mechanism but it's not. I'm starting to think this an issue with Rails 5 because I've had similar relationships and forms structured like this that works fine. Do you guys see anything I'm missing?
here's my controller:
def create
#expense = Expense.new(expense_params)
respond_to do |format|
if #expense.save!
#expenses = Expense.all.paginate(:page => params[:page], :per_page => 9).order("created_at DESC")
format.html { redirect_to #expense, notice: 'Expense was successfully created.' }
format.js {}
format.json { render json: #expense, status: :created, location: #expense }
else
#expenses = Expense.all.paginate(:page => params[:page], :per_page => 9).order("created_at DESC")
format.html { render action: "index" }
format.js {}
format.json { render json: #expense.errors, status: :unprocessable_entity }
end
end
end
def expense_params
params.require(:expense).permit(:id, :date, :amount, :check_number, :debit, :notes, :amount, :expense_expense_categories_attributes => [:id, :amount, :expense_id , :expense_category_id, :_destroy])
end
Here are my models:
Expense
class Expense < ApplicationRecord
has_one :payee
monetize :amount_cents
has_many :expense_expense_categories
has_many :expense_categories, through: :expense_expense_categories, :dependent => :destroy
accepts_nested_attributes_for :expense_expense_categories,:allow_destroy => true
end
ExpenseCategory:
class ExpenseCategory < ApplicationRecord
has_many :expense_expense_categories
has_many :expenses, through: :expense_expense_categories
end
ExpenseExpenseCategory
class ExpenseExpenseCategory < ApplicationRecord
monetize :amount_cents
belongs_to :expense
belongs_to :expense_category
accepts_nested_attributes_for :expense_category
end
_form.html.erb:
<%= form_for #expense, html: { :class => "ui form segment" }, :remote => true do |f|%>
<div class="field">
<%= f.label :date%>
<div class="ui small input">
<%= f.date_field :date %>
</div>
</div>
<div class="field">
<%= f.label :amount %>
<div class="ui small input">
<%= f.text_field :amount %>
</div>
</div>
<div class="field">
<%= f.label :check_number %>
<div class="ui small input">
<%= f.text_field :check_number %>
</div>
</div>
<div class="field">
<%= f.label :debit %>
<div class="ui small input">
<%= f.check_box :debit %>
</div>
</div>
<div class="field">
<%= f.label :notes %>
<div class="ui small input">
<%= f.text_area :notes %>
</div>
</div>
<%= f.fields_for :expense_expense_categories do |builders| %>
<%= render 'expense_expense_category_fields', :f => builders %>
<% end %>
<%= link_to_add_fields "Add Category", f, :expense_expense_categories %>
<%= f.submit class: "ui blue button" %>
expense_expense_category_fields.htnl.erb
<div class="field">
<%=f.label :amount%>
<%= f.text_field :amount %>
</div>
<div class="field">
<%=f.label :category%>
<%= f.select :expense_category_id, ExpenseCategory.all.collect { |p| [p.category, p.id] } %>
</div>
<%= f.hidden_field :_destroy %>
<%= link_to "Remove option", "#", :class => "remove_expense_expense_categories" %>
Here is the form data from the browser being submitted:
utf8:✓
expense[date]:2016-05-12
expense[amount]:23
expense[check_number]:23
expense[debit]:0
expense[notes]:
expense[expense_expense_categories_attributes][1464030986149][amount]:23
expense[expense_expense_categories_attributes][1464030986149][expense_category_id]:1
expense[expense_expense_categories_attributes][1464030986149][_destroy]:false
expense[expense_expense_categories_attributes][1464030991099][amount]:43
expense[expense_expense_categories_attributes][1464030991099][expense_category_id]:10
expense[expense_expense_categories_attributes][1464030991099][_destroy]:false
commit:Create Expense
This is because Rails is attempting to create ExpenseExpenseCategory before the expense has been created. You must define; :inverse_of on the associations.
class Expense < ApplicationRecord
has_one :payee
monetize :amount_cents
has_many :expense_expense_categories, inverse_of: :expense
has_many :expense_categories, through: :expense_expense_categories, :dependent => :destroy
accepts_nested_attributes_for :expense_expense_categories,:allow_destroy => true
end
class ExpenseCategory < ApplicationRecord
has_many :expense_expense_categories, inverse_of: :expense_category
has_many :expenses, through: :expense_expense_categories
end
class ExpenseExpenseCategory < ApplicationRecord
monetize :amount_cents
belongs_to :expense, inverse_of: :expense_expense_categories
belongs_to :expense_category, inverse_of: :expense_expense_categories
accepts_nested_attributes_for :expense_category
end
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Setting+Inverses
Please check this link
https://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html
inverse_of didn't worked for me, I have to use optional: true in belongs_to association.
I have tried all of the solutions to similar problems and haven't gotten this one figured out.
I have a has_many :through relationship between 'Clinician', and 'Patient' with a joined model 'CareGroupAssignment'. None of the methods I have tried so far been able to save the clinician to patient association. I would like to have a patient be able to have multiple clinicians associated with it and clinicians will have multiple patients.
clinician.rb (simplified)
class Clinician < ActiveRecord::Base
belongs_to :care_group
has_many :patients ,:through=> :care_group_assignments
has_many :care_group_assignments, :dependent => :destroy
belongs_to :user
accepts_nested_attributes_for :user, :allow_destroy => true
end
patient.rb
class Patient < ActiveRecord::Base
belongs_to :care_group
has_many :clinicians ,:through=> :care_group_assignments
has_many :care_group_assignments
belongs_to :user
accepts_nested_attributes_for :user, :allow_destroy => true
end
care_group_assignments.rb
class CareGroupAssignment < ActiveRecord::Base
belongs_to :clinician
belongs_to :patient
end
I first tried to follow the example from Railscasts PRO #17- HABTM Checkboxes to at least start getting the data collected and to have the models set up correctly. Below is the form with the checkboxes for each clinician as described in the RailsCast, checkboxes show up and the data is sent but not stored (can't figure out why).
patient new.html.erb form
<%= form_for #patient do |form| %>
<%= form.fields_for :user do |builder| %>
<div class="form-group">
<%= builder.label "Email or Username" %>
<%= builder.text_field :email, class: "form-control" %>
</div>
<div class="form-group">
<%= builder.label :password %>
<%= builder.password_field :password, class: "form-control" %>
</div>
<% end %>
<div class="form-group">
<%= form.label :first_name %>
<%= form.text_field :first_name, class: "form-control", placeholder: "First name" %>
</div>
<div class="form-group">
<%= form.label :last_name %>
<%= form.text_field :last_name, class: "form-control", placeholder: "Last name" %>
</div>
<div class="form-group">
<% Clinician.where(care_group_id: #care_group.id).each do |clinician| %>
<%= check_box_tag "patient[clinician_ids][]", clinician.id, #patient.clinician_ids.include?(clinician.id), id: dom_id(clinician) %>
<%= label_tag dom_id(clinician), clinician.full_name %><br>
<% end %>
</div>
<%= form.button 'Create Patient', class: "btn btn-u btn-success" %>
<% end %>
Next, I tried the collection_select answer to this question. This creates a badly formatted list where only one clinician can be selected. The data seems to get sent but again doesn't save.
patient new.html.erb form
<div class="form-group">
<%= collection_select(:patient, :clinician_ids,
Clinician.where(care_group_id: #care_group.id).order("first_name asc"),
:id, :full_name, {:selected => #patient.clinician_ids, :include_blank => true}, {:multiple => true}) %>
</div>
Lastly, I copied what was done in this questions/solution. Also isn't formatted as a normal collection_select dropdown but instead a list with a boarder around it where only one clinician can be selected.
patient new.html.erb form
<div class="form-group">
<% Clinician.where(care_group_id: #care_group.id).each do |clinician| %>
<%= check_box_tag "patient[clinician_ids][]", clinician.id, #patient.clinician_ids.include?(clinician.id), id: dom_id(clinician) %>
<%= label_tag dom_id(clinician), clinician.full_name %><br>
<% end %>
</div>
None of these methods have so far been able to save the clinician to patient association.
patient_controller.rb
def new
#patient = Patient.new
#user = User.new
#patient.build_user
#care_group = current_clinician.care_group
end
def create
#patient = Patient.create(patient_params)
#patient.care_group = current_clinician.care_group
if #patient.save
redirect_to patient_path(#patient), notice: "New patient created!"
else
render "new"
end
end
def show
#patient = Patient.find_by(id: params["id"])
end
private
def patient_params
params.require(:patient).permit({:clinician_ids => [:id]},:first_name,:last_name,:user_id,:care_group_id, user_attributes: [ :email, :password, :patient_id, :clinician_id ])
end
I plan to display the clinicians associated with a patient on the patient show page:
patient show.html.erb
<strong>Shared with:</strong>
<% #patient.clinicians.each do |clinician| %>
<%= clinician.full_name %><
<% end %>
This works if I seed the database but since the data doesn't seem to be stored, nothing is showing up.
Rails 4.1.8, ruby 2.2.1p85, PostgreSQL
Thanks
Found the answer on another question I asked:
problem is this line in the controller:
params.require(:patient).permit({:clinician_ids => [:id]}...
It should be:
params.require(:patient).permit({:clinician_ids => []}...
I'm using rails to create a new product and want to add a category to every product.
I have three tables: product, category, and categorizations (which stores the relationship between products and categories). I'm trying to use nested attributes to manage the creation of the categorizations, but unsure how my controller and view/form should be updated so that new products also update the categorizations table.
Here are my models:
class Product < ActiveRecord::Base
belongs_to :users
has_many :categorizations
has_many :categories, :through => :categorizations
has_attached_file :photo
accepts_nested_attributes_for :categorizations, allow_destroy: true
attr_accessible :description, :name, :price, :photo
validates :user_id, presence: true
end
class Category < ActiveRecord::Base
attr_accessible :description, :name, :parent_id
acts_as_tree
has_many :categorizations, dependent: :destroy
has_many :products, :through => :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :category
belongs_to :product
attr_accessible :category_id, :created_at, :position, :product_id
end
Here is my new product controller:
def new
#product = Product.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #product }
end
end
And here is my view form:
<%= form_for #product, :html => { :multipart => true } do |f| %>
<% if #product.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#product.errors.count, "error") %> prohibited this product from being saved:</h2>
<ul>
<% #product.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :description %><br />
<%= f.text_field :description %>
</div>
<div class="field">
<%= f.label :price %><br />
<%= f.number_field :price %>
</div>
<div class="field">
<%= f.file_field :photo %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
How should I update my controller so that both the product and the categorization tables are updated when a new product is added? How do I update my view file so that the categories appear in a drop down menu?
I see that product has_many categories. It's naturally to allow the user to specify them at product creation/edition. One approach is described here (via checkboxes to assign categories to your product). Another way: create product as usually and allow adding/removing categories on its edit page, like:
cat_1 [+]
cat_2 [-]
cat_3 [+]
Also take a look at Railcasts, like this one for doing it on a more beautiful way.
First of all to show categories in view file use something like following to show category in dropdown
<%= select_tag("category_id[]", options_for_select(Category.find(:all).collect { |cat| [cat.category_name, cat.id] }, #product.category.collect { |cat| cat.id}))%>
Then in create method of product controller do something like following
#product = Product.create(params[:category])
#product.category = Category.find(params[:category_id]) if params[:category_id]
I hope this would help you.
Thanks.
Tutorial for Nested Model Form from RailsCasts
Maybe help you, or maybe It will help someone else.
Here is what I addded to my Product view file, in _form.html - this created multiple check boxes that I could use to select multiple categories per product:
</div class="field">
<% Category.all.each do |category| %>
<%= check_box_tag "product[category_ids][]", category.id %>
<%= label_tag dom_id(category), category.name %><br>
<% end %>