Unable to save data in "has_many through" relation from API - ruby-on-rails

I am using Ruby on Rails API with Postgresql I have two models subject and teacher, which has a relation "many to many", so using intermediate table subject_teacher. I want to create a map where which teacher is teaching which subject. All I want to store the ID's of teacher and subject in front of each other so that I can fetch them using JOIN later. (or suggest any other alternative).
Using Ruby on Rails 6.1
MIGRATIONS
Teacher
class CreateApiV1Teacher < ActiveRecord::Migration[6.1]
def change
create_table :api_v1_teacher do |t|
t.string :name
t.timestamps
end
end
end
Subject
class CreateApiV1Subject < ActiveRecord::Migration[6.1]
def change
create_table :api_v1_subject do |t|
t.string :name
t.timestamps
end
end
end
Subject Teacher
class CreateApiV1SubjectTeacher < ActiveRecord::Migration[6.1]
def change
create_table :api_v1_subject_teacher do |t|
t.belongs_to :teacher
t.belongs_to :subject
t.timestamps
end
end
end
MODEL
Teacher
class Api::V1::Teacher < ApplicationRecord
has_many :subject_teacher
has_many :subject, :through => :subject_teacher
end
Subject
class Api::V1::Subject < ApplicationRecord
has_many :subject_teacher
has_many :teacher, :through => :subject_teacher
end
SubjectTeacher
class Api::V1::SubjectTeacher < ApplicationRecord
belongs_to :teacher
belongs_to :subject
end
CONTROLLER (Post Method)
I want to take array from front-end and save entries in DB (or any other alternative to make things faster ?)
class V1::SubjectTeacherController < ApplicationController
before_action :set_api_v1_subject_teacher, only: [:show, :update, :destroy]
def create
#api_v1_subject = Api::V1::SubjectTeacher.new(api_v1_my_subject_teacher)
if #api_v1_subject.save
render json: #api_v1_subject, status: :created, location: #api_v1_subject
else
render json: #api_v1_subject.errors, status: :unprocessable_entity
end
end
def api_v1_my_subject_teacher
params.require(:my_ids).permit([:teacher_id, :subject_id])
end
end
JSON
{
"my_ids": [
{
"teacher_id": 1,
"subject_id": 2
},
{
"teacher_id": 1,
"subject_id": 3
}
]
}
I am new to Ruby on Rails and backend any other alternative method or new way will be a great help.
Thank you

Related

ActiveRecord::AssociationTypeMismatch in one to one relationship rails

