Rails - How to model object privacy settings and associations - ruby-on-rails

I have Forms created by Users. Every form is only visible to the creator. I would like to grant permission to other users to see a specific form. One could say I want to whitelist other users for a specific form.
Here's what I tried by creating a third model called "SharedForm".
app/models/form.rb
Class Form < ApplicationRecord
belongs_to :user
...
end
app/models/user.rb
Class User < ApplicationRecord
has_many :forms
has_many :forms, through: :sharedforms
...
end
app/models/shared_form.rb
Class SharedForm < ApplicationRecord
belongs_to :user
belongs_to :form
...
end
migration
class CreateSharedForms < ActiveRecord::Migration[5.0]
def change
create_table :shared_forms do |t|
t.integer :form_id, index: true
t.integer :user_id, index: true
t.timestamps
end
add_foreign_key :shared_forms, :users, column: :user_id
add_foreign_key :shared_forms, :forms, column: :form_id
end
end
In order to present both user forms and forms shared with the user I defined the index as:
app/controllers/forms_controller.rb
Class FormsController < ApplicationController
def index
#forms = Form.where(user_id: current_user.id)
shared = SharedForm.where(user_id: current_user.id)
#sharedforms = Form.where(id: shared)
end
end
This doesn't work.
Is there a way to access the records I need by user.forms and user.sharedforms respectively?

You can't use the same name for two associations as the latter will overwrite the former:
class User < ApplicationRecord
has_many :forms
# this overwrites the previous line!
has_many :forms, through: :sharedforms
...
end
Instead you need to give each association a unique name:
class User < ApplicationRecord
has_many :forms
has_many :shared_forms
has_many :forms_shared_with_me, through: :shared_forms
end
Note that the through option for has_many should point to an association on the model!
This would let you use:
class FormsController < ApplicationController
def index
#forms = current_user.forms
#shared = current_user.forms_shared_with_me
end
end

Related

how to create a record for a join table?

I have the following associations set up:
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :users_books
has_many :users, through: :user_books
end
and
class User < ActiveRecord::Base
has_many :users_books
has_many :books, through: :users_books
end
I created a join table migration as I ought to
class CreateUsersBooks < ActiveRecord::Migration[4.2]
def change
create_table :users_books do |t|
t.integer :user_id
t.integer :book_id
end
end
end
Now I need to create a method called check_out_book, that takes in a book and a due_date as arguments. When a user checks out a book, it should create a new UserBook record (or Checkout record or whatever you want to call you join table/model). That new UserBook record should have a attribute (and therefore table column) of returned? which should default to false. How would I go about creating this method and the migrations?
Your tablenames and your associations in Rails should always be singular_plural with the exception of the odd duckling "headless" join tables used by the (pretty useless) has_and_belongs_to_many association.
class CreateUserBooks < ActiveRecord::Migration[4.2]
def change
create_table :user_books do |t|
t.references :user
t.references :book
t.boolean :returned, default: false
end
end
end
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :user_books
has_many :users, through: :user_books
end
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
But you should really use a better more descriptive name that tells other programmers what this represents in the domain and not just a amalgamation of the two models it joins such as Loan or Checkout.
I would also use t.datetime :returned_at to create a datetime column that can record when the book is actually returned instead of just a boolean.
If you want to create a join record with any additional data except the foreign keys you need to create it explicitly instead of implicitly (such as by user.books.create()).
#book_user = Book.find(params[:id]).book_users.create(user: user, returned: true)
# or
#book_user = current_user.book_users.create(book: user, returned: true)
# or
#book_user = BookUser.new(user: current_user, book: book, returned: true)

Rails - relation between activeadmin model and custom model

First I would like to know if there is any possibilities to associate one of my model with the ActiveAdmin::Comment and the AdminUser models
this is my model
class AdminAction < ActiveRecord::Base
has_one :comment, :class_name => "ActiveAdmin::Comment", :foreign_key => "admin_action_id"
belongs_to :admin_user
end
thoses associations don't raise any errors, just returning `nil``
I have added a field in thoses two models :
add_column :admin_users, :admin_action_id, :integer
add_column :active_admin_comments, :admin_action_id, :integer
The goal here is to fetch the AdminUser and the Comment associate to my new model AdminAction
and when I do
a = AdminAction
a.admin_user
# and
a.comment
it works
any ideas ?
You need to have a admin_user_id in the admin_actions table to make this belongs_to association work.
class AdminAction < ActiveRecord::Base
belongs_to :admin_user
end
Also, the foreign_key param is unneeded because it will be inferred from the AdminAction class name.
class AdminAction < ActiveRecord::Base
has_one :comment, :class_name => "ActiveAdmin::Comment", :foreign_key => "admin_action_id"
end
Other than that, what you have should work as expected. If it is not, please provide more detail as to what you are seeing, or not seeing as the case may be.
I have this working, albiet with a User model rather than AdminUser. Here is my code:
Migrations
class CreateAdminAction < ActiveRecord::Migration
def change
create_table :admin_actions do |t|
t.references :user, index: true
t.timestamps
end
end
end
class AddFieldsForAdminAction < ActiveRecord::Migration
def change
add_column :active_admin_comments, :admin_action_id, :integer
end
end
AdminAction class
class AdminAction < ActiveRecord::Base
has_one :comment, class_name: 'ActiveAdmin::Comment'
belongs_to :user
end
Another thought: if you are looking to get the ActiveAdmin::Comment records for a single AdminUser, I think you can fetch them directly like this:
admin_comments = ActiveAdmin::Comment.find_for_resource_in_namespace(AdminUser.find(some_id), :admin)

