Issues with creating a "many-to-many" relation - ruby-on-rails

I'm trying to do a simple task : I created a "magazine" scaffold, but I want it to have a specific relation : a user can have participate for the creation / redaction of a magazine, and it can take several users to create a magazine.
I checked the APIDock and did the following:
Specified the relation between the magazines and the users
model/magazine.rb
class Magazine < ApplicationRecord
mount_uploader :thumbnail, ThumbnailUploader
has_and_belongs_to_many :users
end
model/user.rb
class User < ApplicationRecord
has_and_belongs_to_many :magazines
# More code...
end
Created a migration to add a table to link both the models
class ManyToMany < ActiveRecord::Migration[5.0]
def change
create_table :magaziness_users, :id => false do |t|
t.integer :user_id
t.integer :magazine_id
end
add_index :magazines_users, [:magazine_id, :user_id]
end
end
Then I ran the migration
Added the list of all users ever recorded to the database to create a dropdown
<div class="field">
<%= f.label :users %>
<%= f.select :users, User.all_except(current_user).collect {|u| [u.username, u]}, {prompt: 'Add a creator?'}, { :multiple => true, :size => 3 } %>
</div>
But, when I'm saving a new magazine, the user doesn't get saved, and the "magazines_user remains empty.
edit 1
This is an auto-generated controller, since I use the scaffold command to create it. I didn't touch anything excepted the set_magazine function, where I added the Friendly_Id
class MagazinesController < ApplicationController
before_action :set_magazine, only: [:show, :edit, :update, :destroy]
def index
#magazines = magazine.all
end
def show
end
def new
#magazine = magazine.new
end
def edit
end
def create
#magazine = magazine.new(magazine_params)
if #magazine.save
redirect_to #magazine, notice: 'magazine was successfully created.'
else
render :new
end
end
def update
if #magazine.update(magazine_params)
redirect_to #magazine, notice: 'magazine was successfully updated.'
else
render :edit
end
end
def destroy
#magazine.destroy
redirect_to magazines_url, notice: 'magazine was successfully destroyed.'
end
private
def set_magazine
#magazine = magazine.friendly.find(params[:id])
end
def magazine_params
params.require(:magazine).permit(:titre, :description, :apercu, :users)
end
end
Did I forget any step?

so here is the answer with my code working:
I did two scaffolds:
rails generate scaffold user username:string email:uniq password:digest
rails generate scaffold magazine title:string description:text preview:string
Then added this to magazine migration:
create_table :magazines_users, id: false do |t|
t.belongs_to :magazine, index: true
t.belongs_to :user, index: true
end
In my form, I added:
<div class="field">
<%= f.label :users %>
<%= f.select :user_ids, User.all.collect { |u| [u.username, u.id] }, {include_blank: true}, {multiple: true} %>
</div>
And in my magazines controller I only modified magazine_params:
def magazine_params
params.require(:magazine).permit(:title, :description, :preview, :user_ids => [])
end
To see that it works, I added this in magazin show view:
<p>
<strong>Users:</strong>
<%= #magazine.users.map(&:username).join(" - ") %>
</p>
Of course I added "has_and_belongs_to_many" as you did in User and Magazine models.
And that's it :)
Tested with Rails 5 and it works just fine. :)
Also I strongly advice you to take a look at the simple_form gem. It has some great methods to handle associations (like has_and_belongs_to_many) easily, like this : <%= f.association :users, collection: User.all_except(current_user).order(:username) %>

Related

No route matches {:action=>"show", :controller=>"items"} missing required keys: [:id]

