Ruby on Rails Association with model - ruby-on-rails

I am beginner Ruby on rails.Application have 4 model.State, Province, District and City.
Model
app/models/state.rb
Class State < ActiveRecord::Base
has_many :provinces
end
app/models/province.rb
Class Province < ActiveRecord::Base
belongs_to :state
has_many :districts
end
app/models/district.rb
Class District < ActiveRecord::Base
belongs_to :province
has_many :cities
end
app/models/city.rb
Class City < ActiveRecord::Base
belongs_to :district
end
Schema.rb
ActiveRecord::Schema.define(version: 20140714165543) do
create_table "states", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "provinces", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "state_id"
end
add_index "provinces", ["state_id"], name: "index_provinces_on_state_id"
create_table "districts", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "province_id"
end
add_index "districts", ["province_id"], name: "index_districts_on_province_id"
create_table "citys", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "district_id"
end
add_index "citys", ["district_id"], name: "index_citys_on_district_id"
end
I am using simple_form gem.I am Create CRUD for all models. My question is
When iam create some state. then i create provinces and assign to state error in browser . state_id is nil
class ProvincesController < ApplicationController
#GET /Provinces/new
def new
#province = Province.new
end
# GET /provinces/1/edit
def edit
end
# POST /provinces
# POST /provinces.json
def create
#province = Province.new(province_params)
respond_to do |format|
if #province.save
format.html { redirect_to #province, notice: 'Province was successfully created.' }
format.json { render :show, status: :created, location: #province }
else
format.html { render :new }
format.json { render json: #province.errors, status: :unprocessable_entity }
end
end
end
end
_form.html.erb in province
<%= simple_form_for(#province) do |f| %>
<% if #province.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#province.errors.count, "error") %> prohibited this province from being saved:</h2>
<ul>
<% #province.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= f.association :state %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>

You are getting state_id is nil because you haven't have it in the list of permitted params.
Change your province_params method to
def province_params
params.require(:province).permit(:name,:state_id)
end
And with the error you are getting
NoMethodError in Province#show
would be most probably due to this line <%= #state.name %> because #state is not defined in your show method.
It should be <%= #province.state.name %> as you are trying to display the state name of the associated province.

Please run the following generator to add the state_id field to the proviences table.
rails g migration add_state_id_to_proviences state_id:integer
then run the migration command
rake db:migrate

Related

How can I set my controller and views when I have two Many-to-Many association?

My aim is to organize uploaded posts by categories. These categories are shown on the nav bar and if you click one of the category's names, you will see the posts assigned with the category. When uploading a post, you can assign it with multiple categories too. So, I think this is like many posts can have many categories and many categories can have many posts.
This is how I want my posts organized by categories
However, I cannot set things right in my posts_controller.rb, posts/index.html.erb, posts/show.html.erb, and _navigation.html.erb
post.rb
class Post < ActiveRecord::Base
#This validates presence of title, and makes sure that the length is not more than 140 words
validates :title, presence: true, length: {maximum: 140}
#This validates presence of body
validates :body, presence: true
has_many :categorizations
has_many :categories, :through => :categorizations
end
category.rb
class Category < ApplicationRecord
has_many :categorizations
has_many :posts, :through => :categorizations
end
categorization.rb
class Categorization < ApplicationRecord
belongs_to :post
belongs_to :category
end
Then, here are these controller and views I am confused with:
posts_controller.rb
class PostsController < ApplicationController
before_action :find_post, only: [:edit, :update, :show, :delete]
before_action :authenticate_admin!, except: [:index, :show]
# Index action to render all posts
def index
if params.has_key?(:category)
#category = Category.find_by_name(params[:category])
#posts = Post.where(category: #category)
else
#posts = Post.all
end
end
# New action for creating post
def new
#post = Post.new
end
# Create action saves the post into database
def create
#post = Post.new(post_params)
if #post.save
flash[:notice] = "Successfully created post!"
redirect_to post_path(#post)
else
flash[:alert] = "Error creating new post!"
render :new
end
end
# Edit action retrives the post and renders the edit page
def edit
end
# Update action updates the post with the new information
def update
#post = Post.find(params[:id])
if #post.update_attributes(post_params)
flash[:notice] = "Successfully updated post!"
redirect_to posts_path(#posts)
else
flash[:alert] = "Error updating post!"
render :edit
end
end
# The show action renders the individual post after retrieving the the id
def show
end
# The destroy action removes the post permanently from the database
def destroy
#post = Post.find(params[:id])
if #post.present?
#post.destroy
flash[:notice] = "Successfully deleted post!"
redirect_to posts_path
else
flash[:alert] = "Error updating post!"
end
end
private
def post_params
params.require(:post).permit(:title, :body, category_ids: [])
end
def find_post
#post = Post.find(params[:id])
end
end
index.html.erb
<div class="container">
<div class="col-sm-10 col-sm-offset-1 col-xs-12">
<% #posts.each do |post| %>
<div class="col-xs-12 text-center">
<div class="text-center">
<h2><%= post.title %></h2>
<h6><%= post.created_at.strftime('%b %d, %Y') %></h6>
</div>
<div>
<%= raw post.body.truncate(358) %>
</div>
<div class="text-center">
<%= link_to "READ MORE", post_path(post) %>
</div>
<% if admin_signed_in? %>
<%= link_to "Show", post_path(post), class: "btn btn-primary" %>
<%= link_to "Edit", edit_post_path(post), class: "btn btn-default" %>
<%= link_to "Delete", post_path(post), class: "btn btn-danger", data: {:confirm => "Are you sure?"}, method: :delete %>
<% end %>
<hr />
</div>
<% end %>
</div>
</div>
show.html.erb
<div class="col-sm-11 col-xs-12 blog-content">
<h2 class="text-center"><%= #post.title %></h2>
<h1 class="text-center"><%= #category.name %></h1>
<h5 class="text-center"><%= #post.created_at.strftime('%b %d, %Y') %></h5>
<div class="text-center"><%= raw #post.body %></div>
</div>
_navigation.html.erb(part of it)
<ul class="nav navbar-nav navbar-left">
<% Category.all.each do |cat| %>
<li class="text-center"><%= link_to cat.name, posts_path(category: cat.name) %></li>
<% end %>
</ul>
Just in case, schema.rb
ActiveRecord::Schema.define(version: 2018_11_07_082317) do
create_table "admins", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "avatar"
t.index ["email"], name: "index_admins_on_email", unique: true
t.index ["reset_password_token"], name: "index_admins_on_reset_password_token", unique: true
end
create_table "categories", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "categories_posts", id: false, force: :cascade do |t|
t.integer "category_id"
t.integer "post_id"
end
create_table "categorizations", force: :cascade do |t|
t.integer "post_id"
t.integer "category_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "ckeditor_assets", force: :cascade do |t|
t.string "data_file_name", null: false
t.string "data_content_type"
t.integer "data_file_size"
t.string "type", limit: 30
t.integer "width"
t.integer "height"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["type"], name: "index_ckeditor_assets_on_type"
end
create_table "posts", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
In show view you have
<h1 class="text-center"><%= #category.name %></h1>
But you don't define #category in show action. If you want to list categories, it should be
<h1 class="text-center"><%= #post.categories.pluck(:name).join(', ') %></h1>
Btw, looks like you have useless table categories_posts in the schema.rb
Update:
About index action - you should change query for #posts, since the post doesn't have a category column, but he has categories association:
def index
if params.has_key?(:category)
# you can remove #category defining if you don't need it somewhere in view
#category = Category.find_by_name(params[:category])
#posts = Post.joins(:categories).where(categories: { name: params[:category] } )
else
#posts = Post.all
end
end
Note, it is better to use id for the query, not name, searching by id is faster. You need to change link in navbar to link_to cat.name, posts_path(category: cat.id) for it and replace name with id in the query. And it is better to move the whole query to named scope in the Post model.

Creating form for an object which has an association

I have two models: project and todo. Project has many todos.
So I wanna create a form, where I select project category from the combobox and then I add a todo to it.
For instance:
I have following categories: family, work, study.
In form in the combobox I select 'study', and then in textfield I spell a todo like 'make homework for monday' and press submit button.
project.rb
class Project < ActiveRecord::Base
has_many :todos
end
todo.rb
class Todo < ActiveRecord::Base
belongs_to :project
end
my data schema:
create_table "projects", force: :cascade do |t|
t.string "title"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "todos", force: :cascade do |t|
t.string "text"
t.boolean "isCompleted"
t.integer "project_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
_form.html.erb
<%= form_for #project do |f| %>
<div class="form_control">
<%= f.select :title, options_for_select([["Work", "w"],
["Family", "f"],
["Study", "f"],
["TheRest", "t"]]) %>
</div>
<div class="form_control">
*** HERE I NEED TO FIGURE OUT HOW TO ADD SOME DATA TO todo.text ***
</div>
<div class="form_control">
<%= f.submit 'Add' %>
</div>
<% end %>
this is how I show all the projects with their todos:
<% #projects.each do |project| %>
<h2> <%= project.title %> </h2>
<% project.todos.all.each do |todo| %>
<p><%= todo.text %> <%= check_box('tag', todo.__id__, {checked: todo.isCompleted}) %></p>
<% end %>
<% end %>
GitHub link : https://github.com/NanoBreaker/taskmanager
In your todo form, you could have a select box to choose the project the todo belongs to:
# todos/_todo_form.html.erb
<%= select_tag "project_id", options_for_select(Project.pluck(:title, :id)) %>
And in your todos_controller create action:
def create
#project = Project.find(params[:project_id])
#todo = #project.todos.new(todo_params)
if #todo.save
# success
else
# error
end
end
finally, permit the project_id in todo_params:
def todo_params
params.require(:todo).permit(:text, :project_id) # add any other attributes you want
end

Rails has_many :through, form with checkboxes not working

I've been going at this for about 5 hours now and have tried just about everything. I'm a front-end dev with limited rails experience so I could just be 100% off base.
Here are my models:
class Apartment < ActiveRecord::Base
validates :name, :amenities, presence: true
has_many :apartment_amenities
has_many :amenities, through: :apartment_amenities
accepts_nested_attributes_for :amenities
end
class Amenity < ActiveRecord::Base
validates :name, presence: true
has_many :apartment_amenities
has_many :apartments, through: :apartment_amenities
end
class ApartmentAmenity < ActiveRecord::Base
belongs_to :apartment
belongs_to :amenity
end
schema:
create_table "amenities", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "apartment_amenities", force: :cascade do |t|
t.integer "apartment_id"
t.integer "amenity_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "apartment_amenities", ["amenity_id"], name: "index_apartment_amenities_on_amenity_id", using: :btree
add_index "apartment_amenities", ["apartment_id"], name: "index_apartment_amenities_on_apartment_id", using: :btree
create_table "apartments", force: :cascade do |t|
t.string "name"
t.string "address"
t.string "website"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Apartment controller:
class Admin::ApartmentsController < AdminController
before_action :set_apartment, only: [:edit, :update, :destroy]
def new
#apartment = Apartment.new
end
def create
#apartment = Apartment.new(apartment_params)
respond_to do |format|
if #apartment.save
format.html { redirect_to apartments_path, notice: 'Apartment was successfully created.' }
format.json { render :show, status: :created, location: #apartment }
else
format.html { render :new }
format.json { render json: #apartment.errors, status: :unprocessable_entity }
end
end
end
private
def set_apartment
#apartment = Apartment.find(params[:id])
end
def apartment_params
params.require(:apartment).permit(:name, :address, :website, amenities_attributes: [:id])
end
end
and last but not least the new apartment form
<%= form_for([:admin, #apartment]) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.collection_check_boxes(:amenities, Amenity.all, :id, :name ) %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Everything looks right when I load the page up and the apartments are saved but the amenities aren't actually getting saved. Thanks for taking a look.
Its because your form fields don't match your strong params. Look at the source code of your form. I suspect you'll find that the n checkboxes look something like
apartment[amenities]
But your strong params has the amenities_attributes as a nested hash. Look at the params hash in the logs and you'll see how the form data is formatted. You'll need to change the form to use a fields_for or change the strong params
params.require(:apartment).permit(:name, :address, :website, amenities: [])
I think you need to permit the name for amenities_attributes as follows:
def apartment_params
params.require(:apartment).permit(:name, :address, :website, amenities_attributes: [:id, :name])
end

How do I save my child records from the parent controller?

I have a bunch of 'kid' objects saved already and I want to create a parent object which is linked to the kids via a 'relative' model.
This object gives me a many-to-many, through relatives.
To be clear: the user visits the 'parents' page, clicks create parents and is presented with a form that lets them name the parent and add up to four children to this parent (by creating 'relatives'), each of these 'relations' is also named - that's an important part. So, I could name the relation 'step son' or 'son', for instance.
Here's the code I have so far:
class Kid < ActiveRecord::Base
has_many :relatives
has_many :parents, through: :relatives
end
class Parent < ActiveRecord::Base
has_many :relatives
has_many :kids, through: :relatives
accepts_nested_attributes_for :relatives,
:reject_if => lambda { |a| a[:content].blank? },
:allow_destroy => true
end
class Relative < ActiveRecord::Base
belongs_to :parent
belongs_to :kid
end
class ParentsController < ApplicationController
before_action :set_parent, only: [:show, :edit, :update, :destroy]
before_action :lookup_kids, only: [:new, :edit]
# GET /parents
# GET /parents.json
def index
#parents = Parent.all
end
# GET /parents/1
# GET /parents/1.json
def show
end
# GET /parents/new
def new
#parent = Parent.new
4.times { #parent.relatives.build }
end
# GET /parents/1/edit
def edit
end
# POST /parents
# POST /parents.json
def create
#parent = Parent.new(parent_params)
parent_params[:relatives_attributes].each do |k,r|
#parent.relatives.build(r.except(:_destroy))
end
respond_to do |format|
if #parent.save
format.html { redirect_to #parent, notice: 'Parent was successfully created.' }
format.json { render :show, status: :created, location: #parent }
else
format.html { render :new }
format.json { render json: #parent.errors, status: :unprocessable_entity }
end
end
end
# cut for brevity.
private
# Use callbacks to share common setup or constraints between actions.
def set_parent
#parent = Parent.find(params[:id])
end
def parent_params
params.require(:parent).permit(:name,
relatives_attributes: [:parent_id, :kid_id, :relationship, :_destroy])
end
def lookup_kids
#kids = Kid.all #for this nursery.
end
end
<%= form_for(#parent) do |f| %>
<% if #parent.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#parent.errors.count, "error") %> prohibited this parent from being saved:</h2>
<ul>
<% #parent.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<h4>Kids:</h4>
<%= f.fields_for :relatives do |r| %>
<%= r.label :kid %>
<%= r.collection_select :kid_id,
#kids, :id, :name, include_blank: true%>
<%= r.label :relationship %>
<%= r.text_field :relationship %>
<%= r.check_box :_destroy %>
<%= r.label :_destroy, "Remove" %>
<br/>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
ActiveRecord::Schema.define(version: 20151030113634) do
create_table "kids", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "parents", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "relatives", force: :cascade do |t|
t.string "relationship"
t.integer "parent_id"
t.integer "kid_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "relatives", ["kid_id"], name: "index_relatives_on_kid_id"
add_index "relatives", ["parent_id"], name: "index_relatives_on_parent_id"
end
When I get to 'create' in the parents controller, I can see the right parameters are getting through but the relationship records aren't being saved. SHouldn't this happen automatically?
I've tried looping through the :relatives_attributes but that doesn't seem to work with 'build'.
How am I suppsed to get the 'relatives' records to save?
EDIT: adding parameters posted:
parent"=>{
"name"=>"Dad",
"relatives_attributes"=>{
"0"=>{"kid_id"=>"2", "relationship"=>"Son", "_destroy"=>"0"},
"1"=>{"kid_id"=>"", "relationship"=>"", "_destroy"=>"0"},
"2"=>{"kid_id"=>"", "relationship"=>"", "_destroy"=>"0"},
"3"=>{"kid_id"=>"", "relationship"=>"", "_destroy"=>"0"}}}
Edit: I've updated this to show my latest edit - note the 'parent_params[:relatives_attributes].each do |k,r|' in the controller. This now saves the kid records but the only problem is, it also saves the fields that are blank! So I have 'relative' records with null values for kid records. How can I stop it saving empty fields (or creating empty relative records)?
The answer was to build each sub-record of relative, like so:
parent_params[:relatives_attributes].each do |k,r|
#parent.relatives.build(r.except(:_destroy))
end
Before calling #parent.save.
However, I'm still having issues getting rid of the blank records. So if anyone has an answer to that problem, please comment here - or if there's a better or more traditional way of doing this, hit me up. Follow up question here: Why is this reject_if in my model not rejecting blank records?
You are almost there, depending upon how your form submission is, you most likely need an accepts_nested_attribute_for in your Relative associative class as well:
class Relative
belongs_to :parent
accepts_nested_attributes_for :parent
belongs_to :kid
accepts_nested_attributes_for :kid
end
If this doesn't work, then please submit your params that are passed into the controller and we can adjust accordingly

Rails: How to get one model to "belong" to another model?

I have a model "votes" which belongs_to two models by polymorphous association, and has the attributes user_id and comment_id. Previously, I had a voting system in place for users that would create a new vote for a specific user every time a button was pressed:
<%= form_for [#user, #vote] do |f| %>
<input type="hidden" id="user_id" name="user_id" value="#{#user.id}" />
<%= f.submit ": )", :onclick => 'alert("Voted up!")' %>
<% end %>
and #user.votes.count would return the number of times the button was pressed. However, I switched to a different method:
View:
<%= link_to "voteuser", vote_user_path(#user.id), method: :post, :class => "btn btn-small" %>
Controller:
def vote
#user = User.find(params[:id])
Vote.create!(user_id: #user.id)
redirect_to #user
end
Routes:
Website::Application.routes.draw do
root 'home_page#home'
get "votes/new"
get 'users/random'
post 'users/vote/:id' => 'users#vote', as: 'vote_user'
get 'users/users/random' => 'users#random'
resources :users
get "all/allusers"
get "all/users/new" => 'users#new'
get 'all/all/allusers' => 'all#allusers'
end
and a Vote is still created, with a user_id equal to the current User.id, but now #user.votes.count returns 0, so the application isn't registering that the vote belongs to the user. How can I remedy this?
Vote Model:
class Vote < ActiveRecord::Base
belongs_to :voteable, polymorphic: true
end
Votes Schema:
create_table "votes", force: true do |t|
t.integer "thing_id"
t.integer "comment_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "voteable_id"
t.string "voteable_type"
end
User Model:
class User < ActiveRecord::Base
has_many :votes, as: :voteable
end
User Schema:
create_table "users", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
end
It should be Vote.create!(voteable_id: params[:id], voteable_type: 'User') for users or in short
Vote.create!(vote: User.find(params[:id])) # Vote.create!(vote: Comment.find(params[:id]))
check if you retrieve the user id in your controller and you can in your view
<%= form_for [#user, #vote] do |f| %>
<%= f.hidden_field :user_id, value: f.model.user.id %>
<%= f.submit ": )", :onclick => 'alert("Voted up!")' %>
<% end %>
on your controller you can simply do:
def vote
Vote.create!(user_id: params[:id])
redirect_to #user
end
but you use the polymorphous association so you have to specify the user_id and the user_type.

Resources