Undefined Method when .build new Object - ruby-on-rails

I'm new to Ruby on Rails and currently trying to make a little test website for me.I've got an issue in my code that states an "undefined method `service_providers' for #"
The line of code which produces the error is the following:
def new
#service_provider = current_user.service_providers.build(serviceprovider_params)
end
My Database Model is Table "User" has_one "ServiceProvider" has_many "Services".
I use the rubygem "devise" for the user-model.
I've tried to transfer the idea of the micropost of the "Ruby on Rails Tutorial" (https://www.railstutorial.org/book/user_microposts) in my example app. In Listing 13.36 there's also this code because with this ruby knows the reference between the current_user and the micrrpost.
I don't have an idea why it isn't working with my code:
Model
class ServiceProvider < ApplicationRecord
belongs_to :user
has_many :service
validates :name, presence: true
validates :street, presence: true
validates :plz, presence: true
validates :location, presence: true
validates :user_id, presence: true
end
Controller
class ServiceProvidersController < ApplicationController
before_action :set_serviceprovider, only: [:show, :edit, :update]
before_action :authenticate_user!
def index
end
def show
end
def new
#service_provider = current_user.service_providers.build(serviceprovider_params)
end
def create
#service_provider = current_user.service_provider.build(serviceprovider_params)
if #service_provider.save
redirect_to #service_provider, notice: "Gespeichert"
else
render :new
end
end
def edit
end
def update
end
private
def set_serviceprovider
#service_provider = Service_Provider.find(params [:id])
end
def serviceprovider_params
params.require(:service_provider).permit(:name, :street, :plz, :location)
end
end
ServiceProvider-Helper
module ServiceProvidersHelper
def current_service_provider
#current_service_provider = service_provider.user_id.find(current_user.id)
end
end
If there's some coding missing which you need for your help, please ask. I'm a newbie in coding with ruby, but i think taht must be the relevant parts of the code which is involved.
Thanks for help.

If you have has_one association then you need to use build_ASSOCIATION_NAME so in this case, it will be build_service_provider
#service_provider = current_user.build_service_provider(serviceprovider_params)
Also, I think you need to take a look at this association
class ServiceProvider < ApplicationRecord
belongs_to :user
has_many :service
If you are building an association with has_many then the class name should be plural.
Change it to
has_many :services

Related

How to limit the CRUD content of Rails' current_user

I'm making a task management system and building a user registration and login system without relying on the devise gem. Currently I am facing a challenge, I want each user (called current_user when logging in) can only see the tasks they created (listed on the homepage/index), including that they can only edit or delete their own tasks. To this end, I added a before action def find_user_task to the controller, but it is completely invalid. Please tell me, what am I missing in principle?
This is part of TasksController
before_action :find_task, only: [:show, :edit, :update, :destroy]
before_action :check_login!, except: [:index, :show]
before_action :find_user_task, only: [:edit, :update, :destroy, :publish]
private
def find_task
#task = Task.find(params[:id])
end
def task_params
p = params.require(:task).permit(:name, :due_date, :note, :priority)
p[:status] = params[:task][:status].to_i
return p
end
def find_user_task
#task = Task.find(params[:id])
#task = Task.find_by(id: params[:id], user_id: current_user.id)
# #task = current_user.tasks.find(params[:id])
end
end
This is part of Model Task
class Task < ApplicationRecord
validates :name, :note, :due_date, :status, :priority, presence: true
has_many :category_tasks
has_many :categories, through: :category_tasks
belongs_to :user, foreign_key: true, optional: true
This is part of Model User
require 'digest'
class User < ApplicationRecord
validates :email, presence: true, uniqueness: true
validates :password, confirmation: true, length: { minimum: 4 }, presence: true
validates :role, presence: true, uniqueness: true
has_many :tasks, dependent: :destroy
hope I can help you here...
first of all, here on this piece of code:
#task = Task.find(params[:id])
#task = Task.find_by(id: params[:id], user_id: current_user.id)
Your second line overrides the first, so you can remove the first one. About your problem, I would suggest you to do that based on your models and relationships... it's easier (and better) than to handle it in controller. The thing is you wanna make sure that a task belongs to a user (and which can have N tasks). Before you create a task, you make sure to set the user_id for that task as the current_user.id. It would be somthing like that:
class User < ApplicationRecord
has_many :tasks
end
and for your task model:
class Task < ApplicationRecord
belongs_to :user
end
Then, on your create action on your tasks controller (or wherever you do that), be sure to set the user_id for your current_user:
#task.user_id = current_user.id
If you still didn't created the foreign_key (user_id) for a task, just make a migration, something like this:
rails g migration AddUserIdToTask user_id:integer
rails db:migrate
Then, if you do it correctly, you can get the tasks for a specific user just acessing them by the relationship: user.tasks (or, in your case):
#tasks = current_user.tasks
For debugging and testing that, rails console is your friend... create a task for a user, then check on console if the user_id is set correctly. Then try to load that user.tasks and see if it brings them as you expected. Go checking it step by step.