I'm trying to use postman for submitting a form in rails. I have these parameters
{
"book": {
"title": "rrewr",
"media": {
"name": "hello",
"format": "jpg"
}
}
}
When submitting these parameters. I have these code for creating book in my controller
class Admin::BookController < ApplicationController
def create
#book = Book.new(book_params)
if(#book.save)
# redirect_to #post
end
end
private def book_params
params.require(:book).permit(:title, :media => [:name, :format])
end
end
I have one to one relationship with books to media. But when I receive the request. I got this error
<ActiveRecord::AssociationTypeMismatch: Media(#70348614328640) expected, got {\"name\"=>\"hello\", \"format\"=>\"jpg\"} which is an instance of ActiveSupport::HashWithIndifferentAccess(#47385709875460)>
I already search this in rails documentation and even in google but I can't find one that same problem of mine. I have this model for book and media
class Book < ApplicationRecord
belongs_to :media
end
class Media < ApplicationRecord
has_one :book
end
I have these migrations. Please Correct me If I did wrong. I can't identify which part of my code is wrong or I have a missing code to add.
class CreateBooks < ActiveRecord::Migration[5.2]
def change
create_table :books do |t|
t.string :title
t.references :media, index: true, foreign_key: true
t.timestamps
end
end
end
class CreateMedia < ActiveRecord::Migration[5.2]
def change
create_table :media do |t|
t.string :name
t.string :format
t.timestamps
end
end
end
As you are trying to create book with media. so please try this:
class Admin::BookController < ApplicationController
def create
#media = Media.new(media_params)
ActiveRecord::Base.transaction do
#media.save!
#media.create_book!(book_params)
# redirect_to #post
end
end
private
def book_params
params.require(:book).permit(:title)
end
def media_params
params.require(:media).permit(:name, :format)
end
end
For postman, try passing parameter like this:
media: {
name: '',
format: ''
},
book: {
title: ''
}
}

Multiple belongs_to in ActiveRecord

I am looking to model a messaging system.
At the moment I have two models, User and Message. I wish for there to be multiple associations on a message to a user, the sender and receiver.
This is my active record migration for my Message.
class CreateMessages < ActiveRecord::Migration[5.1]
def change
create_table :messages do |t|
t.string :body
t.belongs_to :sender, :class_name=>'User', :foreign_key=>'sent_from'
t.belongs_to :recipient, :class_name=>'User', :foreign_key=>'sent_to'
t.timestamps
end
end
end
However when I attempt to create a new Message I get the following error:
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
Message.create(body: "Hello World", 1, 2)
end
end
However I get the following error
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such table: main.recipients: INSERT INTO "messages" ("body", "created_at", "updated_at") VALUES (?, ?, ?)):
What am I missing here? What is the best way to model this relationship?
A few things wrong here.
According to the docs, belongs_to (which is an alias for references) in a migration takes the options: :type, :index, :foreign_key, :polymorphic, and :null. There is no :class_name, and :foreign_key should be true or false (defaults to false).
So, your migration should probably look more like:
class CreateMessages < ActiveRecord::Migration[5.1]
def change
create_table :messages do |t|
t.string :body
t.belongs_to :sender
t.belongs_to :recipient
t.timestamps
end
end
end
message.rb will look something like:
class Message < ActiveRecord::Base
belongs_to :sender, class_name: 'User'
belongs_to :recipient, class_name: 'User'
...
end
I don't know why you're creating a Message in the index action of your DashboardController. But, hey, whatever floats your boat.
But, it should probably look more like:
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
Message.create(body: "Hello World", sender_id: 1, recipient_id: 2)
end
end
Or, it might look something like:
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
sender = User.find_by(...)
recipient = User.find_by(...)
Message.create(body: "Hello World", sender: sender, recipient: recipient)
end
end
Rails can be pretty smart, but when you do:
Message.create(body: "Hello World", 1, 2)
it's not so smart to know whether 1 is a sender or a recipient. Same with 2. You need to tell rais which is which.

Record not saving to join table in has_many through relationship

