validating uniqueness within a has_many through - mysql

I have a User that can have many Restaurants. I can also have multiple users.
I'd like to have it so that if User A creates Restaurant A, he should NOT be able to make another restaurant with the same name.
However, if User B goes to create Restaurant A, that should be allowed but still cannot make another Restaurant A afterwards.
I have the following has_many through relationship:
restaurant.rb
has_many :ownerships
has_many :users, :through => :ownerships
# This following ensures uniqueness of the name within the
# Restaurants table regardless of who the User is that created it.
validates :name, presence: true, uniqueness: true
user.rb
has_many :ownerships
has_many :restaurants, :through => :ownerships
ownership.rb
belongs_to :restaurant
belongs_to :user
What I've Tried
1. Adding :uniqu => true
I've tried adding :uniq => true to the restaurant.rb file so it looks like this:
has_many :ownerships
has_many :users, :through => :ownerships, :uniq => true
And removing uniqueness: true from the validation so it looks like this:
validates :name, presence: true
But that doesn't do anything useful.
2. Adding validation within ownership.rb
I've tried adding the validation to the ownership.rb file as such:
validates :restaurant, uniqueness: {:scope => :user}
But I get:
NoMethodError in RestaurantsController#create
undefined method `text?' for nil:NilClass
And I can't seem to tell it to look for the restaurant name within the scope of user either within this validation.
3. Creating before_create callback function
In my restaurant.rb file, I declared the following:
before_create :check_uniqueness
def check_uniqueness?
user = User.find_by_id(self.user_ids)
isUnique = false
user.restaurants.each do |restaurant|
if !Restaurant.find_by_name(self.name).nil? # Restaurant w/ same now found
isUnique = false
else
isUnique = true
end
return isUnique
end
end
My assumption is that before the restaurant record is created, it'll do this check_uniqueness check and if the function returns false, it'll not save.
But I'm getting the following error when I hit the submit button:
NameError in RestaurantsController#create
undefined local variable or method `check_uniqueness' for #<Restaurant:0x007f95a16d10f8>
Working Solution
Thanks to Robert Chuchro's help below, I was able to get the validation to work. Here's what I did:
restaurant.rb
before_create :unique_per_user?
def unique_per_user?
user = User.find_by_id(self.user_ids)
restaurant = user.restaurants.find(:all, :conditions => ["name = ?", self.name])
if restaurant.size > 0
self.errors.add(:name, ": You've already created a restaurant with this name.")
end
return (restaurant.size <= 0)
end

You can try to define a method to do this in your restaurant model
def unique_per_user?
#get user trying to create restaurant, either by paramter or association
#check if any of the user's current restaurant names match this name (return true/false)
end
now whereever you define a new restaurant check if its unique_per_user? before deciding to save it.

Related

Create a spreadsheet-like form with Rails

