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.
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 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
I have three models, Subrating, Thing, and Category. Subrating belongs_to Thing, and Thing has_many Categories.
I have a list of Subratings, and I'm trying to eager-load the Categories that are associated with the Things that are associated with each Subrating.
This is what I tried:
controller
#subratings = Subrating.all( :include => { :thing => :categories })
view
<% #subratings.sort_by(&:rating).reverse.each do |subrating| %>
<%= subrating.thing.categories.sort_by(&:thing_count).second.name %>
<% end %>
But it doesn't solve my n+1 problem. I'm not even sure whether the database is lazy-loading Things or Categories or both, but this is the line that keeps reappearing hundreds of times in my server:
CACHE (0.0ms) SELECT COUNT(*) FROM "things" INNER JOIN "category_things" ON "things"."id" = "category_things"."thing_id" WHERE "category_things"."category_id" = $1 [["category_id", 1]]
What am I doing wrong?
associations
class Subrating < ActiveRecord::Base
belongs_to :thing
end
class Thing < ActiveRecord::Base
has_many :category_things
has_many :categories, :through => :category_things
has_many :subratings
end
class Category < ActiveRecord::Base
has_many :category_things
has_many :things, :through => :category_things
end
class CategoryThing < ActiveRecord::Base
belongs_to :category
belongs_to :thing
end
Maybe you should try to call includes on the Model class like:
#subratings = Subrating.includes(thing: [:categories])
as documented in http://apidock.com/rails/ActiveRecord/QueryMethods/includes
although it also should work the way you are trying it, since .all is an alias for .find(:all) but then i would propose to try:
#subratings = Subrating.all( :includes => { :thing => [:categories] })
(note that i changed include to includes and made thing point to an array)
#subratings = Subrating.includes(:thing => :categories)
This will also work for you and give you all records of substrings table.
I have two models lets suppose
class A < ActiveRecord::Base
has_many :bs
end
class B < ActiveRecord::Base
belogns_to :a
end
now because of some system changes I need to convert this association to many to many some thing like this
class A < ActiveRecord::Base
has_and_belongs_to_many :bs
end
class B < ActiveRecord::Base
has_and_belongs_to_many :as
end
OR
class A < ActiveRecord::Base
has_many :cs
has_many :bs, through: :cs
end
class B < ActiveRecord::Base
has_many :cs
has_many :as, through: :cs
end
class C < ActiveRecord::Base
belongs_to :a
belongs_to :b
end
what is best way to do this and most importantly I DO NOT WANT TO LOSE MY EXISTING DATA. Existing records should automatically adopt these changes. Thanks in advance.
many to many means you have connected table(model) between two other, so you could just create this one and write relation to it, after that you could remove garbage ids from B.
A, B are not good names;)
imagine you have User and Comments, and you decide that comments can have also many users, so it can look like:
class User
has_many :comments # will be changed
has_many :user_comments
end
class UserComments
belong_to :user
belong_to :comment
end
class Comment
belong_to :user # id will be removed from table and relation will be changed
has_many :user_comments
end
# as direction for import could be something like:
User.all.each do |user|
user.comments.each do |comment|
UserComments.create user: user, comment: comment
end
end
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!