Creating homes using nested routes

First this is all of my code
#models/user.rb
class User < ApplicationRecord
has_many :trips
has_many :homes, through: :trips
has_secure_password
accepts_nested_attributes_for :trips
accepts_nested_attributes_for :homes
validates :name, presence: true
validates :email, presence: true
validates :email, uniqueness: true
validates :password, presence: true
validates :password, confirmation: { case_sensitive: true }
end
#home.rb
class Home < ApplicationRecord
has_many :trips
has_many :users, through: :trips
validates :address, presence: true
end
class HomesController < ApplicationController
def show
#home = Home.find(params[:id])
end
def new
if params[:user_id]
#user = User.find_by(id: params[:user_id])
#home = #user.homes.build
end
end
def create
#user = User.find_by(id: params[:user_id])
binding.pry
#home = Home.new
end
private
def home_params
params.require(:home).permit(:address, :user_id)
end
end
I am trying to do something like this so that the home created is associated with the user that is creating it.
def create
#user = User.find_by(id: params[:user_id])
#home = Home.new(home_params)
if #home.save
#user.homes << #home
else
render :new
end
end
The problem is that the :user_id is not being passed into the params. So the #user comes out as nil. I can't find the reason why. Does this example make sense? Am I trying to set the associations correctly? Help or any insight would really be appreciated. Thanks in advance.
The way you would typically create resources as the current user is with an authentication such as Devise - not by nesting the resource. Instead you get the current user in the controller through the authentication system and build the resource off it:
resources :homes
class HomesController < ApplicationController
...
# GET /homes/new
def new
#home = current_user.homes.new
end
# POST /homes
def create
#home = current_user.homes.new(home_parameters)
if #home.save
redirect_to #home
else
render :new
end
end
...
end
This sets the user_id on the model (the Trip join model in this case) from the session or something like an access token when dealing with API's.
The reason you don't want to nest the resource when you're creating them as a specific user is that its trivial to pass another users id to create resources as another user. A session cookie is encrypted and thus much harder to tamper with and the same goes for authentication tokens.
by using if params[:user_id] and User.find_by(id: params[:user_id]) you are really just giving yourself potential nil errors and shooting yourself in the foot. If an action requires a user to be logged use a before_action callback to ensure they are authenticated and raise an error and bail (redirect the user to the sign in). Thats how authentication gems like Devise, Knock and Sorcery handle it.

Active Model Serializer customize json response