I've been learning Rails for about two months now. I'm creating an application for teachers to track the progress of their students. I've got the "Assignments" model working for teachers to add new assignments to a classroom, and I've got the "Users" model working so that teachers and students are both Users who can log in to the app. There's also a "Classroom" model, and each classroom has_many students and has_many assignments.
One of the main views needs to feature a spreadsheet form like traditional teacher gradebook programs. The spreadsheet will use students as the rows and assignments as the columns. Each cell in the spreadsheet will represent the student's score on that assignment.
From what I've learned so far, I think that my next step should be to create a join table that links students and assignments, with a third column for "score".
The part where I'm stumped is in creating the form so that the input cells are tied to the "score" column in the join table, so that entering a new number will change the student's score for that assignment.
I'm sure that articles or tutorials must exist somewhere for this concept, but I haven't been able to find any yet. At least, none that I recognize as a solution to this goal.
Thank you in advance for any guidance.
UPDATED TO INCLUDE CODE FOR MODELS
User Model:
class User < ApplicationRecord
attr_accessor :remember_token, :activation_token, :reset_token
before_save :downcase_email
before_create :create_activation_digest
has_many :seminars, dependent: :destroy
# Neccessary for finding all classes that a student is enrolled in
has_many :aulas, dependent: :destroy,
foreign_key: :student_id
validates :first_name, length: {maximum: 25},
presence: true
validates :last_name, length: {maximum: 25},
presence: true
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: {minimum: 6}, allow_nil: true
### Several methods that I omitted to keep the question shorter
end
Seminar Model:
(A "Seminar" is a class period, but I wanted to avoid the word, "Class" because I thought that would cause errors.)
class Seminar < ApplicationRecord
belongs_to :teacher, class_name: "User",
foreign_key: "user_id"
has_many :aulas, dependent: :destroy
has_many :students, through: :aulas, source: :student
has_many :assignments
validates :user_id, presence: true
validates :name, presence: true, length: { maximum: 40 }
end
Aula Model:
(Aula is Spanish for class. Again, I wanted to avoid the word, "Class". This model creates a relationship between a student user and a seminar (class period)."
class Aula < ApplicationRecord
# Aula is the relationship between a student and a classperiod.
belongs_to :student, class_name: "User"
belongs_to :seminar
validates :student_id, presence: true
validates :seminar_id, presence: true
end
Assignment model:
class Assignment < ApplicationRecord
belongs_to :seminar
validates :name, presence: true, length: { maximum: 40 }
validates :seminar_id, presence: true
validates :possible, presence: true
end
I would suggest you to show the Users x Assignments in a table and use in place edit, so the user can click in the cell and edit it value right there. For rails, you have a gem called "best in place" (https://github.com/bernat/best_in_place) that does the trick (there's also a rails cast that shows hot to use it: http://railscasts.com/episodes/302-in-place-editing?view=asciicast). Hope it helps, thanks
EDIT:
Answering your question, I used best_in_place for a project manager and it performance it's really nice. Looks like you're editing on microsoft excel, or something else.
About the backend: Well, you have a n x n relationship between student and assignments. You'll need a assignments_student model, for example, that belongs both to your user and assignment model and also has the score (take a look on nxn relationships if you're in doubt). So each row on your assignments_student junction table (that has two foreign keys to user and assignment table plus the score attribute) will be a cell on your table, in a way that you are editing the value of score attribute for that respective user/assignment.
Hope I made it clear. Good luck!
You might be interested in cocoon, it is a gem that allows you to do:
Dynamic nested forms using jQuery made easy
That allows you to add the "rows" to your spreadsheet-like form dynamically, based on the number of students.
Also read about Rails's accepts_nested_attributes_for, this is the foundation for allowing to do nested forms.

The Create method in a Comments controller is not working. Rails

I know similar questions have been asked for this subject and I have read all of them and was not able to figure out a clear solution. Before I state my problem, I will post all the required code.
The models:
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :scoreboard
end
class User < ActiveRecord::Base
has_many :scoreboards, dependent: :destroy
has_many :comments, dependent: :destroy
end
class Scoreboard < ActiveRecord::Base
belongs_to :user
has_many :teams, dependent: :destroy
has_many :comments, dependent: :destroy
end
The scoreboard is similar to a article page where users can post comments.
The migration for the Comments:
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.text :body
t.text :reply
t.references :user, index: true
t.references :scoreboard, index: true
t.timestamps null: false
end
add_foreign_key :comments, :users
add_foreign_key :comments, :scoreboards
end
end
The problem is with the create method in the comments controller. Here is the code for the method:
def create
#scoreboard = Scoreboard.find(params[:scoreboard_id])
#comment.user_id = current_user.id
#comment = #scoreboard.comments.build(comment_params)
redirect_to scoreboard_url(#comment.scoreboard_id)
end
The current_user method is located in a helper file in a separate folder.
Whenever I submit the form for a new comment, I get the following error:
undefined method `user_id=' for nil:NilClass
One of the questions on stack stated that a user_id column is needed in comments, and when I tried migrating it said duplicated column cannot be created. Could it because there is a foreign key to users already present in the migration? What could I be doing wrong?
The error is quite simple:
#comment.user_id = current_user.id
#comment = #scoreboard.comments.build(comment_params)
You're calling #comment without having defined it previously.
It should be like this:
#comment = #scoreboard.comments.build comment_params
#comment.user_id = current_user.id
One of the questions on stack stated that a user_id column is needed in comments
To clarify, they were referring to the foreign_key of the Comment model.
You must remember that Rails is built on top of a relational database:
Whichever variant of SQL you use, you'll still be using it in a relational manner; to which Rails has added the ActiveRecord "object relational mapper".
Simply, this gives Rails the ability to call associative data with different queries etc. We don't see any of the technicalities; only the #scoreboard.comments association.
In the backend, Rails has to compute which data is related to the other. This can only happen when using appropriate relational database structures - including the use of foreign_keys.
This is why you have to assign the likes of user_id when creating associated objects. There is a trick to make it more succinct, though:
#app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
#scoreboard = Scoreboard.find params[:scoreboard_id]
#comment = #scoreboard.comments.build comment_params
end
private
def comment_params
params.require(:comment).permit(:params).merge(user_id: current_user.id)
end
end

Updating a Rails Association table

I'm working on a quiz app in Rails that keeps track of a number of Facts
app/models/fact.rb
class Fact < ActiveRecord::Base
validates(:question, presence: true, uniqueness: { case_sensitive: false })
validates(:answer, presence: true)
end
Every time a user takes a new quiz, they generate an Exam
app/models/exam.rb
class Exam < ActiveRecord::Base
after_create :assign_facts
belongs_to :user
default_scope -> { order('created_at DESC') }
validates :user_id, presence: true
has_many :problems
has_many :facts, through: :problems
def assigned?(fact)
problems.find_by(fact_id: fact.id)
end
def assign!(fact)
problems.create!(fact_id: fact.id)
end
private
def assign_facts
facts = Fact.all.sample(10)
facts.each do |fact|
self.assign!(fact)
end
end
end
Since there are many Exams all using the same Facts, each Exam has_many Facts though an association table of Problems
app/models/problem.rb:
class Problem < ActiveRecord::Base
belongs_to :exam
belongs_to :fact
validates :exam_id, presence: true
validates :fact_id, presence: true
end
excerpt from db/scheme.rb:
create_table "problems", force: true do |t|
t.integer "exam_id"
t.integer "fact_id"
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "correct", default: false
end
My problem is that I'm trying to figure out how to store the results of each user's exam (whether they answer a specific question either correctly, or incorrectly). I was planning on updating the association table each time the user answers a question and storing the result in the t.boolean "correct" collumn. This would be a fairly simple matter in PHP/MySQL (UPDATE problems SET correct = 1 WHERE exam = 'id' AND fact = 'id'), but I'm having difficulty figuring out how to do it the Rails way.
Is there some way I can simply, and easily update my associations table (problems) with Rails? OR Should I create a fourth table (maybe 'results' or something) to keep track of the user's correct/incorrect answers? --I know I don't have any controller code here, I'm just thinking out the broad strokes, and I want to keep things simple. Any help would be greatly appreciated.
You are almost there... you have already added a Boolean column called correct in the schema for the problem model, so now you just need to access that as an attribute when updating an Exam. Som somewhere in your controller code, you would say:
ps=#exam_taken.problems
ps.each do |p|
if answered_correctly(p)
p.correct=true
p.save
end
end
# This assumes you have a method that checks correctness inside the binding where the
# above code is written
#exam_taken.save
If you are using Rails3, you would have to also declare the correct attribute as attr_accessible in your model.
And here's a free pro-tip: Default scope is evil :)

Multiple Foreign Keys with Ruby on Rails

I have the following setup:
One matchdays table with a column called home_team_id and one called visitor_team_id
and a team table.
My Match model looks like this:
class Match < ActiveRecord::Base
belongs_to :home_team, class_name: "Team", foreign_key: :home_team_id
belongs_to :visitor_team, class_name: "Team", foreign_key: :visitor_team_id
belongs_to :matchday
validates :home_team, presence: true
validates :visitor_team, presence: true
end
And the Team model like that:
class Team < ActiveRecord::Base
has_many :matches
has_many :player
end
Now it's getting tricky (at least for me). I'd like to be able to call team.matches and get all of the matches for the team. Since every team has home games and also games on the road.
Currently I'm getting a ActiveRecord::StatementInvalid Error because it's looking for the team_id column in the matches table.
So if I understand correctly, what you need is just a method that returns all games where the current team is playing. This should do the trick:
class Team < ActiveRecord::Base
has_many :player
def matches
Team.where(home_team_id => self.id, foreign_key => self.id)
# This line will also work if you want to try it out.
# Team.where("home_team_id = ?", self.id).where("foreign_key = ?", self.id)
end
end
Happy coding!

:include searches with the wrong value

I have a number of models in a Rails project that are linked to a user, and I'm running into a loading problem I try to get all of the user's data displayed on a page.
My model declarations look sort of like this:
class User < ActiveRecord::Base
validates_presence_of :server_user_id
has_many :setup_notes, :foreign_key => "server_user_id"
has_many :closure_votes, :foreign_key => "user_id"
end
class SetupNote < ActiveRecord::Base
belongs_to :user, :foreign_key => "server_user_id"
end
I should note that in the SQL table for closure votes, user_id is the same value as server_user_id in the table for users.
When I try using the :include symbol in a Rails query, it ends up using user.id for the search value when I need to find closure votes and setup notes through user.server_user_id. In other words,
me = User.first(:conditions => ["server_user_id = ?", 12610], :include => :setup_notes)
generates the MySQL query
SELECT `setup_notes`.* FROM `setup_notes` WHERE (`setup_notes`.server_user_id = 1)
Which returns an empty set. Is there any way to format the Rails query/models to send the relevant SQL query, or do I need to write a raw query for all of the models associated with the users (and if that's the case, how do I do that?)
Thanks!
If I understand correctly (that User has a server_user_id field), in the User model you have to set the :primary_key option like so:
class User < ActiveRecord::Base
validates_presence_of :server_user_id
has_many :setup_notes, :foreign_key => "server_user_id", :primary_key => "server_user_id"
has_many :closure_votes, :foreign_key => "user_id", :primary_key => "server_user_id"
end