I'm building a questionnaire app.
for now I've 3 models
1- Exam -> has_many :questions
2- Question -> has_many :answers && belongs_to :exam
class QuestionsController < ApplicationController
def index
#questions = Question.all
end
def show
exam = Exam.find(params[:exam_id])
#question = exam.questions.find(params[:id])
end
def new
exam = Exam.find(params[:exam_id])
#question = exam.questions.build
#question = Question.new
#question.answers.build
end
def create
exam = Exam.find(params[:exam_id])
#question = exam.questions.create(question_params)
if #question.save
redirect_to #question.exam, notice: "Exam created!"
else
render :new
end
end
def destroy
#question = Question.find(params[:id])
#question.destroy
redirect_to #question.exam
end
private
def question_params
params.require(:question).permit(:title,:timer,:exam_id,answers_attributes:[:title,:correct, :question_id], :sort => [])
end
end
3- Answer -> belongs_to :question
I can add an exam, and add questions to the exam and add answers for each question. (through nested form)
So for now the admin is the only user who can add exams,questions,answers. I want make the exam visible to other users so the can take and exam and see the result.
I thought about making a 4th model for submission that belongs to Exam where users can see the questions with the options and chose the option that they want and submitting it. After the submit they get a page with the result if the pass the exam or not!
BUT HOW TO DO IT?
UPDATE!!!
The code above works fine!. I'm only searching for some way to make the exam visible or takable for the users
<%= form_for [#exam, #submission] do |f| %>
<% #questions.each do |question| %>
<p><%= question.title %></p>
<ul>
<% question.answers.each do |answer| %>
<li>
<%= #chosen_option = answer.title %>
<%= f.fields_for :option do |o| %>
<%= render 'option_fields', :f => o %>
<% end %>
</li>
<% end %>
</ul>
<% end %>
<br>
<br>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.submit "Submit", class: "btn btn-primary " %>
<% end %>
First, you need to work some more on your models.
I am assuming that every question has at least one correct answer (unless you are building a survey tool). If that is the case, you also need the notion of correct answer(s) to be encapsulated in the models.
Case 1: Single correct answer per question
Add a correct_answer_id to the question model.
Case 2: Multiple correct answers per question
If a question can have multiple correct answers you need to create a join table/model
# migration for model CorrectAnswer
create_table :correct_answers do |t|
t.integer question_id
t.integer answer_id #answer_id is the id of the answer selected by a user
end
# Also update the Question class
class Question < ActiveRecord::Base
...
has_many :correct_answers
...
end
Note: I will recommend the second option even if you are planning on a single correct answer. This will allow your models to be flexible for any future requirements.
For creating submissions, you need to create a new Model called Submission. The fields of the submission model with following fields
#migration for Submission model
create_table :submissions do |t|
t.integer user_id
t.integer exam_id
t.integer question_id
t.integer answer_id
end
You will need to create a corresponding Submissions controller which will allow you non-admin users to submit answers to an exam.
Do not forget to create appropriate index for the tables
So, I already solved this problem by changing the questions model.
I added a answers array to the question model
create_table "questions", force: :cascade do |t|
t.string "answers", default: [], array: true
end
changed also something in the view of the form of submission
<% question.answers.each_with_index do |answer, index| %>
<p><%= answer %><span>
<%= radio_button_tag 'submission[answers][]',"#{answer}", id: "#{answer}", class: "form-control"%>
</span></p>
<% end %>
The problem now is that I get the questions like this
radio_button
I can only select one radio button! So if I select answer_A from question 1 and then I select answer_B from question 2. Question 1 then will be deselect?
Related
I don't know how to title my question, I'm sorry haha. I am building an app where users sign up as authors or reviewers. Authors upload their books which belong to genres. Users that are reviewers find the books they want to review by genre. When I create a book resource with an author user, how would I create a way of selecting multiple genres that book belongs to in the new book form??
I am learning ruby on rails development and am very early in my understanding of it. I so far have devise for my app and am using enum to create roles for users when they sign up so that they're either and author or reviewer. I've also made a users controller so that when they sign up they get redirected either to an author profile or reviewer profile. Next step is figuring out how to create genres that books belong to. Any suggestions would be helpful. Thanks
Let's start with the database. We'll create a has-and-belongs-to-many relation,
create_table :books do |t|
t.string :name
t.timestamps
end
create_table :genres do |t|
t.string :name
t.timestamps
end
create_table :books_genres do |t|
t.references :book, foreign_key: true
t.references :genre, foreign_key: true
end
add_index :books_genres, [:book_id, :genre_id], unique: true
Now to add association for the models:
# app/models/book.rb
class Book < ApplicationRecord
has_and_belongs_to_many :genres
end
# app/models/genres.rb
class Genre < ApplicationRecord
has_and_belongs_to_many :books
end
As for the User Interface, we could use standard HTML Multiple Select, but it's really not user friendly, so we'll go with checkboxes.
# config/routes.rb
resources :books # this is too much, but you will probably use other actions anyway.
# app/controllers/books_controller.rb
def new
#book = Book.new
#genres = Genre.all
end
# app/views/books/new.html.erb
<%= form_with(model: #book, local: true) do |form| %>
<%= form.label :name %>
<%= form.text_field :name %>
<% #genres.each_with_index do |genre, i| %>
<%= check_box_tag "book[genre_ids][#{i}]", genre.id %>
<%= label_tag "book[genre_ids][#{i}]", genre.name %>
<% end %>
<%= form.submit %>
<% end %>
Now we have to do some magic in the controller as we'll receive the genre_ids as a hash ({"0" => "1", "1" => "3"}), this will transform that to [1, 3]
# app/controllers/books_controller.rb
def book_params
params[:book][:genre_ids] = (params[:book][:genre_ids] || {}).values.map(&:to_i)
params.require(:book).permit(:name, genre_ids: [])
end
And finally we can have our action to create books
# app/controllers/book_controller.rb
def create
#book = Book.new(book_params)
if #book.save
redirect_to #book
else
render :new
end
end
To see any checkboxes, you'll need to have your genres in database. You can easily do this through rails console:
%w[Adventure Tragedy Fantasy].each { |g| Genre.create(name: g) }
Checkboxes might not be the best user interface for that either, you may want to google for user friendly multiple selects, but this should explain you the basics of what you're trying to achieve.
I'm pretty new to Rails and I couldn't find a solution to this problem (I hope I haven't missed something obvious).
how can I make my application accept blank fields, but not saving the empty value to the db?
I mean, if I do
validates :name, presence: true
I will get an error message, that name can't be blank.
But I just want it to ignore blank/nil with not saving it into db.
How would I do that?
(I hope asked that question comprehensible, tell me if not)
Update:
ok, so here is my code:
Models:
class Voting < ApplicationRecord
has_many :options
accepts_nested_attributes_for :options
validates :content, presence: true
end
and
class Option < ApplicationRecord
belongs_to :voting
validates_associated :voting
validates :vote, presence: { message: "at least two Options required." }, if: :option_counter
def option_counter
voting.options.count < 2
end
end
OptionsController:
class OptionsController < ApplicationController
def index
#voting = Voting.find(params[:voting_id])
#options = #voting.options
end
def create
#voting = Voting.find(params[:voting_id])
#option = #voting.options
#option.create(option_params)
if #voting.valid?
flash[:notice] = "Voting created!"
redirect_to voting_options_path
else
render 'votings/show'
end
end
private
def option_params
...
end
end
Views: votings/show.html.erb
<strong>Your Voting:</strong>
<p><%= #voting.content %></p>
<% if #voting.errors.any? %>
<div>
<% #voting.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</div>
<% end %>
<ul>
<%= form_with(model: [ #voting, #voting.options.build ], local: true) do |form| %>
<p>
<%= form.label :vote %><br>
<% 3.times do %>
<input type="string" name="option[][vote]" /><br><br>
<% end %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
</ul>
I think, this should be the interesting stuff. I hope it helps to understand the situation.
Validations occur when saving to the database. So it is possible to accept empty fields even with your validation. But you must provide a value before saving. If you are getting an error it’s because your application is trying to save the record. So your code is not doing what you say (accepting empty fields without saving). You are saving.
The question is. Which line of code raises the error? Check your logs.
Validations are explained here: http://guides.rubyonrails.org/v2.3.11/activerecord_validations_callbacks.html (or any other version)
Edit 1
If you want to prevent this record from saving, but don’t inform the user, you could define a method save_unless_empty
def save_unless_empty
if empty_fields? and new_record?
delete
else
save
end
end
You must define the empty_fields? method so that you check the fields you need.
Edit 2
Thanks for adding the code. I see you are creating many options in the OptionsController#Create. This method is supposed to create only one option. You should create or update many options in VotingsController#create (or #update), given that all options belong to a voting object.
Another issue: You are building a new option object in the form (#voting.options.build), but you are not using it (you should use fields_for :option, #voting.options.build do.... This could be inside the 3.times loop. Even better, this can be done in the controller, before showing the view
And another one. Your validation regarding at least 2 options should be in the Voting model.
Some refactoring (although a lot is still missing):
In the view
<%= form_with #voting do |form| %>
<%= form.label :vote %><br>
<% #options.each do |option| %>
<%= form.fields_for option do |form_opt| %>
<%= form_opt.input_field :vote %><br><br> #question: is vote really the name of the attribute in the option model? or should this be a check_box showing the option name?
<% end %>
<% end %>
<%= form.submit %>
<% end %>
Then you should put the saving logic in VotingsController:
class VotingsController < ApplicationController
def new
#voting = Voting.new
3.times do
#voting.options.build
end
#options = #voting.options
end
def edit
#voting = Voting.find_by(id: params[:id])
#options = #voting.options
end
def create
#voting = Voting.create_without_null_options(voting_params)
#etc
end
def edit
#voting = Voting.find_by(id: params[:id])
#voting.update_without_null_options(voting_params)
#etc
end
private
def voting_params
...
end
end
In the Voting Model
def self.create_without_null_options params
#Create the voting without its options
#voting = Voting.create(params.except[:options_attributes])
#create options but save only non empty options
params[:options_attributes].each do |option_att|
#option = #voting.options.build(option_att)
#option.save_unless_empty
end
end
def update_without_null_options params
#Save the voting without its options
update_attributes(params.except[:options_attributes])
#create options but save only non empty options
params[:options_attributes].each do |option_att|
#option = options.build(option_att)
#option.save_unless_empty
end
end
What that validation is doing is checking that the :name field has a value (if it does not have a value it will show the error message).
If you wanted to allow a blank value then I'm pretty sure you would just need to remove that validation. If a blank value (ie a string of ' ') is being saved in your database then I would check the 'name' column (in the database) to see if null is allowed.
There is no such thing as saving specific database fields. An entire record is stored/received.
If the name field is not required, remove the validates :name, presence: true on the model.
If there's a default value that you want in your database instead of nil/"", you can set it in the migration; for example a single space
add_column :name, :string, :default => " "
If you need to preserve those records and modify that database column, you can use change_column in a new migration
change_column :users, :name, :string, :default => " "
I used users as an example table name, because from your code above I could not understand which table the name column was on.
I want to allow the admin to review the description. So, whenever user posts the description admin can either approve or reject it. If approved it'll display on the index page. At the moment the user can add the description but it can't be reviewed by admin. Is there any way I could do this?
index.html.erb
<% #posts.each do |post| %>
<p> <%= post.description %> </p>
<% end %>
admin/post.rb
ActiveAdmin.register Post do
permit_params :description
index do
id_column
column :description
end
form do |f|
f.inputs do
f.input :description
end
f.actions
end
end
add new boolean field to post called reviewed with default value false:
class AddReviewedToPosts < ActiveRecord::Migration
def self.up
add_column :posts, :reviewed, :boolean, default: false
end
def self.down
remove_column :posts, :reviewed
end
end
then create on AA under index page a button the set it as reviewed:
index do
id_column
column :description
column "" do |post|
link_to 'Mark as reviewed', admin_posts_reviewed_path(post)
end
end
then the last thing create a reviewed action on admin/posts.rb:
def reviewed
#post = Post.find(params[:id])
if #post.present?
#post.update_attribute(:reviewed, true)
flash[:notice] = 'Post marked as reviewed'
else
flash[:error] = 'Could not find post'
end
redirect_to admin_posts_path
end
don't forget to add the action to routes.rb!
now you can display it if the boolean field is true.
code is not tested this is from my head.
I have two models, Companies and Employees, with a many-to-many association between them.
class Company < ActiveRecord::Base
has_and_belongs_to_many :employees
end
class Employee < ActiveRecord::Base
has_and_belongs_to_many :companies
end
I have a join table :companies_employees
class CreateCompaniesEmployeesJoin < ActiveRecord::Migration
def change
create_table :companies_employees, :id => false do |t|
t.integer "company_id"
t.integer "employee_id"
end
add_index :companies_employees, ["company_id", "employee_id"]
end
end
I have a Show view for Company, which includes a form_for adding a new Employee, who I want to associate with that Company via the HABTM association:
<%= form_for :employee, :url => employees_path do |f| %>
<p>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
</p>
<br>
<p>
<%= f.hidden_field :company_id, :value => #company.id %>
</p>
<p>
<%= f.submit "Save Employee", class: "btn btn-default" %>
</p>
<% end %>
I have a controller for Employee, through which I want to create a new Employee that will be automatically associated with the Company from the Company Show view:
def create
#company = Company.find(params[:company_id])
#employee = Employee.new(employee_params)
#company.employees << #employee
if #employee.save
flash[:success] = "Company Employee Added!"
redirect_to #employee
else
render 'new'
end
end
When I use the form to try to create a new employee, I get an error in EmployeeController -- "Couldn't find Company without an ID"
Seems my view is failing to pass the :company_id on to the create action in the EmployeeController.
I've scoured other posts and nothing seems to be on point. Any suggestions most appreciated!
Ok, the problem seems to be the nested attribute.
Try to change in the EmployeesController#create the first row in:
#company = Company.find(params[:employee][:company_id])
EDIT
Alternatively, and probably more easy, you can also change the form hidden_field like this:
hidden_field_tag(:company_id, #company.id)
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
I've built a simple rails app with three models, Posts, Users, and Comments.
I've tried every commenting gem out there and they all have some shortfall.
So I build my own comments system.
Users can comment on Posts. Every comment is votable (using acts_as_votable gem). A users score is made up by the sum total votes they have received on their comments.
Here is what I have in my schema for the comments:
create_table "comments", force: true do |t|
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
t.integer "post_id"
end
In my user model:
class User < ActiveRecord::Base
has_many :comments
acts_as_voter
end
In my post model:
class Post < ActiveRecord::Base
has_many :comments
end
In my comment model:
class Comment < ActiveRecord::Base
belongs_to :post
belongs_to :user
acts_as_votable
end
In my comments controller:
class CommentsController < ApplicationController
def create
post.comments.create(new_comment_params) do |comment|
comment.user = current_user
end
respond_to do |format|
format.html {redirect_to post_path(post)}
end
end
def upvote
#post = Post.find(params[:post_id])
#comment = #post.comments.find(params[:id])
#comment.liked_by current_user
respond_to do |format|
format.html {redirect_to #post}
end
end
private
def new_comment_params
params.require(:comment).permit(:body)
end
def post
#post = Post.find(params[:post_id])
end
end
In my routes file:
resources :posts do
resources :comments do
member do
put "like", to: "comments#upvote"
end
end
end
In my view:
<% #post.comments.each do |comment| %>
<%= comment.body %>
<% if user_signed_in? && (current_user != comment.user) && !(current_user.voted_for? comment) %>
<%= link_to “up vote”, like_post_comment_path(#post, comment), method: :put %>
<%= comment.votes.size %>
<% else %>
<%= comment.votes.size %></a>
<% end %>
<% end %>
<br />
<%= form_for([#post, #post.comments.build]) do |f| %>
<p><%= f.text_area :body, :cols => "80", :rows => "10" %></p>
<p><%= f.submit “comment” %></p>
<% end %>
In the user profile views: (this shows the users score
<%= (#user.comments.map{|c| c.votes.count}.inject(:+))%>
How do I implement threading?(at one level, I'm assuming multiple levels just makes it really messy)
How do I make the threaded comments votable?(both the parents and children) What has to be done with the routes?
How do I ad a simple email notification a user can subscribe to to receive a simple message saying that a new comment has been posted to their thread?
How do I pull the users score calculated by all votes received on comments made by the user including child comments?
If I understand the question correctly, you want to allow comments to be commented on. In that case, in your Comment model you will need a parent_id:integer attribute. Then add the following associations :
class Comment
...
belongs_to :parent, class_name: 'Comment'
has_many :children, class_name: 'Comment', foreign_key: 'parent_id'
...
end
Now you have a tree structure for your comments. This allows comments of comments of comments and so on.
if my_comment.parent.nil? then you are at the root. if my_comment.children.empty? then there are no comments on the comment.
Trees can be expensive to move through, so adding a max depth could be smart.
How do you implement threading? (To answer one of your questions)
Make the comment-to-user association polymorphic, and then you can add a comment-to-comment association the same way.
What was the 'shortfall' you found with existing gems that prevented you doing this? (Since acts_as_commentable supports this out if the box)