So I'm having a problem with ams. I'm using Rails 5.2 and I read lots of tutorials and even when I did exactly what they showed I still have something that they don't and I didn't find answer on google.
I have model Course, Video, Quiz and Segment.
Course has many segment, segment can be video or quiz (I'm using sti).
This is how I wrote it:
app/models/course.rb
class Course < ApplicationRecord
validates :title ,presence: true
validates :author ,presence: true
has_many :videos
has_many :quizs
has_many :segments
end
app/models/segment.rb
class Segment < ApplicationRecord
belongs_to :course
end
app/models/quiz.rb
class Quiz < Segment
validates :course_id ,presence: true
validates :name ,presence: true
belongs_to :course
end
app/models/video.rb
class Video < Segment
validates :course_id ,presence: true
validates :name ,presence: true
belongs_to :course
end
app/controllers/courses_controller.rb
class CoursesController < InheritedResources::Base
def show
#course = Course.find(params[:id])
render json: #course.attributes
end
def index
#courses = Course.all
render json: #courses
end
end
app/serializers/course_serializer.rb
class CourseSerializer < ActiveModel::Serializer
attributes :title, :author
has_many :segments
end
and this is what is showed me
I have couple of problems:
I don't know where this data name come from and I don't know how to change it or hide it.
even when I request to see one course I get the created date and other stuff I didn't want, although I configured it so I only see title and author.
I want to know if I can custom the json response so I won't see the title of the relations or change it's name.
You haven't created a SegmentSerializer. By default, AMS will serialize all fields.
class SegmentSerializer < ActiveModel::Serializer
attributes :name
end
Remove the attributes method call. It returns a Hash and then your serializer is not used.
```
class CoursesController < InheritedResources::Base
def show
#course = Course.find(params[:id])
render json: #course
end
def index
#courses = Course.all
render json: #courses
end
end
```
Use the key option
has_many :segments, key: :your_key

Rails: callback in controller to change attribute from true to false in database

so I'm testing how to make a multiplayer Tic Tac Toe game, and for this I made a User model and a Game model, and by a has_many_through association they have various game_users.
Each game has an attribute: "seeking_players", that by default is true. When I create a new game_user, I check if they're exists a game with the seeking_players attribute set to true. If such a game exists, I make a new game_user for this game and I want to set this attribute to false.
But whatever I try, I can't seem to change this attribute. So, my question: what's wrong with this code: EDIT: this is the new working code after suggestions from #Малъ Скрылевъ
class GamesController < ApplicationController
before_action :logged_in_user
before_action :assign_game, only: [:new]
def new
#game.game_users.create(user: current_user)
update_seeking_players
redirect_to game_url(id: #game.id)
end
def game
#game = Game.find_by_id(params[:id])
end
private
def assign_game
#game = Game.find_by_seeking_players(true) || Game.create
end
def update_seeking_players
if #game.users.size == 2
#game.update_attributes(seeking_players: false)
end
end
end
PS: I also tried changing this "seeking players" attribute in the Game model (with a callback "after_add"), which is maybe a more appropriate place? But I really can't figure out how to do this...
UPDATE:
these are the Game & GameUser model
class Game < ActiveRecord::Base
has_many :game_users
has_many :users, :through => :game_users
end
class GameUser < ActiveRecord::Base
belongs_to :game
belongs_to :user
validates :game_id, presence: true
validates :user_id, presence: true
end
UPDATE 2 the migration for seeking_players
class AddSeekingPlayersWithIndexToGames < ActiveRecord::Migration
def change
add_column :games, :seeking_players, :boolean, :default => true
add_index :games, :seeking_players
end
end
I see some issues with the contoller.
It seems that gameRequest isn't real action name like new, so replace it with real one.
Try not using camel case (or similar) in names of variables and method, since it descreace understability of the code.
So I propose you to change the contoller as follows:
class GamesController < ApplicationController
before_action :logged_in_user
before_action :assign_game, only: [:new]
def new
#game.game_users.create(user: current_user)
update_seeking_players
redirect_to game_url(#game)
end
def game
end
private
def assign_game
#game = Game.find_by_seeking_players(true) || Game.create
end
def update_seeking_players
if #game.users.size == 2
#game.update(seeking_players: false)
end
end
end
so and if seeking_players is a real column, and it must be, so remove attr_accessor, since it block access to db column:
class Game < ActiveRecord::Base
has_many :game_users
has_many :users, :through => :game_users
end

Match multiple models in single Rails route with friendly_id

I have a Company and a User model, both with a slug via friendly_id. Slugs are ensured to be unique across both models.
I'd like to have URLs:
http://www.example.com/any_company_name
http://www.example.com/any_user_name
e.g. both /apple and /tim
I'm not sure how to achieve this in Rails.
I have tried various permutations of:
routes.rb:
resources :users, path: ''
resources :companies, path: ''
get '*search', to: 'my_controller#redirect'
and
my_controller#redirect:
#company = Company.friendly.find(params[:search])
redirect_to #company if #company
#user = User.friendly.find(params[:search])
redirect_to #user if #user
However I can't get it to work. I can get /apple to redirect to /companies/apple and /tim to redirect to /users/tim (by removing the path: '' option) but this is not what I want to achieve.
Instead redirect with urls, you can redirect with controller#action by using url_for.
For example:
my_controller#redirect:
#company = Company.friendly.find(params[:search])
redirect_to url_for(action: 'show', controller: :companies , status: :success, company_id:#company.id)
#user = User.friendly.find(params[:search])
redirect_to url_for(action: 'show', controller: :users, status: :success,user_id:#user.id)
I had a similar problem and was able to solve it by creating a PublicSlug model with a slug attribute and a polymorphic association to a "Sluggable" class. I also used a Sluggable concern that I included in models I would like to query.
The PublicSlug model
class PublicSlug < ActiveRecord::Base
extend FriendlyId
friendly_id :sluggable_name, use: :slugged
belongs_to :sluggable, polymorphic: true
private
# Generate the slug based on the title of the Sluggable class
def sluggable_name
sluggable.name
end
end
The Sluggable concern
module Sluggable
extend ActiveSupport::Concern
included do
has_one :public_slug, dependent: :destroy, as: :sluggable
delegate :slug, to: :public_slug
end
end
Company and User models.
class User < ActiveRecord::Base
include Sluggable
end
class Company < ActiveRecord::Base
include Sluggable
end
I can now query both models using
Sluggable.friendly.find(slug).sluggable
The redirect could be handled in your controller as follows:
def redirect
#sluggable = Sluggable.friendly.find(params[:search]).sluggable
redirect_to #sluggable
end
Building off of #Essn's answer...
Still use a PublicSlug model:
# app/models/public_slug.rb
class PublicSlug < ActiveRecord::Base
extend FriendlyId
friendly_id :sluggable_name, use: :slugged
belongs_to :sluggable, polymorphic: true
validates :slug, presence: true, uniqueness: { case_sensitive: false }
private
# Generate the slug based on the title of the Sluggable class
def sluggable_name
sluggable.name
end
end
And a Sluggable concern:
# app/models/concerns/sluggable.rb
module Sluggable
extend ActiveSupport::Concern
included do
before_validation :create_public_slug
has_one :public_slug, dependent: :destroy, as: :sluggable
delegate :slug, to: :public_slug
private
def create_public_slug
self.public_slug = PublicSlug.new unless public_slug.present?
end
end
end
Include that concern in all models you want to lookup:
# app/models/user.rb
class User < ActiveRecord::Base
include Sluggable
...
end
Create a migration:
# db/migrate/...create_public_slugs.rb
class CreatePublicSlugs < ActiveRecord::Migration[5.0]
def change
create_table :public_slugs do |t|
t.references :sluggable, polymorphic: true
t.string :slug
end
end
end
Then you can lookup the model via:
# app/controllers/home_controller.rb
class HomeController < ApplicationController
# /:slug
def show
#sluggable = PublicSlug.friendly.find(params[:id]).sluggable
render #sluggable
end
end

Resources