Ruby on Rails Active Record Query Interface

I have the following:
models/like.rb:
class Like
belongs_to :post
end
models/post.rb:
class Post
has_many :likes, dependent: :destroy
def self.popular
Like.group(:post_id).count << ???
end
end
I would like to make a scope that returns the most popular posts: posts with more than 20 likes, but I don't know how to make the conditional.
You can use counter_cache to do this. You will have to create an extra column, but it is more performatic when SELECTing.
models/like.rb
class Like < ActiveRecord::Base
belongs_to :post, counter_cache: true
end
models/post.rb
class Post < ActiveRecord::Base
has_many :likes, dependent: :destroy
def self.popular
where('likes_count > 20').order('likes_count DESC')
end
end
Then create the migration:
class AddLikesToPosts < ActiveRecord::Migration
def change
add_column :posts, :likes_count, :integer, default: 0
end
end
And populate likes_count for your current Posts on rails console (only needed if you already have some created posts):
Post.find_each { |post| Post.reset_counters(post.id, :likes) }
After this, each time you create a new Like, the counter will be automatically incremented.

Has and belongs to many - check additional field

I have an User and Employer models. An User can have multiple Employers and vise versa and there is also a flag indicating if their relationship is active:
class User < ActiveRecord::Base
has_and_belongs_to_many :employers
end
class Employer < ActiveRecord::Base
has_and_belongs_to_many :users
end
and a migration:
class CreateUserEmployers < ActiveRecord::Migration
def change
create_table :users_companies do |t|
t.integer :user_id
t.integer :employer_id
t.boolean :is_active
end
end
end
If I have an User and one of their employers
test1 = User.find(1).employers.first
how do I check if an User's relationship with that Employer is active (the field is_active in users_employers table)?
Per the Rails Guides:
A has_and_belongs_to_many association creates a direct many-to-many connection with another model, with no intervening model.
If you want to add the is_active boolean field (or any other attributes), I suggest you use the has_many :through association. You would need to create a third model (i.e. UserEmployer, EmployerUser, or something else altogether) and your associations would be:
class User < ActiveRecord::Base
has_many :employers, through: :user_employers
has_many :user_employers
end
class Employer < ActiveRecord::Base
has_many :users, through: :user_employers
has_many :user_employers
end
class UserEmployer < ActiveRecord::Base
belongs_to :user
belongs_to :employer
end

STI: user has multiple child class ids.( I want the user has only one of the child class id)

I'm trying to use STI because I want to use single sign-in page for a device. I want to assign either teacher_id or student_id to a user, but it turned out that all the user has both. How can I fix this problem? Below are the models and the migration.
class User < ActiveRecord::Base
...
DEFAULT_ROLE = 'Student'
after_create :set_role
attr_accessible ..., :role
has_one :role
...
private
def set_role
self.role ||= Role.find_by_name(DEFAULT_ROLE)
end
...
end
class Student < User
has_many :bookings
end
Class Teacher < User
has_many :bookings
end
class Role < ActiveRecord::Base
validates :name, :presence => true
belongs_to :user
end
Class Booking < ActiveRecord::Base
attr_accessible :student_id, :teacher_id
belongs_to :teacher, :class_name => 'Teacher'
belongs_to :student, :class_name => 'Student'
...
class CreateBookings < ActiveRecord::Migration
def change
create_table :bookings do |t|
t.integer :student_id
t.integer :teacher_id
t.date :booking_date
t.time :booking_time
t.timestamps
end
end
end
It looks like you need to separate the "role" part of User into a separate object and then allow users to have multiple roles. Sometimes these are called "profiles" as they really refer to a way of presenting the user.
You can then use the user model as a proxy for accessing these things where you'd test for the presence of the profile:
if (user.teacher)
# ...
else
flash[:notice] = "You must be a teacher to perform this operation."
end

Resources