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

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

Related

Activerecord "where" condition for Array attribute

I have model course.rb with pre_courses is an array.
class Course < ActiveRecord::Base
serialize :pre_courses, Array
end
Now I want to check if an exist course is pre_course of any course by Activerecord or raw SQL (I am using MySQL), such as Course.where("pre_courses INCLUDEs self.id").
Is there any way to do this?
A serialized array is just a string in the db, so try using LIKE, for example:
Course.where("pre_courses LIKE ?", "% #{self.id}\n%")
Notice that a serialzed array adds a space before each item and a new line after, thus the added space before the interpolated string and the \n at the end.
It sounds like a pre_course is actually a regular course, a course can have many pre_courses and a pre_course can belong to many courses. A self referential has_many through relationship is possible and may give you more flexibility to work with your data than serializing it as an array.
You'll need a join model I'll call CoursePreCourse. It will have the columns course_id and pre_course_id. pre_course_id will be a foreign key for records on the courses table.
class CreateCoursePreCourses < ActiveRecord::Migration[5.1]
def change
create_table :course_pre_courses do |t|
t.references :course, foreign_key: true
t.references :pre_course, foreign_key: { to_table: :courses }
t.timestamps
end
end
end
class CoursePreCourse < ApplicationRecord
belongs_to :course
belongs_to :pre_course, class_name: 'Course'
end
class Course < ApplicationRecord
# A straight-forward has_many :through association for a course that has_many :pre_courses
has_many :course_pre_courses
has_many :pre_courses, through: :course_pre_courses
# A little coercion is necessary to set up the association as a pre_course that has_many :courses
has_many :pre_course_courses, class_name: 'CoursePreCourse', foreign_key: :pre_course_id
has_many :courses, through: :pre_course_courses
end
Now you can retrieve the courses that are pre_courses of any course with course.pre_courses. If you want to see if a course is a pre_course of other courses it's pre_course.courses. Similarly you can add a course to another course's pre_courses with either course.pre_courses << pre_course or pre_course.courses << course.
Do you ever say a word so many times it loses its meaning?

Rails association - Customised list of items from existing list

I am developing a rails app where users can add tasks they wish to do to a customised list of their own. Each task can also belong to 0 or more categories. So far I've tried this:
user.rb
has_one :user_list
has_many :tasks, through: :user_list
user_list.rb
belongs_to :user
has_many :tasks
tasks.rb
has_and_belongs_to_many :categories
[timestamp}_migration.rb
create_table :user_lists do |t|
t.integer :user_id
t.integer :task_id
t.timestamps null: false
end
The issue I am having is in the console I try to run User.find(1).tasks it cannot find the column tasks.user_list_id when using the following query:
SELECT "tasks".* FROM "tasks" INNER JOIN "user_lists" ON "tasks"."user_list_id" = "user_lists"."id" WHERE "user_lists"."user_id" = ? [["user_id", 1]]
This query should be joining the tasks id from the tasks table with the tasks id on the user_lists table. Are the associations correct and if so what can I do to change the query?
To allow tasks to be placed on many lists you need a M2M join table which joins the user_lists and tasks table.
class User < ActiveRecord::Base
has_many :user_lists
has_many :tasks, through: :user_lists
end
class Task < ActiveRecord::Base
has_many :user_list_items
has_many :user_lists, through: :user_list_items
end
class UserListItem < ActiveRecord::Base
belongs_to :task
belongs_to :user_list
has_one :user, through: :user_list
# optional
validates_uniqueness_of :task_id, scope: :user_list_id
end
class UserList < ActiveRecord::Base
belongs_to :user
has_many :user_list_items
has_many :tasks, through: :user_list_items
end
You can create the join model and migration with:
rails g model UserListItem user_list:belongs_to task:belongs_to
You may also want to open up the migration and add a compound index:
add_index :user_list_items, [:user_list_id, :task_id], unique: true
Setting it as unique is optional - but in most cases you want join table entries to be unique for table A and B.
See:
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
http://guides.rubyonrails.org/active_record_migrations.html
Your use case calls for a task to be assigned to more than one user and a user has only one task list. This sounds like a HABM association between users and tasks.
The simplest way to express that would be:
class User
has_and_belongs_to_many: :tasks
...
end
class Task
has_and_belongs_to_many: :users
...
end
and a migration to create the join table:
create_join_table :users, :tasks, do |t|
t.index :user_id
t.index.task_id
end
You don't need to create a TaskUser model to match the join table until you need to track additional attributes. Rails will take care of that automatically.
If a user needs more than one task list, you'll need that TaskList model. Let me know and I'll update my answer.
Here's the documentation on HABM and join migration

Creating nested records on create with Rails

I have a projects model that I am using to auto generate departments within a specific project on create. This is included in the projects model with:
class Project < ActiveRecord::Base
attr_accessible :title, :departments_attributes, :positions_attributes, :id
belongs_to :user
has_many :departments
has_many :positions
validates :title, presence: true
before_create :set_departments
accepts_nested_attributes_for :departments
accepts_nested_attributes_for :positions
private
def set_departments
self.departments.build department: "Test Dept", production_id: self.id
end
end
Each department has many positions. I am trying to create positions as well for the departments. How could I associate a new position with a department in this model?
There are two ways:
#app/models/project.rb
class Project < ActiveRecord::Base
has_many :departments
accepts_nested_attributes_for :departments
before_create :set_department
private
def set_department
self.departments.build department: "Test"
end
end
#app/models/department.rb
class Department < ActiveRecord::Base
has_many :positions
accepts_nested_attributes_for :positions
before_create :set_positions
private
def set_positions
self.positions.build x: y
end
end
... or ...
#app/models/project.rb
class Project < ActiveRecord::Base
has_many :departments
accepts_nested_attributes_for :departments, :projects
before_create :set_departments
private
def set_departments
dpt = self.departments.build department: "Test"
dpt.positions << Position.new position: "Admin"
dpt.positions << Position.new position: "Tester"
end
end
--
You can also declare multiple nested attributes on a single line:
accepts_nested_attributes_for :departments, :positions
If I understand your question correctly, you might do something like this in your Department model:
after_create { self.positions.create! }
Though this might be a problematic approach. Creating records like this using ActiveRecord callbacks (which is what gives us after_create) can make your whole app really fragile. For example, if you do this, you'll never be able to make a department without an associated position. Perhaps one day you'll need to do just that.
So even though it's not the exact answer to your question, I suggest looking at created these associated models in a service object, or at least in controller code.
You can add a new position with:
Project.first.positions << Position.create(:foo => 'foo', :bar => 'bar')
or
position = Position.create(:foo => 'foo', :bar => 'bar')
Department.first.positions << position
Project.first.positions << position
Obviously the ".first" is just for illustration and you can use the << notation with any Department or Project instance.
Looking at this again, it seems like a really good fit for polymorphic associations.
class Position < ActiveRecord::Base
belongs_to :positioned, polymorphic: true
end
class Project < ActiveRecord::Base
has_many :positions, as: :positioned
end
class Department < ActiveRecord::Base
has_many :positions, as: :positioned
end
And in your migration:
create_table :positions do |t|
...
t.integer :positioned_id
t.string :positioned_type
...
end
There may be a more suitable way to name things for your app but this is the general idea.

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