I'm trying to access all relations beetween two models: Serie has multiple categories and multiple categories can be in diferent series. Is a many to many relationship.
I try to do the following:
class SeriesController < ApplicationController
def category
#category = params[:category]
#series = []
Serie.all.each do |serie|
#serie.categories.all.each do |cat|
if #cat.category == #category
#series << #serie
end
end
end
end
end
Rails throws me that exception:
undefined method `categories' for nil:NilClass
Here are the models:
class Serie < ApplicationRecord
has_many :types
has_many :categories, through: :types
end
class Type < ApplicationRecord
belongs_to :serie
belongs_to :category
end
class Category < ApplicationRecord
has_many :types
has_many :series, through: :types
end
class CreateCategories < ActiveRecord::Migration[5.1]
def change
create_table :categories do |t|
t.string :category
t.timestamps
end
end
end
class CreateTypes < ActiveRecord::Migration[5.1]
def change
create_table :types do |t|
t.references :serie, index: true
t.references :category, index: true
t.timestamps
end
end
end
I don't know why this doesn't work.
Any idea? Thanks.
Change
Serie.all.each do |serie|
#serie.categories.all.each do |cat|
if #cat.category == #category
#series << #serie
end
# ...
to
Serie.all.each do |serie|
serie.categories.all.each do |cat|
if cat.category == #category
#series << serie
end
# ...
because there a local variables serie and cat defined in the blocks, but no instance variables #serie or #cat
You are mixing up your variables in your create method. You reference #serie which is set to equal [] so in the each it is empty, the variable you create there is serie so use that.
....
Serie.all.each do |serie|
serie.categories.all.each do |cat|
if cat.category == #category
#series << serie
end
end
end
Related
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
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
I made categories using this comment as a guide. I set categories as a resource in my routes, and used this to query the specific Product instances:
class CategoriesController < ApplicationController
def show
#category = Category.find(params[:id])
#products = []
products = Product.all
products.each do |product|
if product.categories.include?(#category)
#products << product
end
end
end
end
I then iterate over #products in my view. This has become a problem because I want categories/show to share a view with products/index to be more DRY. products/index just uses <%= render #products %>, and I can't pass render an array.
How can I query products with specific categories?
Pseudo-ish code of what I had in mind:
class CategoriesController < ApplicationController
def show
#category = Category.find(params[:id])
#products = Product.where(categories.include?(#category))
end
end
Category setup from comment:
class Category < ActiveRecord::Base
acts_as_tree order: :name
has_many :categoricals
validates :name, uniqueness: { case_sensitive: false }, presence: true
end
class Categorical < ActiveRecord::Base
belongs_to :category
belongs_to :categorizable, polymorphic: true
validates_presence_of :category, :categorizable
end
module Categorizable
extend ActiveSupport::Concern
included do
has_many :categoricals, as: :categorizable
has_many :categories, through: :categoricals
end
def add_to_category(category)
self.categoricals.create(category: category)
end
def remove_from_category(category)
self.categoricals.find_by(category: category).maybe.destroy
end
module ClassMethods
end
end
class Product < ActiveRecord::Base
include Categorizable
end
p = Product.find(1000) # returns a product, Ferrari
c = Category.find_by(name: 'car') # returns the category car
p.add_to_category(c) # associate each other
p.categories # will return all the categories the product belongs to
I think this is your main question:
How can I query products with specific categories?
Product.includes(:categories).where(categories: {id: params[:id]}).references(:categories)
Check out this link for more info on preloading associations: http://blog.arkency.com/2013/12/rails4-preloading/
In my quiz game Rails project, I have a table for "Participations" that stores information on the user, the quiz category, and the quiz score after a user completes the test.
class CreateParticipations < ActiveRecord::Migration
def change
create_table :participations do |t|
t.references :user
t.string :category
t.boolean :finished, default: false
t.integer :current_question_index, default: 0
t.integer :score, default: 0
t.timestamps
end
end
end
In my user.rb, I specify an association that a user has_many :participations, which allows a user to play multiple quizzes while storing categories/scores in the table.
If I want to show a user a table of his results (so return all Participations results, but only for those that match the user) in a view, can I call that without generating a new controller?
You can just do like below
#in the controller action
#user_participations = Participation.where(user_id: current_user.id)
and just call #user_participations in the view.
#config/routes.rb
resources :users do
resources :participations, path: "results", only: :index #-> url.com/users/:user_id/results
end
#app/controllers/participations_controller.rb
class ParticipationsController < ApplicationController
def index
#user = User.find params[:user_id]
#participations = #user.participations
end
end
#app/views/participations/index.html.erb
<% #participations.each do |participation| %>
<%= participation.score %>
<% end %>
--
If you have your associations set up correctly, you should be using the associative method (#user.participations), which will basically do what Pavan has suggested:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :participations
end
#app/models/participation.rb
class Participation < ActiveRecord::Base
belongs_to :user
end
Users controller
has_many :participations
def participations
#user = User.find(params[:id])
#participations = #users.participations.build(params[:participation_id])
end
end
ParticipationsController
belongs_to :user
In your routes file you can create the route by
GET 'users/:id/participations', to: 'users#participations', as: 'user_participations'
That will give you a user_participations_path route
So if you wanted to link to it you could add
<%= link_to 'Show user games!', user_participations_path(#participations.user) %>
Then in views/users/participations
<% #participations.each do |participation| %>
<%= participation.inspect %>
<% end %>
Let me know how you go!
EDIT
Please not that the has_many and belongs_to declarations should be in the user.rb and participation.rb models respectively. Thanks to #richpeck for picking up the mistake.
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