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
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?
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
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.
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.
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