I have 2 model with the association many_to_many. When creating a brand I have error:
NameError in Admin::Brands#new
Showing /.../app/views/admin/brands/_form.html.slim where line #3 raised:
uninitialized constant Brand::BrandCatalog
What am I doing wrong?
#app/models/category.rb
class Category < ActiveRecord::Base
has_many :brand_catalogs
has_many :brands, through: :brand_catalogs
end
#app/models/brand.rb
class Brand < ActiveRecord::Base
has_many :brand_catalogs
has_many :categories, through: :brand_catalogs
end
#app/models/brandcatalog.rb
class BrandCatalog < ActiveRecord::Base
belongs_to :category
belongs_to :brand
end
migration
#db/migrate/20151230092013_create_brand_catalogs.rb
class CreateBrandCatalogs < ActiveRecord::Migration
def change
create_table :brand_catalogs, id: false do |t|
t.integer :category_id
t.integer :brand_id
end
add_index :brand_catalogs, [:category_id, :brand_id]
end
end
brands controller
#app/controllers/admin/brands_controller.rb
class Admin::BrandsController < Admin::BaseController
before_action :require_login
load_and_authorize_resource
before_action :load_brand, only: [:edit, :update, :destroy]
def index
#brands = Brand.all
end
def new
#brand = Brand.new
end
def edit
end
def create
#brand = Brand.create(brand_params)
if #brand.save
redirect_to admin_brands_path, notice: 'Brand was successfully created.'
else
render :new, notice: 'Something wrong!'
end
def update
end
def destroy
end
private
def load_brand
#brand = Brand.find(params[:id])
end
def brand_params
params.require(:brand).permit(:title, {category_ids: []})
end
end
brands form
# views/admin/brands/_form.html.slim
= bootstrap_form_for [:admin, #brand] do |f|
div class='form-group'
= f.collection_check_boxes(:category_ids, Category.all, :id, :title)
div class='form-group'
= f.text_field :title, class: 'form-control'
= f.submit 'Save', class: 'btn btn-success'
Try renaming your #app/models/brandcatalog.rb file to brand_catalog.rb. Modelnames needs to have matching filenames but with underscore instead of camelcase.
For example a model called ThisIsMyModel should have its file named this_is_my_model.rb
Related
in my app I'm creating the categories in admin(so the admins creates, updates and destroys the categories name) and then when the users will create the posts they will select( or I'm thinking for switch with checkbox) a category for the posts.
I decide to do this implementation with a has many through for posts and categories. But I' having doubts for implements:
the post_params;
the methods for add the categories and then destroy the categories
and the parts for create, update and destroy the posts.
How can I implement this? It's a better way do different? So if someone help me with this I will appreciate.
Post.rb
class Post < ActiveRecord::Base
has_many :categorizations
has_many :categories, through: :categorizations
...
def add_category(category)
categorizations.create(category_id: category.id)
end
def remove_category(category)
categorizations.find_by(category_id: category.id).destroy
end
category.rb
class Category < ActiveRecord::Base
has_many :categorizations
has_many :posts, through: :categorizations
validates :name,
presence: true,
length: { minimum: 3, maximum: 30 },
uniqueness: true
end
categorization.rb
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
validates :post_id, presence: true
validates :category_id, presence: true
end
controllers/admin/PostsController
class Admin::PostsController < Admin::ApplicationController
def new
#post = Post.new
#categories = Category.all.map{ |c| [c.name, c.id]}
end
def create
#post = Post.new(post_params)
#post.author = current_user
#post.categories << params[:category_id]
if #post.save
flash[:notice] = "Post has been created."
redirect_to #post
else
flash[:alert] = "Post has not been created."
render "new"
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
flash[:notice] = "Post has been deleted."
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:title,
:subtitle,
:content,
:attachment,
:attachment_cache,
:remote_attachment_url,
:categorizations_attributes => [:id,
:post_id,
:category_id,
:_destroy,
:categories_attributes => [:id,
:name]
]
)
end
end
controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: [:show, :edit, :update]
def index
#posts = policy_scope(Post)
end
def show
authorize #post, :show?
end
def edit
authorize #post, :update?
end
def update
authorize #post, :update?
if #post.update(post_params)
flash[:notice] = "Post has been updated."
redirect_to #post
else
flash.now[:alert] = "Post has not been updated."
render "edit"
end
end
private
def set_post
#post = Post.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The post you were looking for could not be found."
redirect_to posts_path
end
def set_category
#category = Category.find(params[:category_id])
end
def post_params
params.require(:post).permit(:title, :subtitle, :content, :attachment, :attachment_cache, :remove_attachment, :remote_attachment_url, :category_id)
end
end
posts/_form.html.slim
= simple_form_for([:admin, #post], :html => { :multipart => true }) do |f|
= select_tag(:category_id, options_for_select(#categories), :prompt => "Select ad Category")
routes
Rails.application.routes.draw do
namespace :admin do
root 'application#index'
resources :posts, only: [:new, :create, :destroy]
resources :categories
resources :users do
member do
patch :archive
end
end
end
devise_for :users
root "posts#index"
resources :posts, only: [:index, :show, :edit, :update]
end
In your form
= select_tag(:category_ids, options_for_select(#categories), :prompt => "Select ad Category", multiple: true)
In your controller
params.require(:post).permit(:title, :subtitle, :content, :attachment, :attachment_cache, :remove_attachment, :remote_attachment_url, :category_ids)
Also need to give some advise, in your posts_controller.rb
remove below line
rescue ActiveRecord::RecordNotFound
Insterad of this write it in application_controller.rb , so it will work for whole application.
What I do for fix was put on the PostsController this:
class Admin::PostsController < Admin::ApplicationController
before_action :set_categories, only: [:new, :create]
.
.
.
private
def set_categories
#categories = Category.all.select(:id, :name)
end
def post_params
params.require(:post).permit(:title,
:subtitle,
:content,
:attachment,
:attachment_cache,
:remote_attachment_url,
category_ids:[]
)
end
And on the form I changed for use checkboxes and with that I can select and add more categories for a Post:
= f.association :categories, label: "Select the Categories", as: :check_boxes , collection: #categories.map{|c| [c.name, c.id]}, include_hidden: false
Add on the class Post and Category a dependent: :destroy for destroy the joins properly.
has_many :categorizations, dependent: :destroy
has_many :categories, through: :categorizations
And was necessary delete the validation for post_id on categorization so I just comment because when was uncommented when I try to create a Post it wasn't possible, So I do this:
class Categorization < ActiveRecord::Base
belongs_to :post
belongs_to :category
#validates :post_id, presence: true
validates :category_id, presence: true
end
And work's!
I am new to Ruby on Rails.I am facing a problem using nested resources.
I am building a learning app where there are courses and lessons.
Every course will have many lessons and a lesson belongs to only one course.
I am unable to create a lesson for a course currently.
Example : http://localhost:3000/courses/19/lessons/new is a page where i want to create and display lessons for course 19.
Routes.rb
Rails.application.routes.draw do
devise_for :users
resources :courses
resources :courses do
resources :lessons
end
resources :lessons
root 'pages#landing'
get 'pages/home' => 'pages#home' ,as: :home
get '/user/:id' => 'pages#profile',as: :profile
get '/users' => 'courses#index',as: :user_root
end
Course.rb
class Course < ActiveRecord::Base
belongs_to :user
has_many :lesson
validates :user_id , presence: true
end
Lesson.rb
class Lesson < ActiveRecord::Base
belongs_to :course
validates :course_id , presence: true
end
CourseController.rb
class CoursesController < ApplicationController
def index
#courses = Course.all;
end
def new
#course = Course.new;
end
def create
#course = Course.new(course_params);
#course.user_id = current_user.id;
if #course.save
redirect_to course_path(#course)
else
flash[:notice]="Course could not be created ! "
redirect_to new_course_path
end
end
def edit
end
def update
end
def destroy
#course = Course.find(params[:id]);
#course.destroy;
end
def show
#course = Course.find(params[:id]);
end
private
def course_params
params.require(:course).permit(:title, :description, :user_id)
end
end
LessonController.rb
class LessonsController < ApplicationController
def index
#lessons = Lesson.all;
end
def new
#lesson = Lesson.new;
end
def create
#lesson = Lesson.new(lesson_params);
#course = Course.find_by(id: [params[:course_id]]);
if #lesson.save
redirect_to new_course_lesson_path , flash[:notice] = "Lesson successfully saved !"
else
redirect_to new_course_lesson_path , flash[:notice] = "Lesson cannot be created ! "
end
end
def show
#lesson = Lesson.find(params[:id])
end
private
def lesson_params
params.require(:lesson).permit(:title,:description,:video,:course_id)
end
end
Lessonform.html.erb
<%= form_for ([#course,#lesson]) do |f| %>
<%= f.label :lesson_Title %>
<%= f.text_field :title ,placeholder: "Enter the lesson Title" ,:class=>"form-control" %><br />
<%= f.label :Description %>
<%= f.text_area :description ,placeholder: "Enter the lesson Description",rows:"8",:class=>"form-control" %><br />
<center>
<%= f.submit "Create lesson",:class =>"btn btn-lg btn-primary" %>
</center>
<% end %>
One problem i see is that you have defined route resources :lessons twice. Once, inside courses scope and second time outside.
The error seems to occur because in your view #course is nil. So, please check you set #course in a before_action inside lessons_controller#new action.
EDIT
class LessonsController < ApplicationController
before_action :set_course, only: [:new, :create]
def new
#lesson = #course.lessons.build
end
private
def set_course
#course = Course.find_by(id: params[:course_id])
end
end
Also replace has_many :lesson with has_many :lessons inside Course model.
First change you need to make in your Course model as you have singular lesson when defining many association:
has_many :lessons
Also let me know if their are any chances of lessons page being called without courses? If no then please remove:
resources :lessons
I guess also the two defining of courses in routes in creating issue. Please try removing the:
resources :courses
Let me know if you still face any issue.
I'm somewhat of a newbie with ruby on rails and went off of samurails.com single table inheritance with rails 4 tutorial to add different comment types. This worked great but the problem I'm running into is when I try to use polymorphic associations to get comments and the specific type to function under other models such as project and challenge. A regular comment works, but the specific types do not.
I haven't seen anything that clearly says how to make this work or another option of going about it so any help would be greatly appreciated.
class Comment < ActiveRecord::Base
has_merit
acts_as_votable
belongs_to :commentable, :polymorphic => true
belongs_to :user
belongs_to :commenttype
belongs_to :project
def self.types
%w(Question Idea Problem)
end
def commentable_type=(sType)
super(sType.to_s.classify.constantize.base_class.to_s)
end
scope :questions, -> {where(type: 'Question')}
scope :ideas, -> {where(type: 'Idea')}
scope :problems, -> {where(type: 'Problem')}
end
class Question < Comment
end
class Idea < Comment
end
class Problem < Comment
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :comments, :as => :commentable, :class_name => "Comment"
has_many :questions, :as => :commentable, :class_name => "Question"
has_many :ideas, :as => :commentable, :class_name => "Idea"
has_many :problems, :as => :commentable, :class_name => "Problem"
delegate :questions, :ideas, :problems, to: :comments
end
class CommentsController < ApplicationController
before_action :set_commentable, only: [:index, :new, :create]
before_action :set_type
before_action :set_comment, only: [:show, :edit, :update, :destroy]
def index
#comments = type_class.all
end
def show
end
def new
#comment = type_class.new
end
def edit
end
def create
#comment = #commentable.comments.new(comment_params)
#comment.user = current_user
if #comment.save
redirect_to :back, notice: "#{type} was successfully added."
else
render action: 'new'
end
end
def update
if #comment.update(comment_params)
redirect_to #comment.commentable, notice: "#{type} was successfully updated."
else
render action: 'edit'
end
end
def destroy
#user = current_user
#comment = #commentable.comments.where(comment_user: current_user).first
#commentable.comment.destroy
respond_to do |format|
format.html { redirect_to #commentable, notice: "Comment was deleted." }
format.js
end
end
private
def set_comment
#comment = type_class.find(params[:id])
end
def set_type
#type = type
end
def type
Comment.types.include?(params[:type]) ? params[:type] : "Comment"
end
def type_class
type.constantize
end
def set_commentable
#commentable = find_commentable
end
# add more commentable models here
def find_commentable
if params[:challenge_id]
Challenge.find(params[:challenge_id])
else
end
end
def find_commentable
if params[:project_id]
Project.find(params[:project_id])
else
end
end
def comment_params
params.require(type.underscore.to_sym).permit(:body, :type, :user_id, :commentable_id, :commentable_type, :commentable, :comment_type)
end
end
module CommentsHelper
def sti_comment_path(type = "comment", comment = nil, action = nil)
send "#{format_sti(action, type, comment)}_path", comment
end
def format_sti(action, type, comment)
action || comment ? "#{format_action(action)}#{type.underscore}" : "#{type.underscore.pluralize}"
end
def format_action(action)
action ? "#{action}_" : ""
end
end
<%= form_for [commentable, Comment.new], :html => { :multipart => true } do |f| %>
<%= f.text_area :body, class: "form-control", placeholder: "What's on your mind?" %>
<%= f.label :type %><br>
<%= f.select :type, Comment.types.map {|r| [r.humanize, r.camelcase]}, {}, disabled: #type != "Comment" %>
<%= f.submit "Post", class: "btn pull-right" %>
I'm building a rails app Where I have users who belong to a school that has many classrooms.
users create a pin in an associated classroom and use a code to ensure they're associating the pin in the correct classroom. Right now I have this working with a text field but I need to switch to a drop down that lists all the classroom codes. I'm having trouble figuring this out. (new to RoR).
Here's the part of the form I need to change form for creating a pin:
<div class="form-group">
<%= label_tag(:classroom, "Enter your classroom code:") %>
<input type="text" name="pin[code]">
</div>
Classroom model:
class Classroom < ActiveRecord::Base
belongs_to :school
belongs_to :teacher, :class_name => "User"
has_and_belongs_to_many :users
has_many :pins
has_many :reflections
validates_presence_of :school
validates_presence_of :teacher
validates :code, :uniqueness => { :scope => :school_id }
end
Pin Model
class Pin < ActiveRecord::Base
belongs_to :user
belongs_to :classroom
has_and_belongs_to_many :emotions
validates_presence_of :user
validates_presence_of :classroom
end
Pin controller
class PinsController < ApplicationController
before_action :set_pin, only: [:show, :edit, :update, :destroy]
respond_to :html
def search
index
render :index
authorize #pins
end
def home
#pins = Pin.all
respond_with(#pins)
authorize #pins
end
def show
respond_with(#pin)
end
def new
#pin = Pin.new
#emotions = Emotion.all
#school = School.find(params[:school])
respond_with(#pin)
authorize #pin
end
def edit
end
def create
code = params[:pin][:code]
#classroom = Classroom.where('code LIKE ?', code).first
unless #classroom
flash[:error] = "Classroom code incorrect"
render :new
else
params[:pin][:classroom_id] = #classroom.id
end
#pin = Pin.new(pin_params)
#pin.save
params[:pin][:emotion_ids].each do |emotion_id|
#emotion = Emotion.find(emotion_id)
#pin.emotions << #emotion
end
if #pin.save
redirect_to signout_path and return
end
respond_with(#pin)
authorize #pin
end
def update
#pin.update(pin_params)
respond_with(#pin)
authorize #pin
end
def destroy
#pin.destroy
respond_with(#pin)
authorize #pin
end
end
You can use select_tag helper to create a dropdown select box in rails. You can do something like this:
<div class="form-group">
<%= label_tag(:classroom, "Select your classroom code:") %>
<%= select_tag "pin[code]", options_from_collection_for_select(Classroom.all, "code", "code") %>
</div>
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