Updating a Rails Association table - mysql

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 :)

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

Trouble locating and displaying list of records from a relationship model in Rails app

I have a relationship model that tracks customers (users) of shops in a has_many :through association. I'm having trouble with my ShopsController and finding the list of relationships for a given shop, then displaying them.
My error is a RecordNotFound, Couldn't find Relationship with 'id'= so I'm having issues with how relationships are created and identified. They need to be found based on a user_id when a shop is logged in (current_shop_id is then given) How can I rework this to make relationships/show work?
In the current version a shop has_many users through relationships:
class Shop < ActiveRecord::Base
has_many :relationships
has_many :users, through: :relationships
Relationships takes the user's id and shop's id and creates a relationship id:
schema.rb
create_table "relationships", force: :cascade do |t|
t.integer "user_id"
t.integer "shop_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "relationships", ["shop_id"], name: "index_relationships_on_shop_id"
add_index "relationships", ["user_id", "shop_id"], name: "index_relationships_on_user_id_and_shop_id", unique: true
add_index "relationships", ["user_id"], name: "index_relationships_on_user_id"
A user is added in the shop model:
# New relationship
def add(user)
relationships.create(user.id)
end
# Unfollows a user.
def remove(user)
relationships.find_by(user.id).destroy
end
#relationships is defined in the shop controller:
def relationships
#title = "Following"
#relationships = Relationship.find(params[:relationship_id])
#users = #shop.relationships.paginate(page: params[:page])
render 'show_relationships'
end
and the relationship is created in the relationship controller
def create
user = User.find(params[user_id: user.id])
current_shop.add(user)
redirect_to user
end
Then these relationships should be shown in relationships/show
<div class="col-md-8">
<h3><%= #title %></h3>
<% if #relationships.any? %>
<ul class="users follow">
<%= render #relationships %>
</ul>
<% end %>
</div>
Your relationships method in your controller is all over the place.
You're trying to find relationshipS using a find by id with a non-existent params[:relationship_id] which is causing the error you're seeing.
You're then setting #users to be all the relationships for the #shop.
Then you're rendering a template show_relationships but you refer later to a relationships/show template.
Additionally in the shop model you're calling create on relationships with just a user id whereas you'd expect to be passing in some attributes for the relationship.
It looks like this code has got messier and messier as you've tried to solve the problem. To be perfectly honest I'd go back to the beginning and start again.
If I understand your use case, a shop has many customers and a customer frequents many shops.
The result of doing this the Rails way is you can retrieve customers belonging to a specific shop just by using an instance of that shop and calling shop.customers You can find a specific customer for that shop, by calling shop.customers.find(:id) If you try calling for another shops customer, you get a ActiveRecord::RecordNotFoundError
Ditto the other way round. To retrieve the shops a customer frequents, ideally you want customer.shops etc as above. To add a new shop to a customer, use customer.shops.create("Arkwrights Dairy")
As Shadwell stated your current efforts to create the structure are far more complicated than you need. And you do not set this up through the controllers, only through the models.
Models, 2 only
class Shop < ActiveRecord::Base
has_and_belongs_to_many :customers
class Customer < ActiveRecord::Base
has_and_belongs_to_many :shops
Migrations, 3
class SampleMigration < ActiveRecord::Migration
def change
create_table :shops do |t|
t.string :name
end
create_table :customers do |t|
t.string :name
end
create_table :customers_shops, id: false do |t|
t.belongs_to :customer
t.belongs_to :shop
end
end
end
Add more detail to the models as you see fit, but this should be enough to establish the relationships. Things to watch are the plurals, shop verse shops is important. Shop the class and belongs_to :shop are both singular. References to many shops is plural and tables hold many shops so are also plural. And the join table has to be in alphabetical order, customers_shops not shops_customers.
Once set up, run your migrations then try.
shop = Shop.create(name: "Dairy")
=> #<Shop id:1, name: "Dairy">
shop.customers.create(name: "Bill")
=> #<Customer id:1, name: "Bill">
customer = Customer.find(1)
=> #<Customer id:1, name: "Bill">
customer.shops
=> [#<Shop id:1, name: "Dairy">]
Note that shops returns an array. Access the first element using customer.shops[0] or alternatively customer.shops.find(1) to retrieve the shop using its :id
Hopefully that will give you the foundation you need :-)
... for a given shop ...
This suggests that you should be finding an instance of Shop, and from it using associations to retrieve the appropriate Users. I'd expect to see this in a shop-oriented view, not a relationship view, and for there to be an association on Shop of:
has_many :users, through => :relationships

Validate uniqueness of both ids in a join table

