Ruby on Rails has_many :through Association controller - ruby-on-rails

these are my 3 models :
model for User:
class User < ActiveRecord::Base
has_many :patients, through: :treatments
has_many :treatments
.
.
.
model for patient:
class Patient < ActiveRecord::Base
has_many :user, through: :treatments
has_many :treatments, dependent: :destroy
.
.
.
model for treatment:
class Treatment < ActiveRecord::Base
belongs_to :patient
belongs_to :user
validates :patient_id, presence: true
default_scope -> { order(created_at: :desc) }
end
And this is my treatment table :
class CreateTreatments < ActiveRecord::Migration
def change
create_table :treatments do |t|
t.date :teartment_date
t.text :remark
t.float :fee
t.references :patient, index: true, foreign_key: true
t.timestamps null: false
end
add_index :treatments, [:patient_id, :created_at]
end
end
now i want to define a controller to create a new treatment that belongs to a specific user's patient.
this is my controller :
def new
#treat = Treatment.new
end
def create
#userpatient = current_user.treatments.build(treat_params)
if #userpatient.save
flash[:success] = "new treatment added"
redirect_to root_url
else
render 'new'
end
end
but this is the error that i receive, while i want to create a new treatment :
ActiveRecord::UnknownAttributeError in TreatmentsController#create
unknown attribute 'user_id' for Treatment.
and this is the current_user :
def current_user
if (user_id = session[:user_id])
#current_user ||= User.find_by(id: user_id)
elsif (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated?(cookies[:remember_token])
log_in user
#current_user = user
end
end
end
i'm new to rails, the basic idea is i want my user to have treatment that belongs to a specific patient.
Thanks to replies i've over come with this issue by adding a reference column . now i receive no, but it does not save any treatments. i mean the part :
if #treat.save
flash[:success] = "new treatment added"
redirect_to root_url
else
render 'new'
end
it does not save and just render 'new' .
i have 2 questions :
1- how can i code my create controller ?
2- how to retrieve my treatments base on patient.what variable should i define in my patient 'show' method to have its treatments retrieved ?

When you say that User has_many :treatments and that Treatment belongs_to :user, both associations are expecting to find a user_id column in your treatments table. You might want to change your migration to include:
t.integer :user_id
and then drop your tables (if they have no data yet!) and rerun the migrations. Alternatively, you could create a new migration and simply run that:
add_column :treatments, :user_id, :integer

Related

Follow Request Ruby on Rails

I have implemented a Follower/Following Relationship,i want to extend the functionality ,i.e. in my current implementation User 'A' follows User 'B' without the acknowledgement of User 'B'.I want User 'A' to send a request to User 'B' and then User 'B' either accepts or rejects it.I want it to be like the Instagram model not Facebook model.[User A sends follow request to User B.If User B accepts request then User A follows User B and User B is not following User A,to do so User B has to send a request to User A].
My files:
schema.rb
class CreateFollowJoinTable < ActiveRecord::Migration
def change
create_table 'follows' do |t|
t.integer 'following_id', :null => false
t.integer 'follower_id', :null => false
t.boolean :accepted, default: false
t.timestamps null: false
end
add_index :follows, :following_id
add_index :follows, :follower_id
add_index :follows, [:following_id, :follower_id], unique: true
end
end
app/models/follow.rb
class Follow < ActiveRecord::Base
belongs_to :follower, foreign_key: 'follower_id', class_name: 'User'
belongs_to :following, foreign_key: 'following_id', class_name: 'User'
end
app/models/user.rb
has_many :follower_relationships, foreign_key: :following_id, class_name: 'Follow'
has_many :followers, through: :follower_relationships, source: :follower
has_many :following_relationships, foreign_key: :follower_id, class_name: 'Follow'
has_many :following, through: :following_relationships, source: :following
def follow(user_id)
following_relationships.create(following_id: user_id)
end
def unfollow(user_id)
following_relationships.find_by(following_id: user_id).destroy
end
routes.rb
post ':user_name/follow_user', to: 'relationships#follow_user', as: :follow_user
post ':user_name/unfollow_user', to: 'relationships#unfollow_user', as: :unfollow_user
app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
def follow_user
#user = User.find_by! user_name: params[:user_name]
if current_user.follow #user.id
respond_to do |format|
format.html { redirect_to root_path }
format.js
end
end
end
def unfollow_user
#user = User.find_by! user_name: params[:user_name]
if current_user.unfollow #user.id
respond_to do |format|
format.html { redirect_to root_path }
format.js
end
end
end
end
first you need to add status:boolean default:false to your following_relationships class
then you need to make a controller that notify the followed user about a new follower,
then you need make another controller for the followed user to edit the status from false to be true
like
def accept_follower
#relationships = current_user.following_relationships.find_by(follower_id: params[:follower_id])
#relationships.update_attributes(active: true)
end
def ignore_follower
current_user.following_relationships.find_by(follower_id: params[:follower_id]).destroy
end
then you need to fix your query about follower/following only select where status= true
has_many :followers, -> { where(status: true) } through: :follower_relationships, source: :follower

private messaging system in rails

I am working on a messaging system in my rails app. I already have it working properly for sending messages between 2 users(sender and recipient). This setup is fine but how can I make a new conversation for each room so the uniqueness checking will be only between an user and a room or viceversa?? Each user is only allowed to send message to a room from the room show page. So room_id can be fetched there. A single user can have many listings which makes it complicated for me.So am confused on what change to make in the below code to accomplish that??Or do I have to make a different design approach for the models?
I have a user, listing, conversation and message model
conversation.rb
class Conversation < ActiveRecord::Base
belongs_to :sender, foreign_key: :sender_id, class_name: 'User'
belongs_to :recipient, foreign_key: :recipient_id, class_name: 'User'
has_many :messages, dependent: :destroy
validates_uniqueness_of :sender_id, scope: :recipient_id
scope :involving, -> (user) do
where("conversations.sender_id = ? OR conversations.recipient_id = ?", user.id, user.id)
end
scope :between, -> (sender_id, recipient_id) do
where("(conversations.sender_id = ? AND conversations.recipient_id = ?) OR (conversations.sender_id = ? AND conversations.recipient_id = ?)",
sender_id, recipient_id, recipient_id, sender_id)
end
end
Message.rb
class Message < ActiveRecord::Base
belongs_to :conversation
belongs_to :user
validates_presence_of :content, :conversation_id, :user_id
def message_time
created_at.strftime("%v")
end
end
conversations_controller.rb
class ConversationsController < ApplicationController
before_action :authenticate_user!
def index
#conversations = Conversation.involving(current_user)
end
def create
if Conversation.between(params[:sender_id], params[:recipient_id]).present?
#conversation = Conversation.between(params[:sender_id], params[:recipient_id]).first
else
#conversation = Conversation.create(conversation_params)
end
redirect_to conversation_messages_path(#conversation)
end
private
def conversation_params
params.permit(:sender_id, :recipient_id)
end
end
messages_controller.rb
class MessagesController < ApplicationController
before_action :authenticate_user!
before_action :set_conversation
def index
if current_user == #conversation.sender || current_user == #conversation.recipient
#other = current_user == #conversation.sender ? #conversation.recipient : #conversation.sender
#messages = #conversation.messages.order("created_at DESC")
else
redirect_to conversations_path, alert: "You don't have permission to view this."
end
end
def create
#message = #conversation.messages.new(message_params)
#messages = #conversation.messages.order("created_at DESC")
if #message.save
redirect_to conversation_messages_path(#conversation)
end
end
private
def set_conversation
#conversation = Conversation.find(params[:conversation_id])
end
def message_params
params.require(:message).permit(:content, :user_id)
end
end
Your relations are off. A conversation where the sender and recipient are fixed is no good - in fact thats just a monolog!
Instead we need a real many to many relation. That means we need a third table to store the link between users and converstations
So lets start by generating a model:
rails g model UserConversation user:belongs_to conversation:belongs_to
This will generate a model and a migration for a join table which will link users and conversations. We should now also take care of the uniqueness requirement. Open up the migration:
class CreateUserConversations < ActiveRecord::Migration
def change
create_table :user_conversations do |t|
t.belongs_to :user, index: true, foreign_key: true
t.belongs_to :conversation, index: true, foreign_key: true
t.timestamps null: false
end
# Add this constraint
add_index :user_conversations, [:user_id, :conversation_id], unique: true
end
end
That constraint that ensures the uniqueness on the database level and protects against race conditions. We also want a validation on the software level.
class UserConversation < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
validates_uniqueness_of :user_id, scope: :conversation_id
end
Now we setup the relations in User and Conversation so that they go through the join model:
class User < ActiveRecord::Base
has_many :user_conversations
has_many :conversations, through: user_conversations
def has_joined?(conversation)
conversations.where(id: conversation).exist?
end
end
class Conversation < ActiveRecord::Base
has_many :user_conversations
has_many :messages
has_many :users, through: user_conversations
def includes_user?(user)
users.where(id: user).exist?
end
end
This lets us do #user.conversations or #conversation.users. We don't need the hacky scopes.
This is an example of how you could possibly add a user to a conversation on the fly:
class MessagesController < ApplicationController
# ...
def create
unless current_user.has_joined?(conversation)
# #todo handle case where this fails
#conversation.users << current_user
end
#message = #conversation.messages.new(message_params) do |m|
# get the current user from the session or a token
# using params is an open invitation for hacking
m.user = current_user
end
if #message.save
redirect_to conversation_messages_path(#conversation)
else
render :new
end
end
# ...
end
But note that you still have quite a way to go and will likely need several different controllers to properly represent messages in different contexts:
/messages/:id => MessagesController
/users/:user_id/messages => Users::MessagesController
/conversations/:id/messages => Conversations::MessagesController

Rails has_many :through association: Updating all 3 models at the same time

This question follows up on Rails has_many :through association: save instance into join table and I am restating things here for more clarity.
In our Rails app, there are 3 models:
class User < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :calendars, through: :administrations
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Calendar < ActiveRecord::Base
has_many :administrations, dependent: :destroy
has_many :users, through: :administrations
end
And here are the corresponding migrations:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email
t.integer :total_calendar_count
t.integer :owned_calendar_count
t.timestamps null: false
end
end
end
class CreateAdministrations < ActiveRecord::Migration
def change
create_table :administrations do |t|
t.references :user, index: true, foreign_key: true
t.references :calendar, index: true, foreign_key: true
t.string :role
t.timestamps null: false
end
end
end
class CreateCalendars < ActiveRecord::Migration
def change
create_table :calendars do |t|
t.string :name
t.timestamps null: false
end
end
end
Here is what we are trying to accomplish:
When a logged in user (current_user) creates a calendar, we should:
Create a new #calendar and save it to the Calendar table
Assign the "Creator" role to the user (current_user) for this newly created calendar through the Role column in the Administration table
Increment the total_calendar_count and the owner_calendar_count columns of the User table
In order to do that, we think we need to work on calendars#create.
In the CalendarsController, we already have the following code:
def create
#calendar = current_user.calendars.create(calendar_params)
if #calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
And we collect data from users through the following _calendar_form.html.erb form:
<%= form_for(#calendar) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<div class="field">
<%= f.text_field :name, placeholder: "Your new calendar name" %>
</div>
<%= f.submit "Create", class: "btn btn-primary" %>
<% end %>
We are considering updating the controller as follows:
def create
#calendar = current_user.calendars.create(calendar_params)
#current_user.total_calendar_count += 1
#current_user.owned_calendar_count += 1
current_user.administrations << #calendar.id
#calendar.administration.role = 'Creator'
if #calendar.save
flash[:success] = "Calendar created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
ActiveRecord::AssociationTypeMismatch in CalendarsController#create
Administration(#70307724710480) expected, got Fixnum(#70307679752800)
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
raise ActiveRecord::AssociationTypeMismatch, message
end
end
app/controllers/calendars_controller.rb:7:in `create'
How can we make it work?
This line is actually causing the error: current_user.administrations << #calendar.id.
current.administrations expects an object of type Administration while you are passing a Fixnum into it.
You can accomplish the same functionality in the following way:
current_user.administrations.create(calendar_id: #calendar.id)
Edit:
As OP asked in comments that it is a good practice or not. See, there is rule that says that controllers should be skinny, and models should be fatty. Well, it means you should try to write minimum code, and all the logic and fetching of objects should be there in models. But that isn't the case in your code scenario. You should move your code into model, and then call that into your controller.
Here's how:
class User < ActiveRecord::Base
def add_calendar_and_role(calendar_id, role)
self.administrations.find_by(calendar_id: calendar_id).update(role: role)
end
end
This way, your code reduces to just:
current_user.add_calendar_and_role(#calendar.id, 'Creator')
And on the same way, you can further refactor your controller code.

Associating new model with user id

I'm (very) new to ror and have read many tutorials for this issue but none seem to work. I'm trying to let one user create one booth to sell things.
This is my db migration:
class CreateBooths < ActiveRecord::Migration
def change
create_table :booths do |t|
t.string :name
t.references :user, index: true
t.timestamps null: false
end
add_index :booths, [:user_id]
end
end
Here is the booth controller:
class BoothsController < ApplicationController
before_action :logged_in_user
def new
#booth = Booth.new
end
def create
#booth = current_user.booths.build(booth_params)
if #booth.save
flash[:success] = "Congrats on opening your booth!"
redirect_to root_url
else
render 'new'
end
end
private
def booth_params
params.require(:booth).permit(:name)
end
end
And this is the booth model:
class Booth < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
end
I also added this to the user model:
has_one :booth, dependent: :destroy
When I include validates :user_id, presence: true it won't save to the db. When I exclude it, it saves but does not include a user id in the database. If you are still reading thank you and I hope you can help!
You need to change create method of your BoothsController to this:
def create
#booth = current_user.build_booth(booth_params)
if #booth.save
flash[:success] = "Congrats on opening your booth!"
redirect_to root_url
else
render 'new'
end
end
Here, you have one-to-one association between user and booth, and that's why you have to instantiate booth for current_user using build_<singular_association_name>, which is build_booth and pass params to it: build_booth(booth_params).
booths.build(booth_params) works for one-to-many association, for example: user has many booths, not vice a versa.

Save originator_id, Activity Feed from Scratch

I am currently following Ryan Bates tutorial on activity feed from scratch.
**I added a originator_id to the database so that I can save the ID of the Owner who originated the post. But for some reason I can't get it to work.
My Database from Scratch
class CreateActivities < ActiveRecord::Migration
def change
create_table :activities do |t|
t.belongs_to :user
t.string :action
t.belongs_to :trackable
t.string :trackable_type
###I want to save the id corresponding to User who created the object
t.belongs_to :originator
t.string :originator_type
t.timestamps
end
add_index :activities, :user_id
add_index :activities, :trackable_id
add_index :activities, :originator_id
end
end
Here is my Code
Models
class Activity < ActiveRecord::Base
belongs_to :user
belongs_to :trackable, polymorphic: true
belongs_to : originator, polymorphic: true
attr_accessible :action, :recipient, :trackable
###how can i set the originator_id value
after_create :set_originator
def set_originator
self.originator.update_attribute(:originator, ???)
end
end
Controllers
class ApplicationController < ActionController::Base
###sets the action and trackable values
###how can i the originator here. i keep getting an error saying undefined method
###why is it that rails recognizes trackable?
def track_activity(trackable, action = params[:action])
current_user.activities.create! action: action, trackable: trackable,
originator: originator
end
end
class LikesController < ApplicationController
def create
#like = Like.create(params[:like])
#dailypost = #like.dailypost
###Used to call track activity method above
track_activity #like
respond_to do |format|
format.js
format.html { redirect_to :back }
end
end
end
Don't know how solid this answer will be as i add more models to the activities, but this worked for my likes model.
If anyone can provide another solution that will work with multiple models i would really appreciate it. :)
class ApplicationController < ActionController::Base
def track_activity(trackable, action = params[:action])
current_user.activities.create! action: action, trackable: trackable,
originator: originator
end
def originator
#like.dailypost.user
end
end

Resources