`#post.userview.users << current_user` - ruby-on-rails

Every post has a userview, and each userview has many users. I want one single many to many to have a simple .add() and .remove() function like django. How do I place the current_user into the many-to-many relationship of the views?
I found this:
#post.userview.users << current_user
But it brings up some SQL error. It's suggesting I add a post_id:
ERROR: column userviews.post_id does not exist
LINE 1: SELECT "userviews".* FROM "userviews" WHERE "userviews"."po...
^
HINT: Perhaps you meant to reference the column "userviews.posts_id".
After the answer, now the error is:
can't write unknown attribute `userview_id`
Because I changed the migrations around a little, userview has references to posts and users. Post has_one userview, has_many users through userview, userview has_many posts and has_many users.
create_table "posts", force: :cascade do |t|
t.integer "userview_id"
t.bigint "userviews_id"
t.index ["userviews_id"], name: "index_posts_on_userviews_id"
end
create_table "userviews", force: :cascade do |t|
t.bigint "users_id"
t.bigint "posts_id"
t.integer "post_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["posts_id"], name: "index_userviews_on_posts_id"
t.index ["users_id"], name: "index_userviews_on_users_id"
end
class User < ActiveRecord::Base
has_many :viewed_posts, through: :userview, class_name: 'Post'
...
class Userview < ApplicationRecord
belongs_to :post
belongs_to :user
end
class Post < ApplicationRecord
belongs_to :user
has_many :userviews
has_many :viewers, through: :userview, class_name: 'User'
...
Could not find the source association(s) "viewer" or :viewers in model Userview. Try 'has_many :viewers, :through => :userviews, :source => <name>'. Is it one of post or user?
Although, that's for unless #post.viewers.include?(current_user)

Not sure if I understood correctly what are you trying to achieve, but I'll try to fix your associations.
To associate 2 models you need to store foreign key in one of them. Model which stores the key belongs_to other model. Eg in posts table you have user_id column, it means Post belongs_to: user and User has_many :posts (or has_one :post if you need one-to-one association). For such association you can write:
user = User.first
post = Post.last
user.posts << post
If you want all this stuff to work automatically you should follow the convention about naming. Foreign keys should be in singular form, like user_id, not users_id
If you want many-to-many association you need to create an intermediate table, which stores both foreign keys. It can be done using simple direct has_and_belongs_to_many association or with more complex has_many through:
I suppose that in your case it should be:
class User < ActiveRecord::Base
has_many :userviews
# it is posts viewed by the user
# you need to specify class name because it differs from association name
has_many :viewed_posts, through: :userviews, class_name: 'Post'
# posts written by the user
has_many :posts
end
class Userview < ApplicationRecord
belongs_to :post
belongs_to :user
end
class Post < ApplicationRecord
belongs_to :user
has_many :userviews
has_many :viewers, through: :userviews, source: :user
end
It means that you need post_id and user_id columns in userviews table and user_id in posts table. Please, remove useless columns and add needed in migration. When you set it all correctly you'll be able to do
#post.viewers << current_user
to add current_user to viewers list. Corresponding userview instance is created automagically

Changed has_many :viewers, through: :userviews, class_name: 'User'
to has_many :viewers, through: :userviews, source: :user

Related

Custom Join Table Name for Rails