I have a list model which has_and_belongs_to_many postings and vice versa.
I have a join table:
create_table :postings_lists do |t|
t.integer :posting_id
t.integer :list_id
end
Users have many lists.
I already validate the list uniqueness for the user with:
validates :name, presence: true, uniqueness: {scope: :user_id}
In the join table, how can I validate the uniqueness of both the :posting_id and the :list_id so that a posting can't belong to a list more than once?
I have tried adding, uniq: true to both has_and_belongs_to_manys in the models but it messes things up and I have tried added custom validations in the list model but it wasn't working.
I think the simplest thing would be just to validate both ids in the join table but I don't know if I can do that without creating a model?
I would use a has_many :through instead of HABTM.
class List < ActiveRecord::Base
has_many :postings
has_many :posts, through: :postings
end
class Posting < ActiveRecord::Base
belongs_to :list
belongs_to :post
validate :post_id, uniqueness: {scope: :list_id}
end
class Post < ActiveRecord::Base
has_many :postings
has_many :lists, through: postings
end
I ended up adding uniq: true
Lists
has_and_belongs_to_many :postings, uniq: true
Postings
has_and_belongs_to_many :lists, uniq: true
This causes some issues if you use things like pluck on postings because things have to be "distinct" before doing other things like "order_by" to them.
But you can work around this issue by using different queries like 'map' instead.

How to create automated association in ruby on rails with mysql

i got 2 tables connected with each other.
device and push information are my models.
class Device < ActiveRecord::Base
has_one :pushinformation
end
class Pushinformation < ActiveRecord::Base
belongs_to :device
end
these are my 2 model files and their relationships.
and these are my migration files
class CreateDevices < ActiveRecord::Migration
def change
create_table :devices do |t|
#t.integer :id
t.string "token"
t.string "carrier"
t.string "segment"
#t.datetime :created_at
#t.datetime :updated_at
t.timestamps
end
end
end
class CreatePushinformations < ActiveRecord::Migration
def change
create_table :pushinformations do |t|
t.integer "device_id"
#t.string "token"
t.string "first_name"
t.string "last_name"
t.string "nickname"
t.timestamps
end
end
end
now the thing is , i was able to create a relationship successfully in rails console by saying
device.pushinformation=push
and they were associated.
How can i make this process automated, like when i add one device- it will have a push information table filled aswell,
i thought about having the same attribute and relating them might be the solution. In this case its token and its completely unique.
How can i create this relationships? I need to be able to know which device has what kind of first_name
i m a beginner in ruby and this is a newbie question sorry guys :)
I am not sure I understand completely what you ask but my guess is that you could use a callback on create
class Pushinformation < ActiveRecord::Base
belongs_to :device
after_create :create_push_notification
private
def create_push_notification
...
end
end
check the docs
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
xlembouras's answer is right (to a degree), but as you're new, let me explain it for you:
--
Associations
ActiveRecord associations are nothing magical, they're just a way to associate two "objects" using a relational database setup.
ActiveRecord is an ORM -- "object relationship mapper" -- which basically means it just provides a level of abstraction for your ActiveRecord objects to associate with each other
Herein lies the crux of the matter - you need to apprciate how and why your associations will work, and more importantly, how to populate them correctly:
--
Models
Firstly, you need to appreciate the object-orientated nature of Ruby (& by virtue of running on Ruby, Rails). This is where the Models of Rails play such a vital role -- they allow you to build & manage objects from your database
The ActiveRecord associations give you the ability to manage the associations those objects have - maning if you build one, you should be able to build the other:
#app/models/device.rb
Class Device < ActiveRecord::Base
has_one :push_information
before_create :build_push_information #-> creates associative object before creation
end
#app/models/push_information.rb
Class PushInformation < ActiveRecord::Base
belongs_to :device
end
You need to consider the importance of what I've written above.
What you need is to create a push_information object with the same foreign_key as the device object, which can be achieved by using the build method
This will essentially create a "blank" version of your associative object, saving it with the correct foreign key etc
--
Nesting
Further to this, you have to appreciate the idea of "nesting", especially the method accepts_nested_attributes_for
This allows you to create associative / dependent objects based on your "parent" object:
#app/models/device.rb
Class Device < ActiveRecord::Base
has_one :push_information
accepts_nested_attributes_for :push_information
end
#app/models/push_informtion.rb
Class PushInformation < ActiveRecord::Base
belongs_to :device
end
This gives you the ability to do the following:
#app/controllers/devices_controller.rb
Class DevicesController < ApplicationController
def new
#device = Device.new
#device.build_push_information
end
def create
#device = Device.new(device_params)
#device.save
end
private
def device_params
params.require(:device).permit(:your, :device, :params, push_information_attributes: [:push, :information, :attributes])
end
end
This gives you the ability to populate the devices#new form like so:
#app/views/devices/new.html.erb
<%= form_for #device do |f| %>
<%= f.text_field :your_device_attributes %>
<%= f.fields_for :push_information do |p| %>
<%= p.text_field :your_field %>
<% end %>
<%= f.submit %>
<% end %>
Add a create
method to your Devise class. Something like:
def self.create(token, carrier, segment)
device = Device.new(:token => token, :carrier => carrier, :segment => segment)
pi = Pushinformation.create(device.id,..) # other params
end