In this Rails app, Users write Stories. Users can create Collections to group their Stories. However, they are allowed to publish Stories that don't belong to any Collection.
When creating a Story, I want the join table Story_Collections to save the Collection/Story ID pairs but it isn't working. Any help is appreciated! :)
Here's what I have
collection.rb
class Collection < ActiveRecord::Base
belongs_to :user
has_many :story_collections
has_many :stories, through: :story_collections
end
story.rb
class Story < ActiveRecord::Base
belongs_to :user
has_many :story_collections
has_many :collections, through: :story_collections
has_many :photos
end
story_collection.rb
class StoryCollection < ActiveRecord::Base
belongs_to :story
belongs_to :collection
end
In views/stories/new.html.erb
<%= f.select :collection_ids, Collection.all.pluck(:name, :id), {}, { multiple: true, class: "selectize" } %>
Creating the collections in collections_controller.rb
class CollectionsController < ApplicationController
def create
#collection = current_user.collections.build(collection_params)
if #collection.save
render json: #collection
else
render json: {errors: #collection.errors.full_messages}
end
end
private
def collection_params
params.require(:collection).permit(:name, :description)
end
end
Creating the stories
class StoriesController < ApplicationController
def new
#story = Story.new
authorize #story
end
def create
#story = current_user.stories.build(story_params)
authorize #story
end
private
def story_params
params.require(:story).permit(:title, :description, category_ids: [],
photos_attributes: [:id, :file_name, :file_name_cache, :_destroy])
end
end
The Story and Collection tables are saving correctly, only the join table is not. Here's the schema for the join table.
create_table "story_collections", force: :cascade do |t|
t.integer "story_id"
t.integer "collection_id"
t.datetime "created_at"
t.datetime "updated_at"
end
You are missing strong-params permitting the parameter story[collection_ids]
def story_params
params.require(:story).permit(
:title,
:description,
collection_ids: [], # you need to whitelist this, so the value gets set
category_ids: [],
photos_attributes: [
:id,
:file_name,
:file_name_cache,
:_destroy
]
)
end

Rails polymorphic # mentions create action

I'm trying to create a simple # mentions model similar to twitters for my app. I've started building it, but I don't know how I would handle the actual creation of the mention. I need some way to scan let's say a status before it's created for any # symbols, then checking the text following against the database for any matching usernames. If there's a match then a mention gets created along with the status. Can someone point me in the right direction?
Here's what I have so far:
db/migrate/create_mentions.rb
class CreateMentions < ActiveRecord::Migration
def change
create_table :mentions do |t|
t.belongs_to :mentionable, polymorphic: true
t.timestamps
end
add_index :mentions, [:mentionable_id, :mentionable_type]
end
end
models/mention.rb
class Mention < ActiveRecord::Base
belongs_to :mentionable, polymorphic: true
end
models/status.rb
class Status < ActiveRecord::Base
attr_accessible :content
has_many :mentions, dependent: :destroy
end
models/member.rb
class Member < ActiveRecord::Base
has_many :mentions, as: :mentionable, dependent: :destroy
end
controllers/mentions_controller.rb
class MentionsController < ApplicationController
before_filter :authenticate_member!
before_filter :load_mentionable
before_filter :find_member
def new
#mention = #mentionable.mentions.new
end
def create
#mention = #mentionable.mentions.new(params[:mention])
respond_to do |format|
if #mention.save
format.html { redirect_to :back }
else
format.html { redirect_to :back }
end
end
end
private
def load_mentionable
klass = [Status].detect { |c| params["#{c.name.underscore}_id"] }
#mentionable = klass.find(params["#{klass.name.underscore}_id"])
end
def find_member
#member = Member.find_by_user_name(params[:user_name])
end
end
config/routes.rb
resources :statuses do
resources :mentions
end
Thanks to this question: parse a post for #username I was able to get this working. My set up:
db/migrate/create_mentions.rb
class CreateMentions < ActiveRecord::Migration
def change
create_table :mentions do |t|
t.belongs_to :mentionable, polymorphic: true
t.belongs_to :mentioner, polymorphic: true
t.integer :status_id
t.integer :comment_id
t.timestamps
end
add_index :mentions, [:mentionable_id, :mentionable_type], :name => "ments_on_ables_id_and_type"
add_index :mentions, [:mentioner_id, :mentioner_type], :name => "ments_on_ers_id_and_type"
end
end
models/mention.rb
class Mention < ActiveRecord::Base
attr_accessible :mentioner_id, :mentioner_type, :mentionable_type, :mentionable_id, :status_id, :comment_id
belongs_to :mentioner, polymorphic: true
belongs_to :mentionable, polymorphic: true
end
models/member.rb
class Member < ActiveRecord::Base
has_many :mentions, as: :mentionable, dependent: :destroy
end
models/status.rb
class Status < ActiveRecord::Base
attr_accessor :mention
has_many :mentions, as: :mentioner, dependent: :destroy
after_save :save_mentions
USERNAME_REGEX = /#\w+/i
private
def save_mentions
return unless mention?
people_mentioned.each do |member|
Mention.create!(:status_id => self.id, :mentioner_id => self.id, :mentioner_type => 'Status', :mentionable_id => member.id, :mentionable_type => 'Member')
end
end
def mention?
self.content.match( USERNAME_REGEX )
end
def people_mentioned
members = []
self.content.clone.gsub!( USERNAME_REGEX ).each do |user_name|
member = Member.find_by_user_name(user_name[1..-1])
members << member if member
end
members.uniq
end
end
config/routes.rb
resources :statuses do
resources :mentions
end
helpers/mentions_helper.rb
module MentionsHelper
def statuses_with_mentions(status)
status.content_html.gsub(/#\w+/).each do |user_name|
member = Member.find_by_user_name(user_name[1..-1])
if member
link_to user_name, profile_path(member.user_name)
else
user_name
end
end
end
end

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