I'm new to Ruby on Rails, and I'm developing a backend API.
Currently, I got 2 Active Record Models called Book and User.
Active Record Model
class Book < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :books
end
DB Schema Model
create_table :books do |t|
t.string "title"
end
create_table :users do |t|
t.string "name"
end
#User favourite books
create_join_table :users, :books do |t|
t.index [:user_id, :book_id]
t.index [:book_id, :user_id]
end
#User read books
create_join_table :users, :books do |t|
t.index [:user_id, :book_id]
t.index [:book_id, :user_id]
t.integer "read_pages"
t.string "status"
t.integer "rating"
t.datetime "start_date"
t.datetime "finish_date"
end
QUESTION
I'd like to create 2 join tables, one for those books added to the user favourites list, another one for those books that a user has read.
Both tables share user_id & book_id, howerver, the second one has more data since it is a record.
Active Record naming convention creates a table named as users_books automatically. So when I migrate this, it reports to me the following error:
Index name 'index_books_users_on_user_id_and_book_id' on table 'books_users' already exists
How do I rename the second join table name?
How do I rename the second join table name?
Pass the table_name: option:
create_join_table :users, :books, table_name: :favorite_books do |t|
t.index [:user_id, :book_id]
end
You also need to use a unique names for the associations and tell rails whats going on since it can be derived from the name:
class User < ApplicationRecord
has_and_belongs_to_many :books
has_and_belongs_to_many :favorite_books,
join_table: 'favorite_books',
class_name: 'Book',
inverse_of: :favorite_books
end
class Book
has_and_belongs_to_many :users
# for lack of a better name?
has_and_belongs_to_many :favorite_users,
join_table: 'favorite_books',
class_name: 'User',
inverse_of: :favorite_books
end
The unique names are necessary since you would just be clobbering the previous associations if you used the same names.
But...
has_and_belongs_to_many and create_join_table are pretty useless as they won't let you access any of the additional columns on the table and don't provide such niceties like primary keys or timestamps. The documentation says:
Use has_and_belongs_to_many when working with legacy schemas or when you never work directly with the relationship itself.
But how are you ever supposed to know if you're going to want those features down the line? And you're stuck there with a table with just two foreign keys. Its a much better idea to go with has_many through: and switch to has_and_belongs_to_many if the memory usage ever becomes a problem (it ain't gonna happen).
TLDR; has_and_belongs_to_many sucks. Use has_many through: instead.
You should use has_many :through association if you want to save other than primary keys for many to many relationship. For more details, refer Rails guides.Your models should be like:
class Book < ActiveRecord::Base
has_many :read_books
has_many :users, through: :read_books
end
class User < ActiveRecord::Base
has_many :read_books
has_many :books, through: :read_books
end
class ReadBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
And you can also make one field/flag(is_favourite) in read_books
and create a scope in read_books for favourites like
scope :favourites, -> { where(is_favourite: true) }

How to set up an admin user in rails

I have a simple relationship
class School < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :schools
end
A user can be part of many schools but at the same time a user might be the admin of a number of schools. I set up a many-to-many relationship to represent this however I'm not sure how I would distinguish between admins and simple users.
I initially thought of setting a table which has a school_id and a user_id and every entry will represent the school id and the user id of any admins that the school has however I'm not sure how I would represent this in rails or if it's the best way to solve this problem? And if it is, how do I access the table without a model associated to it?
What I mean by what I said above:
school_id user_id
1 3
1 4
Which means that the school with id 1 has 2 admins (3 and 4)
What you are looking for is a more complex many_to_many relationship between school and user called has_many :through. This relationship allows you to have many to many relationship with access to the table that represents the relationship. If you use that relationship, your models should look something like this:
class User < ActiveRecord::Base
has_many :school_roles
has_many :schools, through: :school_roles
end
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
end
class School < ActiveRecord::Base
has_many :school_roles
has_many :users, through: :school_roles
end
And the migrations of those tables would look something like this:
class CreateSchoolRoles < ActiveRecord::Migration
def change
create_table :schools do |t|
t.string :name
t.timestamps null: false
end
create_table :users do |t|
t.string :name
t.timestamps null: false
end
create_table :school_roles do |t|
t.belongs_to :school, index: true
t.belongs_to :user, index: true
t.string :role
t.timestamps null: false
end
end
end
I would suggest to make the "role" field in the "school_roles" migration an integer and then use an enum in the model like so:
class SchoolRole < ActiveRecord::Base
belongs_to :school
belongs_to :user
enum role: [ :admin, :user ]
end
which allows you to add more roles in the future, but it's your call
combining polymorphic association with has_many :through in my opinion is best option.
Let's say you create supporting model SchoolRole, which
belongs_to :user
belongs_to :school
belongs_to :rolable, polymorphic:true
This way:
class School ...
has_many :administrators, :as => :schoolroles
has_many :users, :through => :administators
#school.administrators= [..., ...]
It is quite agile.
#user=#school.administrators.build()
class User
has_many :roles, :as => :rolable
def admin?
admin=false
self.roles.each do |r|
if r.role_type == "administator"
admin=true
break
end
end
admin
end
....

Setting up self-referencing HABTM relationship in Rails 4

I'm looking to set up a self-referencing has_and_belongs_to_many relationship using rails 4, with a postgres db.
Basically I've got a Single Inheritance Table set up called Resource, which holds People, Places, and Things in it. (This works beautifully.)
create_table :resources do |t|
t.string :name
t.string :type
t.text :description
end
I'm trying to create a 'has_and_belongs_to_many' relationship so that each Resource can have a series of 'owners', which will be an Relation of People. Each Person, in turn, will have a series of 'possessions'. Since it's a single table, I'll need to join the Resource table to itself.
My migration for the join table looks like this:
create_table :owners_possessions, id: false do |t|
t.integer :owner_id # the id of a person
t.integer :possession_id # the id of a place/thing owned by that person
end
Person.rb:
class Person < Resource
has_and_belongs_to_many :possessions, class_name: :resources,
join_table: :owners_possessions,
foreign_key: :owner_id,
association_foreign_key: :possession_id
end
Resource.rb:
class Resource < ActiveRecord::Base
has_and_belongs_to_many :owners, class_name: :people,
join_table: :owners_possessions,
association_foreign_key: :possession_id,
foreign_key: :owner_id
end
However, when running Resource.find(x).owners in the console, I get the following error message:
ActiveRecord::StatementInvalid: Could not find table 'resources_resources'
Which is perturbing because everything I've searched so far has pointed toward the join_table option as a way to get it looking at the right table.
Furthermore, running `Person.find(x).possessions' yields
NameError: uninitialized constant Person::Possession
What might I be missing here?
I can't reproduce the errors you posted, I suppose you altered your code somewhat.
Anyway, the class_name option in your associations should be the exact name of the other model. So 'Person' in singular form rather than :people:
class Person < Resource
has_and_belongs_to_many :possessions,
class_name: 'Resource',
join_table: :owners_possessions,
foreign_key: :possession_id,
association_foreign_key: :owner_id
end
class Resource < ActiveRecord::Base
has_and_belongs_to_many :owners,
class_name: 'Person',
join_table: :owners_possessions,
association_foreign_key: :possession_id,
foreign_key: :owner_id
end
Notice I also swapped the :foreign_key and :association_foreign_key values so they return the appropriate records.

Many to many relationship in Rails

I'm trying to create a many to many relationship between two models in Rails 3.2.11.
A User can be associated with many Incidents and vice versa.
class User < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
has_many :incident_participants, foreign_key: "participant_id"
has_many :participated_incidents, through: :incident_participants
end
class Incident < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
has_many :incident_participants, foreign_key: "participated_incident_id"
has_many :participants, through: :incident_participants
end
The join table:
class IncidentParticipant < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
t.belongs_to :participant, class_name: "User"
t.belongs_to :participated_incident, class_name: "Incident"
end
Table for IncidentParticipants
create_table "incident_participants", :force => true do |t|
t.integer "participant_id"
t.integer "participated_incident_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
So, why doesn't rails get this relationship? When I try to do #incident.participants in my view I get this error:
"Could not find the source association(s) :participant or
:participants in model IncidentParticipant. Try 'has_many
:participants, :through => :incident_participants, :source => '.
Is it one of ?"
Any ideas?
Try taking out the t.belongs_to and replace with belongs_to.
To create a many to many association you should consider creating an association table. That is to say you will have two 1-M relationships that point to a sort interim table. For instance:
In your first model:
class Example < ActiveRecord::Base
has_and_belongs_to_many :example2
end
In your second model:
class Example2 < ActiveRecord::Base
has_and_belongs_to_many :example
end
Then you need to write a migration to link the two tables together:
class CreateTableExamplesExamples2 < ActiveRecord::Migration
create_table :examples_examples2 do |t|
t.integer :example_id
t.integer :example2_id
end
end
Then just let rails magic work. Check out the guides for more information.

rails has_many_through data insertion question

I have a scenario where the models look like
create_table :users do |t|
t.string :name
t.timestamps
end
create_table :blogs do |t|
t.string :url
t.string :title
t.text :description
t.timestamps
end
create_table :posts do |t|
t.integer :user_id, :null => false
t.integer :blog_id, :null => false
t.text :post_text
end
class Blog < ActiveRecord::Base
has_many :users, :through =>:posts
has_many :posts, :dependent=>true
end
class User < ActiveRecord::Base
has_many :blogs
has_many :posts, :through=>:blogs
end
class Post < ActiveRecord::Base
belongs_to :blog
belongs_to :user
end
The question I have is:
1. When a user is created, I would like to create a blog for him automatically.
#user = User.find_or_create_by_name(user_name)
How do I go about creating a blog?
#blog = Blog.find_or_create_by_user_id(#user)
I am getting the following error:
undefined method `find_or_create_by_user_id' for #<Class:0x1044735b0>
#blogs = #user.blogs
gives me:
Mysql::Error: Unknown column 'blogs.user_id' in 'where clause': SELECT * FROM `blogs` WHERE (`blogs`.user_id=1234)
I know Blogs table does not have user_id column.
But isn't the join supposed to take care of it?
What am I doing wrong here?
Thanks for your help
To use the Post model as the association table, the User model needs to be tweaked to properly demonstrate the association. Once done, you could use after_create to create a new blog for a newly created user.
class User < ActiveRecord::Base
has_many :posts
has_many :blogs, :through=>:posts
after_create :add_blog
private
def add_blog
blogs << Blog.new
end
end
EDIT:
The best I know how to handle it is to explain what I "think" the relationships are attempting to accomplish then you tell me where I'm off and we go from there.
1) A User can "own" many blogs
2) A blog can have many posts
3) A post belongs to a single user and to a single blog
4) a blog can only have one "owner" (user)
5) Blogs can be "owned" by many users thereby giving them permission to post.
If 1-4 are true, and 5 false... that isn't a "has_many :through" scenario or many-to-many relationship, just one-to-many relationships.
Accordingly, posts should not be used as an association table. There isn't an association table needed.
add t.integer :user_id, :null => false to the blogs table
class Blog < ActiveRecord::Base
belongs_to :users,
has_many :posts, :dependent=>:destroy # rec'd error in RoR3... replaced true with :destroy
end
class User < ActiveRecord::Base
has_many :blogs, :dependent=>:destroy
has_many :posts
after_create :add_blog
private
def add_blog
blogs << Blog.new
end
end
class Post < ActiveRecord::Base
belongs_to :blog
belongs_to :user
end
If 5 is true, this would be a true many-to-many... but I don't think that's what you're attempting to do.

Resources