Update: I've been trying to debug my files, so most of the files have changed recently
I am getting a strange error when trying to use a "new" action to my items_controller. Essentially, a wishlist has_many items and an item belongs_to wishlist. The error message is as follows:
Code
Here is my items_controller:
class ItemsController < ApplicationController
def show
#item = Item.find(params[:id])
end
def new
#item = Item.new
end
def create
#item = Item.new(item_params)
if #item.save
redirect_to "/wishlist", :notice => "Success!"
else
redirect_to "/wishlist", :notice => "Failure, try again later!"
end
end
def edit
#item = Item.find(params[:id])
end
def update
#item = Item.find(params[:id])
if #item.update_attributes(item_params)
redirect_to(:action => 'show', :id => #item.id)
else
render 'edit'
end
end
private
def item_params
params.require(:item).permit(:name,:size,:qty,:priority)
end
private
def create_params
params.require(:item).permit(:name,:size,:qty,:priority,:wishlist_id)
end
end
And my routes.rb:
Rails.application.routes.draw do
get '/wishlist' => 'wishlists#index', as: :wishlists
get '/wishlist/:id' => 'wishlists#show', as: :wishlist
get '/wishlist_items/new' => 'items#new'
get '/wishlist_items/:id' => 'items#show', as: :items
get '/wishlist_items/:id/edit' => 'items#edit', as: :edit_items
patch '/wishlist_items/:id' => 'items#update'
resources :items
And finally, my new.html.erb for the items model:
<h1>New Item</h1>
<div class="wishlist-new">
<% if true %>
<%= form_for(#item) do |f| %>
<%= f.text_field :name, :placeholder => "Name" %>
<%= f.text_field :size, :placeholder => "Specifications" %>
<%= f.text_field :qty, :placeholder => "Quantity" %>
<%= f.text_field :priority, :placeholder => "Priority" %>
<%= f.text_field :id, :placeholder => "Wishlist ID" %> # changing :id to :wishlist_id doesn't seem to do anything
<%= f.submit "Create Item", class: "btn-submit" %>
<% end %>
<% end %>
</div>
My migration files (so you know how my databases are structured:
# Migration file for items
class CreateItems < ActiveRecord::Migration
def change
drop_table :items
create_table :items do |t|
t.string :name
t.string :size
t.string :qty
t.integer :priority
t.references :wishlist
t.timestamps
end
end
end
# Migration File for Wishlists
class CreateWishlists < ActiveRecord::Migration
def change
drop_table :wishlists
create_table :wishlists do |t|
t.string :title
t.timestamps
end
end
end
Attempts to Debug
It seems like the routes.rb is sending requests to different methods in the items_controller because the error seems to say that /wishlist_items/new is accessing a show method even though its new method takes priority. To support this, the page loads properly when I comment out get '/wishlist_items/:id' => 'items#show', as: :items in the routes file. What happens is the page loads properly, and the Item is created properly (when I fill out the form) except that when I go into the console, it says that the Item created has a property of wishlist_id: nil even though I specified for it to be 1 in the form.
The method mentioned above has two problems: (1) it doesn't work entirely correctly, and (2) it becomes impossible to show a specific Item in the wishlist.
The error occurs before the inner section of the form_for is loaded, so the problem either is (a) a weird routing thing (as mentioned above) or (b) something weird happening to the #item variable.
Thanks in advance!

Add model/database association upon create

I have a model named Entry, which has many Categories. The page where I create/edit the Entry has checkboxes for every Category.
When I am editing an Entry, everything works okay. When I create the Entry, I get as an error for the #entry:
:entry_categories=>["is invalid"]
My thinking is that rails can't create the entry_categories because it doesn't know the id of the Entry ( which it shouldn't, it hasn't been assigned an id yet ).
I feel like this is a very common thing to try to do. I haven't been able to find an answer though. Here comes the code spamming, there must be something I am missing and hopefully some more experienced eyes can see it.
entry.rb
class Entry < ActiveRecord::Base
validates_presence_of :contents
validates_presence_of :title
has_many :entry_categories, dependent: :destroy
has_many :categories, through: :entry_categories
belongs_to :book
validates_presence_of :book
end
entry_category.rb
class EntryCategory < ActiveRecord::Base
belongs_to :entry
belongs_to :category
validates_presence_of :entry
validates_presence_of :category
end
category.rb
class Category < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
has_many :entry_categories, dependent: :destroy
has_many :entries, through: :entry_categories
end
entries_controller.rb
class EntriesController < ApplicationController
before_action :find_entry, only: [ :show, :edit, :update, :destroy ]
before_action :find_book, only: [ :new, :create, :index ]
before_action :authenticate_admin!, only: [:new, :create, :edit, :update, :destroy ]
def new
#entry = Entry.new
end
def create
#entry = #book.entries.new( entry_params )
if #entry.save
redirect_to entry_path( #entry ), notice: 'Entry Created'
else
render :new
end
end
def show
#categories = Category.joins( :entry_categories ).where( "entry_categories.entry_id = #{#entry.id} " ).select( "name, categories.id " )
#category_class = #categories.first.name.downcase.gsub( / /, '_' ) if #categories.any?
end
def index
#entries = #book ? #book.entries : Entry.all
end
def edit
end
def update
if #entry.update( entry_params )
redirect_to entry_path( #entry ), notice: 'Entry Updated'
else
render :edit
end
end
def destroy
#book = #entry.book
#entry.destroy
redirect_to book_path( #book ) , notice: 'Entry Destroyed'
end
protected
def entry_params
params.require(:entry).permit( :title, :contents, :year, :month, :day, category_ids: [] )
end
def find_entry
#entry = Entry.find( params[:id] )
end
def find_book
#book = Book.find( params[ :book_id ] )
rescue
#book = nil
end
end
_form.html.erb
<%= form_for [ #book, #entry ] do | form | %>
<%= content_tag :p, title %>
<%= form.text_field :title, placeholder: 'Title', required: true %>
<%= form.number_field :year, placeholder: 'Year' %>
<%= form.number_field :month, placeholder: 'Month' %>
<%= form.number_field :day, placeholder: 'Day' %>
<%= form.text_area :contents %>
<fieldset>
<legend>Categories</legend>
<%= form.collection_check_boxes(:category_ids, Category.all, :id, :name ) %>
</fieldset>
<%= form.submit %>
<% end %>
So again, the entry_categories are invalid in the create method, in the update they are fine. It's the same html file.
There must be some way to tell rails to save the Entry before trying to save the EntryCategory?
Thanks.
I managed to get this working by taking the validations out of EntryCategory:
class EntryCategory < ActiveRecord::Base
belongs_to :entry
belongs_to :category
validates_presence_of :category
end
I'm not particularity happy about this solution, and would still appreciate other thoughts.
I think you can use any of the following approach:
You can use autosave functionality of Active Record association. By this, when you will save EntryCategory, it will automatically save Entry as well.
class EntryCategory < ActiveRecord::Base
belongs_to :entry , autosave: true
#rest of the code
end
You can also use before_save callback of active record. by this, whenever you will save EntryCategory, it will first call a specified method, than proceed with saving.
class EntryCategory < ActiveRecord::Base
before_save :save_associated_entries
#rest of the code
def save_associated_entries
# code to save associated entries here
end
end
Try this:
replace this code:
<%= form.collection_check_boxes(:category_ids, Category.all, :id, :name ) %>
with:
<% Category.all.order(name: :asc).each do |category| %>
<div>
<%= check_box_tag "entry[category_ids][]", category.id %>
<%= category.name %>
</div>
you can format it with fieldset instead of div

Adding Sub-categories in Rails4

I have a lot of Main Categories and want to add to each sub categories.->
Main Category
-Sub Category
-Sub Category
-Sub Category
Main Category
-Sub Category
-Sub Category
-Sub Category
A lot of people recommend me to use a gem, but as i am fairly new to Rails i rather learn how to do this on my own, to learn all they way through.
Should i start with a Scaffold or simply a Model ?
Could someone explain me how to start (migrations etc.) and how to set it up?
Thank you.
You have to generate a new model with rails g model category, then edit the file generate in db/migrate and write this
class CreateCategories < ActiveRecord::Migration
def change
create_table :categories do |t|
t.belongs_to :category
t.string :name, :null => false
t.timestamps
end
end
end
And edit app/models/category.rb
class Category < ActiveRecord::Base
belongs_to :category
has_many :children, :dependent => :destroy, :class_name => 'Category'
end
And you have to execute rake db:migrate for create the table in your database.
EDIT:
In app/controllers/categories_controller.rb
class CategoriesController < ApplicationController
def index
#categories = Category.all
end
def new
#category = Category.new
end
def edit
#category = Category.find(params[:id])
end
def create
#category = Category.new(params[:category].permit!)
if #category.save
redirect_to categories_url
else
render :new
end
end
def update
#category = Category.find(params[:id])
if #category.update_attributes(params[:category].permit!)
redirect_to categories_url
else
render :edit
end
end
def destroy
Category.destroy(params[:id])
redirect_to categories_url
end
end
And the form for your categories:
<%= form_for #category do |f| %>
<%= f.text_field :name %>
<%= f.select :category_id, options_from_collection_for_select(Category.all, :id, :name, #category.category_id), :include_blank => true %>
<%= f.submit %>
<% end %>

Why doesn't my user follow/unfollow button work?

I am working on building an application (following Michael Hartl's chapter 11) where users can follow projects that are created by other users.
I created a ProjectRelationship model to hold two components: follower_id for the users and projectuser_id for the projects. The foreign keys have been set up as such.
Right now, my _follow_form.html.erb page renders "follow" or "unfollow" depending on whether the current_user is following the project. Please see my code below and see what I am missing.
Right now, the follow button is generated on each project show page. But when I click the button follow button that is generated by _follow.html.erb, it does not seem to follow the project or update the count when I call #project.followers.count as the POST is not happening.
And thus, when I click follow button, the URL becomes all jumbled. See example:
#Goes from
domain.com/projects/21
#to
domain.com/projects/21?utf8=%E2%9C%93&authenticity_token=5EQmU0EkHB5yKDYakqL78piMWzZl0CfdpHFEqBeQiN4%3D&project_relationship%5Bprojectuser_id%5D=21&commit=Follow%22
**Update:
It seems to work now, but I'm not sure if I really changed anything but got rid of the follower_id index :unique => true through a migration change.
schema.rb
create_table "project_relationships", :force => true do |t|
t.integer "follower_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "projectuser_id"
end
add_index "project_relationships", ["follower_id"], :name => "index_project_relationships_on_follower_id", :unique => true
add_index "project_relationships", ["projectuser_id"], :name => "index_project_relationships_on_projectuser_id"
routes.rb
resources :projects do
resources :comments
member do
get :following
end
end
resources :project_relationships, only: [:create, :destroy]
project_relationship.rb
class ProjectRelationship < ActiveRecord::Base
attr_accessible :projectuser_id
belongs_to :user, foreign_key: "follower_id"
belongs_to :project, foreign_key: "projectuser_id"
end
project.rb
has_many :project_relationships, foreign_key: "projectuser_id"
has_many :favorited_by, through: :project_relationships, source: :user
user.rb
has_many :project_relationships, foreign_key: "follower_id"
has_many :followed_projects, through: :project_relationships, source: :project
def following_project?(project)
project_relationships.find_by_follower_id(project.id)
end
def follow_project!(project)
project_relationships.create!(projectuser_id: project.id)
end
def project_unfollow!(project)
project_relationships.find_by_projectuser_id(project.id).destroy
end
project_relationships_controller.rb
class ProjectRelationshipsController < ApplicationController
def create
#project = Project.find(params[:project_relationship][:projectuser_id])
current_user.follow_project!(#project)
redirect_to #project
end
def destroy
#project = ProjectRelationship.find(params[:id]).followed_project
current_user.project_unfollow!(#project)
redirect_to #project
end
end
projects/show.html.erb
<%= render 'follow_form' if signed_in? %>
projects/_follow_form.html.erb
<% if current_user.following_project?(#project) %>
<%= render 'unfollow' %>
<% else %>
<%= render 'follow' %>
<% end %>
projects/_follow.html.erb
<%= form_for(current_user.project_relationships.build(projectuser_id: #project.id)) do |f| %>
<div><%= f.hidden_field :projectuser_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
projects/_unfollow.html.erb
<%= form_for(current_user.project_relationships.find_by_projectuser_id(#project),
html: { method: :delete }) do |f| %>
<%= f.submit "Unfollow", class: "btn btn-large" %>
<% end %>
First of all - if you run projectfollow!(project) and projectunfollow!(project) in your console (with a user, project etc) do they work properly?
For your forms try the following instead and see if it works:
<%= form_for(current_user.project_relationships.build, url: project_relationships_path(project_id: #project.id)) do |f| %>
Then in your project relationships controller:
class ProjectRelationshipsController < ApplicationController
def create
#project = Project.find(params[:project_id])
current_user.projectfollow!(#project)
redirect_to #project
end
end
So if your create URL is /project_relationships (via POST), the above should post to /project_relationships?project_id=5 and then the controller can find that project.
Also, try to rename your methods so they make sense:
def following_project?(project)
end
def follow_project!(project)
end
def unfollow_project!(project)
end
Now current_user.following_project?(project) makes a lot of sense!
Update
Ok, I think the following is the problem, in your create action you're getting the id from the params:
#project = Project.find(params[:project_relationship][:projectuser_id])
However in your form you're not setting the value of the hidden field:
<%= f.hidden_field :projectuser_id %>
Change it to the following and see if it works:
<%= f.hidden_field :projectuser_id, value: #project.id %> # or wherever the id is from
The problem was that my follow/unfollow form was embedded in another form which caused the error. Once taken out, worked!

Nested form not working to save client_id and user_id

I have Users, Users have many Clients and Contacts. Clients also have many Contacts (a Contact belongs to both Users and Clients).
In my client view, I want to create a new Client and on the same form allow the user to create the first Contact for that client. Initially, I thought using nested attributes would do the trick but I'm running into some issues. When I go to save #client in clients_controller#create, I can't save because user_id can't be blank and client_id can't be blank for the Contact. Here's what I have so far:
clients controller index (where the new client form is located):
def index
#clients = current_user.clients
#client = Client.new
#contact = #client.contacts.build
respond_to do |format|
format.html # index.html.erb
format.json { render json: #clients }
end
end
and the create method:
def create
#client = current_user.clients.new(params[:client])
respond_to do |format|
if #client.save
and the form:
= form_for(#client) do |f|
= f.fields_for(:contacts) do |contact|
but when I go to save it requires a client_id and user_id...but I can't really set those using the nested attributes. how can I accomplish this? is there a better way of doing this? here's my params:
{"name"=>"asdf", "contacts_attributes"=>{"0"=>{"name"=>"asdf", "email"=>"asdf#gmail.com"}}}
I just tried adding the missing values directly into the contacts_attributes but since #client hasn't been saved yet, I can't assign the client.id to contact:
params[:client][:contacts_attributes]["0"].merge!(:user_id => current_user.id)
params[:client][:contacts_attributes]["0"].merge!(:client_id => #client.id)
even when user_id is set...it still says user is missing.
Did you add accepts_nested_attributes_for to your model? You need something like this:
class Client < ActiveRecord::Base
has_many :contacts
accepts_nested_attributes_for :contacts
end
Also, you should be using build in your create action:
#client = current_user.clients.build(params[:client])
Here's my setup that worked for your example:
app/models/user.rb:
class User < ActiveRecord::Base
attr_accessible :name, :clients_attributes
has_many :clients
accepts_nested_attributes_for :clients
end
app/models/client.rb:
class Client < ActiveRecord::Base
attr_accessible :company, :contacts_attributes
belongs_to :user
has_many :contacts
accepts_nested_attributes_for :contacts
end
app/models/contact.rb:
class Contact < ActiveRecord::Base
attr_accessible :email
belongs_to :client
end
app/controllers/users_controller.rb:
class UsersController < ApplicationController
# ...
def new
#user = User.new
#client = #user.clients.build
#concact = #client.contacts.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# ...
end
The actual form:
<% # app/views/users/new.erb %>
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.fields_for :clients do |client_form| %>
<h5>Client</h5>
<%= client_form.label :company, "Company name" %>
<%= client_form.text_field :company %>
<div class="field">
<h5>Contact</h5>
<%= client_form.fields_for :contacts do |contact_form| %>
<%= contact_form.label :email %>
<%= contact_form.text_field :email %>
<% end %>
</div>
<% end %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The form looks like this:
Here's how params sent by forms look like:
{
"utf8"=>"✓",
"authenticity_token" => "bG6Lv62ekvK7OS86Hg/RMQe9S0sUw0iB4PCiYnsnsE8=",
"user" => {
"name" => "My new user",
"clients_attributes" => {
"0" => {
"company" => "Client's Company Name LLC",
"contacts_attributes" => {
"0" => {
"email" => "emailbox#client.com"
}
}
}
}
},
"commit" => "Create User"
}
Update
To enable adding more companies/contacts afterwards, change the UsersController#edit action to this:
class UsersController < ApplicationController
# ...
def edit
#client = current_user.clients.build
#concact = #client.contacts.build
respond_to do |format|
format.html # new.html.erb
format.json { render json: #user }
end
end
# ...
end
The following validations may be causing this problem since the parent id has not assigned to a child object at the time the validations are run for it. The workaround to this is to either remove these validations or set them to run on update only. This way you lose out on these validations for the create method but it works.
# client.rb
validates :user, presence: true
# contact.rb
validates :client, presence: true

Resources