How can i make this specific relationship? - ruby-on-rails

School has one or many Teachers and Teacher has one or many Subjects
The same Teacher can teach in different schools and different subjects,eg:
Dan teaches English and Math in the School A and Physics in the School B
I tried use a has many through between these 3 model, but i don't know how can i add many schools and many subjects in a specific Teacher
Here what i tried
class Teacher < ApplicationRecord
has_many :school_teacher_subjects
has_many :schools, through: :school_teacher_subjects
has_many :subjects, through: :school_teacher_subjects
end
class School < ApplicationRecord
has_many :school_teacher_subjects
has_many :teachers, through: :school_teacher_subjects
has_many :subjects, through: :school_teacher_subjects
end
class Subject < ApplicationRecord
has_many :school_teacher_subjects
has_many :teachers, through: :school_teacher_subjects
has_many :schools, through: :school_teacher_subjects
end
class SchoolTeacherSubject < ApplicationRecord
belongs_to :teacher
belongs_to :school
belongs_to :subject
end
What i want is that inside the Teacher New/Edit form, i can save one or many schools and one or many subjects in the database at same time in this way:
+----+------------+-----------+------------+
| id | teacher_id | school_id | subject_id |
+----+------------+-----------+------------+
| 1 | 2 | 2 | 4 |
| 2 | 2 | 2 | 1 |
| 3 | 1 | 3 | 2 |
| 4 | 1 | 3 | 6 |
+----+------------+-----------+------------+
But all I can do is:
+----+------------+-----------+------------+
| id | teacher_id | school_id | subject_id |
+----+------------+-----------+------------+
| 72 | 8 | 2 | 2 |
| 74 | 2 | | 2 |
| 75 | 2 | | 6 |
| 76 | 1 | 3 | |
| 77 | 1 | | 2 |
| 78 | 1 | | 6 |
+----+------------+-----------+------------+
Here what i'm doing:
my controller and form
def new
#teacher = Teacher.new
#schools = School.all.order(name: :asc)
#subjects = Subject.all.order(name: :asc)
end
def edit
#teacher = Teacher.find(params[:id])
#schools = School.all.order(name: :asc)
#subjects = Subject.all.order(name: :asc)
end
def create
#teacher = Teacher.new(teacher_params)
respond_to do |format|
if #teacher.save
format.html { redirect_to admin_teacher_index_path, notice: 'Escola criada com sucesso.' }
else
format.html { render :new }
end
end
end
def update
#teacher = Teacher.find(params[:id])
respond_to do |format|
if #teacher.update(teacher_params)
format.html { redirect_to admin_teacher_index_path, notice: 'Escola editada com sucesso.' }
else
format.html { render :edit }
end
end
end
private
def teacher_params
params.require(:teacher).permit(:full_name, :genre, :status, school_ids: [], subject_ids: [])
end
FORM.HTML.ERB
<div class="row mb-3">
<div class="col">
<%= f.label :school_ids, 'Escolas(s)' %>
<%= f.collection_select(:school_ids, #schools, :id, :name, {:include_blank => "Selecione uma ou mais"}, {:class => "multiple-select2 custom-select", multiple: true}) %>
</div>
</div>
<div class="row mb-3">
<div class="col">
<%= f.label :subject_ids, 'Disciplina(s)' %>
<%= f.collection_select(:subject_ids, #subjects, :id, :name, {:include_blank => "Selecione uma ou mais"}, {:class => "multiple-select2 custom-select", multiple: true}) %>
</div>
</div>

One of the problems you have is that a Teacher can teach many subjects at many schools, but in your form you are selecting schools and subjects independently. Schools and Subjects must be selected together. I don't think you can do it with two multiple selects and passing two arrays (school_ids and subject_ids). In fact, a teacher could teach a subject in two schools and this cannot be implemented with your form. You need a more complex form. I would do it in a form where you could dynamically add new lines (subjects and schools related) using cocoon gem.
Models
class Teacher < ApplicationRecord
has_many :school_teacher_subjects
has_many :schools, through: :school_teacher_subjects
has_many :subjects, through: :school_teacher_subjects
# NEW
accept_nested_attributes_for :school_teacher_subjects,
reject_if: :all_blank, allow_destroy: true
end
class School < ApplicationRecord
has_many :school_teacher_subjects
has_many :teachers, through: :school_teacher_subjects
has_many :subjects, through: :school_teacher_subjects
end
class Subject < ApplicationRecord
has_many :school_teacher_subjects
has_many :teachers, through: :school_teacher_subjects
has_many :schools, through: :school_teacher_subjects
end
class SchoolTeacherSubject < ApplicationRecord
belongs_to :teacher
belongs_to :school
belongs_to :subject
end
Controller
private
def teacher_params
# CHANGED
params.require(:teacher).permit(:full_name, :genre, :status,
:school_teacher_subjects_attributes => [ :school_id, :subject_id, :id, :_destroy ])
end
View (main form):
<Teacher fields (fullname, genre, status, etc)>
<.............................................>
<div class="row mb-3">
<div class="col">
<%= f.simple_fields_for :school_teacher_subjects do |sts| %>
<%= render 'sts_fields', f: sts %>
<% end %>
<%= link_to_add_association 'Add new class', f,
:school_teacher_subjects,
:partial => 'sts_fields',
:force_non_association_create => true,
:data => {"association-insertion-method" => "before", "association-insertion-node" => 'this'}
%>
</div>
</div>
View (partial form for subject and school) sts_fields.html.erb :
<div class="nested-fields">
<div class="row">
<div class="col-xs-2">
<%= link_to_remove_association 'Remove', f %>
</div>
<div class="col-xs-5">
<%= f.collection_select :subject_id, #subjects, :id, :name %>
</div>
<div class="col-xs-5">
<%= f.collection_select :school_id, #schools, :id, :name %>
</div>
</div>
</div>

Related

Bulk add item rails 6

I have a little project management app.
In the app I have a Project, Item and Delivery Model.
class Project < ApplicationRecord
has_many :locations, dependent: :destroy
has_many :items, dependent: :destroy
has_many :deliveries, dependent: :destroy
end
class Item < ApplicationRecord
belongs_to :project
belongs_to :location, optional: true
has_many :delivery_items, dependent: :destroy
has_many :deliveries, through: :delivery_items
enum status: [:unscheduled, :scheduled, :delivered]
end
class Delivery < ApplicationRecord
belongs_to :project
has_many :delivery_items, dependent: :destroy
has_many :items, through: :delivery_items
enum status: [ :unapproved, :approved, :scheduled ]
end
I also have a delivery_item join table
class DeliveryItem < ApplicationRecord
belongs_to :delivery
belongs_to :item
end
I have added a new Model called location, which is a way of classifying the items into a group on the project.
class Location < ApplicationRecord
belongs_to :project
has_many :items
has_many :part_numbers, through: :items
def bulkadd(delivery)
self.items.each do |row|
batch << Product.new(row)
end
end
end
At the moment the user individually adds items to deliveries via a form on the page
<h6>Add to Delivery</h6>
<%= form_for #delivery_item, html: {class: 'form-inline'} do |form| %>
<div class="form-group">
<%= form.collection_select :delivery_id, #project.deliveries.all, :id, :date, placeholder: 'Add to Delivery', class: 'form-control' %>
</div>
<%= form.hidden_field :item_id, value: item.id %>
<div class="form-group">
<%= form.submit "Add",class: 'btn btn-primary' %>
</div>
<% end %>
I would like to simplify the process by adding a bulk add button to each location which would add all of the associated items to the delivery selected has many items.
I know that I will need the delivery_item(delivery, item).
I just cant seem to get the final part to work in my brain
When you create a has_many or has_and_belongs_to_many assocation the macro creates an others_ids setter/getter. In this case item_ids= which will automatically add/remove rows from the join table.
Its really easy to use this together with the form option helpers to create a select where the user can choose multiple records:
<%= form_for(#delivery) do |form| %>
<div class="field">
<%= f.label :item_ids, 'Select the items' %>
<%= f.collection_select :item_ids, #items, :id, :name, multiple: true %>
</div>
<% end %>
Or if you prefer checkboxes:
<%= form_for(#delivery) do |form| %>
<div class="field">
<%= f.label :item_ids, 'Select the items' %>
<%= f.collection_check_boxes :item_ids, #items, :id, :name %>
</div>
<% end %>
Replace :name with whatever attribute you want to use for the option text.
class DeliveriesController < ApplicationController
before_action :set_delivery, only: [:show, :edit, :update, :destroy]
# This avoids a database query in the view
before_action :set_items, only: [:new, :edit]
# POST /deliveries
def create
#delivery = Delivery.new(delivery_params)
if #delivery.save
redirect_to #delivery, notice: 'Delivery created'
else
set_items
render :new
end
end
# PUT|PATCH /deliveries/1
def update
if #delivery.update(delivery_params)
redirect_to #delivery, notice: 'Delivery updated'
else
set_items
render :edit
end
end
private
def set_delivery
#delivery = Delivery.find(params[:id])
end
def set_items
#items = Item.all
end
def delivery_item_params
# Passing the hash `item_ids: []` allows an array of permitted scalar types.
params.require(:delivery)
.permit(:foo, :bar, :baz, item_ids: [])
end
end

Rails nested association & multiple collection_select

I'm fairly new to rails and I'm having some issues updating my association tables when using multiple select.
I have three tables, portrait portrait_tags and tags
(The tags stores the names of my tag names (traditional, inspirational, community etc))
My desired outcome is that the 'multiple select field' will add the tags to the portrait_tag table based on the Tag.all tag_id value. Currently this seems to insert only one field and the tag_id in the portrait_tag table is NULL, then when I return to the edit page the multiple select is duplicated.
Params
Parameters: {"utf8"=>"✓", "authenticity_token"=>"j+Obhq9u+mvOKYnj4+TAGy+be8s3AbZlMvuyKiot5iyKqjMyFAcs23PjbQjOTjwl6aRBx1M5lmYRZzTjOeDTJA==", "portrait"=>{"portrait_tags_attributes"=>{"0"=>{"tag_id"=>["", "1", "2"]}}}, "commit"=>"Save changes", "id"=>"72"}
Tag.rb
class Tag < ActiveRecord::Base
has_many :portraits, through: :portrait_tags
accepts_nested_attributes_for :portraits
end
Portrait.rb
class Portrait < ActiveRecord::Base
has_many :portrait_tags
has_many :tags, through: :portrait_tags
accepts_nested_attributes_for :portrait_tags
end
Portrait_tag.rb
class PortraitTag < ActiveRecord::Base
belongs_to :portrait
belongs_to :tag
end
Edit.html.haml
%h1 Edit Portrait
= form_for [:admin, #portraits], :html => { :method => :put } do |f|
- if flash[:system].present?
- flash[:system].each do |e|
%div= e
- if flash[:notice].present?
%div= flash[:notice]
= f.fields_for :portrait_tags do |a|
= a.collection_select :tag_id, Tag.all, :id, :name, {}, {multiple: true}
= f.submit "Save changes", class: "btn btn-primary"
PortraitController
class Admin::PortraitsController < ApplicationController
def edit
#portraits = Portrait.where(artist_id: 33, id: params[:id]).take
#portraits.portrait_tags.build
end
def update
#portrait = Portrait.where(artist_id: 33, id: params[:id]).take
if #portrait.update(portrait_params)
p portrait_params
else
flash[:system] = #portrait.errors.full_messages
p #portrait.errors.full_messages
render :edit
end
end
private
def portrait_params
# Permit our attributes
params.require(:portrait).permit(:id, portrait_tags_attributes: [:id, :tag_id => [] ])
end
end
portrait_tags table
+----+-------------+--------+
| id | portrait_id | tag_id |
+----+-------------+--------+
portraits table
+----+-----------+--------------+
| id | artist_id | artist_image |
+----+-----------+--------------+
tags table
+----+-----------+--------------------+
| id | name | portrait_tag_id |
+----+-----------+--------------------+
<%= collection_select(:portrait_tag, :tag_ids,
Tag.all(:order=>"name ASC"),
:id, :name, {:selected => #portraits.tag_ids, :include_blank => true}, {:multiple => true}) %>
Hope this will work for you.

Incorrect user posts being displayed. (Follower & Following)

I am trying to display posts from users I am following. However, for some reason my stream index is displaying posts from users who are following me. How can I display posts from users I am following and not the other way around? Thank you in advance.
User Model
has_many :following, :through => :relationships, :source => :followed
has_many :subscribed, class_name: "Relationship", foreign_key: "follower_id"
Post Model
scope :subscribed, ->(following) { where user_id: following }
Relationships Model
class Relationship < ActiveRecord::Base
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "User"
validates :follower_id, presence: true
validates :followed_id, presence: true
has_many :followed_users, through: :relationships, source: :followed
#fields id | user_id | follower_id | created_at | updated_at
belongs_to :user
end
Stream Controller
class StreamController < ApplicationController
def index
#posts = Post.subscribed current_user.following
end
end
Stream Index
<div class="page-header">
<center><strong><h1> Stream Page </h1></strong></center>
</div>
<div id="posts" class="transitions-enabled">
<% #posts.each do |post| %>
<div class="box panel panel-default">
<%= link_to image_tag(post.image.url(:medium)), post %>
<div class="panel-body">
<%= post.description %><br/>
<strong><%= post.user.name if post.user %></strong>
<% end %>
</div>
<% end %>
</div>
<% end %>
</div>
The problem will be with the association you're calling
--
Associations
You're using my code (which is great!!) - I think the problem will be how you're calling the relationship association data
You'll be able to do this:
#app/controllers/posts_controller.rb
Class PostsController < ApplicationController
#posts = Post.subscribed current_user.followed
end

How to create a new nested resource?

A users can have many favorites top_songs,top_movies through songs and movies table.
A user registered user(current_user) want to post his favorites movies and songs.
Perhaps all Model association are right, i am stuck in controller and view (form).
When i submit from, i gets errors-
Can't mass-assign protected attributes: songs
How can i achieve this please?
all codes are below.
User Model
class User < ActiveRecord::Base
attr_accessible :id, :name_special_char, :screenname, :fullname, :username, :prefix, :firstname, :lastname,:middlename, :suffix, :age, :sex, :email,
:top_movies_attributes,:top_songs_attributes
has_many :top_movies
has_many :movies, through: :top_movies
has_many :top_songs
has_many :songs, through: :top_songs
accepts_nested_attributes_for :top_songs, :allow_destroy => true
accepts_nested_attributes_for :top_movies, :allow_destroy => true
end
Movie Model
class Movie < ActiveRecord::Base
attr_accessible :name
has_many :top_movies
has_many :users, through: :top_movies
end
TopMovie Model
class TopMovie < ActiveRecord::Base
belongs_to :user
belongs_to :movie
# attr_accessible :title, :body
end
Song Model
class Song < ActiveRecord::Base
attr_accessible :name
has_many :top_songs
has_many :users, through: :top_songs
end
TopSong Model
class TopSong < ActiveRecord::Base
belongs_to :user
belongs_to :song
# attr_accessible :title, :body
end
Controller
class MyTopFivesController < ApplicationController
def new
#favorites = current_user
#favorites=#favorites.movies.build()
#favorites=#favorites.songs.build()
respond_to do |format|
format.html # new.html.erb
format.json { render json: #useraccounts_my_top_fife }
end
end
def create
#favorites = current_user(params[:user])
#favorites.save!
# Here i have stuck. i am not sure how to save.
end
view form
<%=nested_form_for #favorites ,:url=>favorites_path(#favorites),:method=>'post' do |f| %>
<label >Songs</label>
<%= f.fields_for :songs do |songs| %>
<div id="Topsongs" >
<div class="input-control text span5 place-left ">
<%= songs.text_field :name,:placeholder=>"songs name.." %>
</div>
<div class="span1 place-left">
<%= songs.link_to_remove "", :class=>"icon-minus" %>
</div>
</div>
<% end %>
<span >
<%= f.link_to_add "", :songs, :class=>"icon-plus", :data => { :target => "#Topsongs" } %>
</span>
<label >movies</label>
<%= f.fields_for :movies do |movies| %>
<div id="Topmovies">
<div class="input-control text span5 place-left ">
<%= movies.text_field :name,:placeholder=>"movies name.." %>
</div>
<div class="span1 place-left">
<%= movies.link_to_remove "", :class=>"icon-minus" %>
</div>
</div>
<% end %>
<span>
<%= f.link_to_add "", :movies, :class=>"icon-plus",:style=>"font-size: 14px;", :data => { :target => "#Topmovies" } %>
</span>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
There are two options for setting up the associations here. One you have used by creating two join models for top songs and top movies. And other, to use polymorphic association.
Lets use polymorphic association. We are going to use User, Movie, Song and Favourite models for this stuff. The Favourite model will contain the polymorphic fields.
User.rb
class User < ActiveRecord::Base
attr_accessible :id, :name_special_char, :screenname, :fullname, :username, :prefix, :firstname, :lastname,:middlename, :suffix, :age, :sex, :email
has_many :favourites
has_many :movies, through: :favourites, source: :favouritable, source_type: 'Movie'
has_many :songs, through: :favourites, source: :favouritable, source_type: 'Song'
end
Movie.rb
class Movie < ActiveRecord::Base
attr_accessible :name
has_many :favourites, as: :favouritable
has_many :users, through: :favourites
end
Song.rb
class Song < ActiveRecord::Base
attr_accessible :name
has_many :favourites, as: :favouritable
has_many :users, through: :favourites
end
Favourite.rb
class Favourite < ActiveRecord::Base
belongs_to :users
belongs_to :favouritable, polymorphic: true
end
We also need to create the migration for new model "Favourite". As of now, we just need 3 columns ie. user_id, favouritable_id, favouritable_type. Here favouritable_type and favouritable_id are the polymorphic fields. favouritable_type is a string and favouritable_id is reference type.
Migration File
class CreateFavourites < ActiveRecord::Migration
def change
create_table :favourites do |t|
t.integer :user_id
t.references :favouritable, polymorphic: true
t.timestamps
end
end
end
Now, as we are going to mark some movies and songs to be favourite for a user, then we can place the code for building the data in UsersController instead of creating another controller or we can also create a controller for Favourites. I am going to use UsersController here. I am using update action to update the favourites as we don't need any extra functionality here. You can add a new action if you want.
In UsersController.rb
def edit_favourites #or some generic name
#user = current_user.includes(:movies, :songs)
#movies = Movie.all
#songs = Song.all
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
redirect_to users_path #user index page
else
if params[:user][:movie_ids].present? or params[:user][:song_ids].present?
render :edit_favourites
else
render :edit
end
end
end
edit_favourites.html.erb
<%= form_for(#user) do |f| %>
<div class="fields">
<%= f.label :movie_ids, "Favourite Movies: " %>
<%= f.collection_select :movie_ids, #movies, :id, :name, {}, multiple: true %>
</div>
<div class="fields">
<%= f.label :song_ids, "Favourite Songs: " %>
<%= f.collection_select :song_ids, #songs, :id, :name, {}, multiple: true %>
</div>
<% end %>
Also, Add the new action to routes.

Rails3 Nested Attribute Validation and Control

I'm rolling with a legacy database unfortunately and am trying to build my rails3 app around it.
Thanks to this previous post, I've figured out where I'm going but still think I'm approaching incorrectly.
My basic problem is that main my main data is stored in a table with multiple rows, each with a different attribute value:
+-----+----------+----------------+----+---------------+------------+
| id | username | attribute_name | op | value | raduser_id |
+-----+----------+----------------+----+---------------+------------+
| 173 | jenny | User-Password | := | March 25 2011 | 33 |
| 172 | jenny | User-Password | := | 1234 | 33 |
+-----+----------+----------------+----+---------------+------------+
2 rows in set (0.00 sec)
I was using a nested form to enter this information but it's not really doing what I need. I can add the nested attributes and set a field thanks to the previous question now.
The issue I have is that I need some more control over my user's inputs. For instance, I need to restrict them to three distinct attributes:
User-Password, Expiration, Simultaneous-Use
I also need to validate the fields. I can't do so with the nested form.
My plan was to get the user to enter these in the parent model and propagate down but I do not have a clue how to do this and save out to separate rows, like I do with my nested atributes.
Can anyone shed any light on this?
--UPDATE--
raduser.rb
class Raduser < ActiveRecord::Base
has_many :radcheck, :dependent => :destroy
accepts_nested_attributes_for :radcheck, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
end
radcheck.rb
class Radcheck < ActiveRecord::Base
set_table_name 'radcheck'
attr_accessible :attribute_name, :username, :value, :op, :groupname
belongs_to :raduser
has_many :radusergroup, :dependent => :destroy, :primary_key => :username, :foreign_key => :groupname
has_many :radgroupcheck, :through => :radusergroup
before_save :sync_usernames
private
def sync_usernames
self.username = self.raduser.username
end
end
Did you try placing the validations in the radcheck.rb model? Try this code:
radcheck.rb
class Radcheck < ActiveRecord::Base
set_table_name 'radcheck'
attr_accessible :attribute_name, :username, :value, :op, :groupname
belongs_to :raduser
validates :attribute_name, :inclusion => { :in => %w(User-Password Expiration Simultaneous-Use) }
before_save :sync_usernames
private
def sync_usernames
self.username = self.raduser.username
end
end
raduser.rb
class Raduser < ActiveRecord::Base
has_many :radcheck, :dependent => :destroy
accepts_nested_attributes_for :radcheck, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
end
radusers_controller.rb
def new
#raduser = Raduser.new
#raduser.radcheck.build
end
def create
#raduser = Raduser.new(params[:raduser])
if #raduser.save
redirect_to(#raduser, :notice => 'Raduser was successfully created.')
else
render :action => "new"
end
end
and finally the form
<% if #raduser.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#raduser.errors.count, "error") %> prohibited this raduser from being saved:</h2>
<ul>
<% #raduser.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_for #raduser do |f| %>
<p>
<%= f.label :username %><br />
<%= f.text_field :username %>
</p>
<%= f.fields_for :radcheck do |builder| %>
<li>
<%= builder.label :attribute_name %>
<%= builder.text_field :attribute_name %>
</li>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
When I tried to save with attribute name other than User-Password, Expiration, Simultaneous-Use, it is giving
1 error prohibited this raduser from being saved:
- Attribute name is not included in the list
If you want to change the message, you can add :message to the validations. You can add other validations like this in the Radcheck model.
See these links RailsCasts, Complex form codes

Resources