I have two models: Account and Profile.
I want render json API with attributes of Profile and include there one attribute from Account.
profile_serializer.rb
class ProfileSerializer < ActiveModel::Serializer
attributes :first_name, :middle_name, :last_name, :role
def role
#UserAccountSerializer.new(object.role)
object.account.role
end
end
account_serializer.rb
class UserAccountSerializer < ActiveModel::Serializer
attributes :role
end
profile.rb
class Profile < Grape::API
desc 'Current user profile'
get '/', serializer: ProfileSerializer do
current_user.profile
end
end
accounts_controller.rb
class AccountsController < AdminController
def index
#accounts = Account.all
end
def show
render json: #account
end
def update
#account = Account.find(params[:id])
redirect_to accounts_path if #account.update(role: params[:role])
end
end
account.rb
class Account < ApplicationRecord
belongs_to :profile
enum role: %i[user admin]
after_initialize :set_default_role, if: :new_record?
def set_default_role
self.role ||= :user
end
end
profile.rb
class Profile < ApplicationRecord
has_one :account
end
schema.rb
create_table "accounts", force: :cascade do |t|
t.string "email", default: "", null: false
t.bigint "profile_id", null: false
t.integer "role", default: 0
t.index ["email"], name: "index_accounts_on_email", unique: true
t.index ["profile_id"], name: "index_accounts_on_profile_id"
end
create_table "profiles", force: :cascade do |t|
t.string "last_name", null: false
t.string "first_name", null: false
t.string "middle_name"
end
Grape give me this error: undefined method `role' for nil:NilClass
Related
I am doing some refactoring and I have seen this project for a while and it worked from what I last recall. But the issue is, I am trying to create a flight and I keep getting "ActiveModel::MissingAttributeError (can't write unknown attribute flights_count):" when trying create a new flight.
As far my models in place:
My Flight, Pilot models
class Flight < ActiveRecord::Base
has_many :passengers
belongs_to :destination
belongs_to :pilot, counter_cache: true
accepts_nested_attributes_for :passengers
belongs_to :user, class_name: "Flight" ,optional: true
validates_presence_of :flight_number
validates :flight_number, uniqueness: true
scope :order_by_flight_international, -> { order(flight_number: :asc).where("LENGTH(flight_number) > 3") }
scope :order_by_flight_domestic, -> { order(flight_number: :asc).where("LENGTH(flight_number) <= 2 ") }
def dest_name=(name)
self.destination = Destination.find_or_create_by(name: name)
end
def dest_name
self.destination ? self.destination.name : nil
end
def pilot_name=(name)
self.pilot = Pilot.find_or_create_by(name: name)
end
def pilot_name
self.pilot ? self.pilot.name : nil
end
end
class Pilot < ActiveRecord::Base
belongs_to :user, optional: true
has_many :flights
has_many :destinations, through: :flights
validates_presence_of :name, :rank
validates :name, uniqueness: true
scope :top_pilot, -> { order(flight_count: :desc).limit(1)}
end
Edit
Flight Controller
class FlightsController < ApplicationController
before_action :verified_user
layout 'flightlayout'
def index
#flights = Flight.order_by_flight_international
#dom_flights = Flight.order_by_flight_domestic
end
def new
#flight = Flight.new
10.times {#flight.passengers.build}
end
def create
#flight = Flight.new(flight_params)
# byebug
if #flight.save!
redirect_to flight_path(current_user,#flight)
else
flash.now[:danger] = 'Flight Number, Destination, and Pilot have to be selected at least'
render :new
end
end
private
def flight_params
params.require(:flight).permit(:flight_number,:date_of_flight, :flight_time, :flight_id, :destination_id, :pilot_id, :pilot_id =>[], :destination_id =>[], passengers_attributes:[:id, :name])
end
end
Edit
Flights, Pilot Schemas
create_table "flights", force: :cascade do |t|
t.integer "pilot_id"
t.integer "destination_id"
t.string "flight_number"
t.string "date_of_flight"
t.string "flight_time"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "pilots", force: :cascade do |t|
t.string "name"
t.string "rank"
t.integer "user_id"
t.integer "flight_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "flight_count", default: 0
end
As I said before when I last worked on this project everything was working fine, but I am faced with this issue. What am I doing wrong this time.
You have defined a counter_cache in your Flight model for pilots. When you just use counter_cache: true to define it, ActiveRecord will look for a column named flights_count in your pilots table but I see that you have named it as flight_count instead. You can either rename the column to flights_count or pass the custom column name to it by using counter_cache: :flight_count
Source https://guides.rubyonrails.org/association_basics.html#options-for-belongs-to-counter-cache
hey guys im working on a application where a devise user sign ups and logs in, Once the user logs in they can 'create a team' or 'join a team'. I have my associations set up like this
user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
validates_presence_of :phone, :city, :state, :street, :zip, presence: true, on: :create
belongs_to :team
end
team.rb
class Team < ApplicationRecord
has_many :users
end
and my tables are set up
schema.rb
create_table "teams", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "team_name"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "firstname"
t.integer "team_id"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
team_controller.rb
class TeamController < ApplicationController
before_action :authenticate_user!
def index
#team = current_user.team
end
def new_team
end
def create_team
#team = current_user.create_team(sanitize_team)
if #team.save
redirect_to team_root_path
else
render json: #team.errors.full_messages
end
end
def join_team
#teams = Team.all
end
def team
end
private
def sanitize_team
params.require(:team).permit(:team_name, :team_statement)
end
end
I want the users 'team_id' attribute to update with the teams id when they create a team. or when they join a team. Are my associations correct? how would i make this happen in the controller ?
Yes, associations are correct. You can do it better only by adding foreign key to your database schema. It can be done by generator rails g migration AddTeamToUsers team:references
More information about associations can be found here: https://guides.rubyonrails.org/association_basics.html
In controller you have to change only the whitelisting params to allow team_id. And you probably need to add to your form in view something like this:
<%= f.select :team_id, Team.all.map { |t| [t.team_name, t.id] } %>
Let's strip your code example down to the minimum required:
# app/models/team.rb
class Team < ApplicationRecord
has_many :users
end
# app/models/user.rb
class User < ApplicationRecord
belongs_to :team
end
# db/migrate/20181124230131_create_teams.rb
class CreateTeams < ActiveRecord::Migration[5.2]
def change
create_table :teams do |t|
t.string :team_name
t.timestamps
end
end
end
# db/migrate/20181124230136_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.belongs_to :team
t.timestamps
end
end
end
Then in your controller:
team = Team.where(team_name: 'foo').first_or_create!
team.users << current_user
Start by setting the association up as optional:
class User < ApplicationController
belongs_to :team, optional: true
end
Otherwise the validations on the user model will not let the user be saved without a team.
Then setup the teams resource:
# config/routes.rb
resources :teams do
post :join
end
post :join creates an additional POST /teams/:team_id/join route.
Then setup the controller:
class TeamsController
# ...
# GET /teams/new
def new
#team = Team.find
end
# POST /teams
def create
#team = Team.new(team_params)
if #team.save
unless current_user.team
current_user.update(team: #team)
end
redirect_to 'somewhere'
else
render :new
end
end
# ...
def join
#team = Team.find(params[:team_id])
if current_user.update(team: #team)
redirect_to #team, notice: 'Team joined'
else
redirect_to #team, error: 'Could not join team'
end
end
#
private
def team_params
params.require(:team).permit(:team_name, :team_statement)
end
end
Note that prefixing your action names is neither needed nor compatible with the "Rails way". Prefixing column names is also largely superfluous.
I have 3 models - User, Shipment and Friendship. User can be friends with another user via Friendship-model. User also can create Shipments and can add a Friend-User to it. There is address-attribute in User and Shipment models. I need to give User a possibility to fill that address field in 2 ways at the same form:
By filling the address field manually.
By choosing from select-list a Friend of that User - so the Friends
address-attribute transfers and fills the Shipments adress-attribute
(like ctrl-c/ctrl-v) and User can Submit the form.
I can guess, that AJAX is needed to refresh the content without refreshing the page.
Shipment model:
class Shipment < ActiveRecord::Base
belongs_to :user
belongs_to :friendship
validates :image, presence: true
validates :user_id, presence: true
end
Shipments controller:
class ShipmentsController < ApplicationController
helper_method :shipment, :user
before_action :set_shipment, only: [:show]
before_action :authenticate_user!
before_action :require_same_user, only: [:show]
def index
#shipments = Shipment.all
end
def new
#shipment = Shipment.new
end
def create
#shipment = Shipment.new(shipment_params)
#shipment.user = current_user
if #shipment.save
flash[:success] = "Shipment etc."
redirect_to shipment_path(#shipment)
else
render 'new'
end
end
def show
#shipment = Shipment.find(params[:id])
end
private
def user
#user = current_user
end
def shipment
#shipment = user.shipments.new
end
def shipment_params
params.require(:shipment).permit(:name, :kg, :length, :width, :height,
:adress, :image, :user_id, :friend_id)
end
def set_shipment
#shipment = Shipment.find(params[:id])
end
def require_same_user
if current_user != #shipment.user
flash[:alert] = "Restricted/"
redirect_to root_path
end
end
end
User model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :shipments, dependent: :destroy
has_many :friendships
has_many :friends, through: :friendships
has_many :inverse_friendships, :class_name => 'Friendship',
:foreign_key => 'friend_id'
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
end
Users controller (the User itself is created by Devise)
class UsersController < ApplicationController
before_action :authenticate_user!
def show
#user = User.find(params[:id])
end
def my_friends
#friendships = current_user.friends
end
def search
#users = User.search(params[:search_param])
if #users
#users = current_user.except_current_user(#users)
render partial: 'friends/lookup'
else
render status: :not_found, nothing: true
end
end
private
def require_same_user
if current_user != set_user
flash[:alert] = "Restricted."
redirect_to root_path
end
end
def set_user
#user = User.find(params[:id])
end
end
Friendship model:
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: 'User'
has_many :shipments
end
Friendships controller:
class FriendshipsController < ApplicationController
def index
#friendships = Friendship.all
end
def create
#friendship = current_user.friendships.build(:friend_id => params[:friend_id])
if #friendship.save
flash[:success] = "Added to friends."
redirect_to my_friends_path
else
flash[:alert] = "Impossible to add as a friend."
redirect_to my_friends_path
end
end
def destroy
#friendship = current_user.friendships.find_by(friend_id: params[:id])
#friendship.destroy
flash[:notice] = "Unfriended."
redirect_to my_friends_path
end
private
def name
#name = friend_id.name
end
end
Schema:
create_table "friendships", force: :cascade do |t|
t.integer "user_id"
t.integer "friend_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "shipments", force: :cascade do |t|
t.string "name"
t.integer "length"
t.integer "width"
t.text "adress"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
t.integer "height"
t.integer "kg"
end
add_index "shipments", ["user_id"], name: "index_shipments_on_user_id"
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
t.integer "phone", limit: 30
t.string "username"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
Shipment form view (new):
<%= form_for(shipment, html: { multipart: true }) do |f| %>
<p>Choose a friend from your friendlist or fill the address field manually:</p>
<%= f.select :friend_id, user.friendships.map{ |friendship|
[friendship.friend.name, friendship.id] } %>
<%= f.text_field :adress, placeholder: "Address and index" %>
<%= f.submit "Submit", class: "button" %>
<% end %>
With ActiveRecord::Base, you could use eager loading and nested form to solve your problem.
Eager load the object related to the main object and use nested form to display the related object.
I have a user model and a shout model. I am trying to have a user be associated with a shout. I did not make this association upon creation of the shouts table so I had to run a new migration. Below is my table, the models of each, and the output when from my console I run a command to try and find the user_id of a shout. Can you see what I am doing wrong?
schema:
create_table "shouts", force: :cascade do |t|
t.string "title"
t.text "description"
t.integer "user_id"
end
add_index "shouts", ["user_id"], name: "index_shouts_on_user_id"
create_table "users", force: :cascade do |t|
t.string "email", null: false
t.string "password_digest", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "username"
end
User Model:
class User < ActiveRecord::Base
validates :email, presence: true, uniqueness: true
validates :password_digest, presence: true
has_many :shouts
end
Shout Model:
class Shout < ActiveRecord::Base
belongs_to :user
end
Shout Controller:
class ShoutsController < ApplicationController
def new
#new_shout = Shout.new
end
def create
#new_shout = Shout.new(shouts_params)
if #new_shout.user_id == nil
render new_shout_path
elsif #new_shout.save
redirect_to dashboard_path
else
render new_shout_path
end
end
private
def shouts_params
params.require(:shout).permit(:title, :description, :user_id)
end
end
Some test code:
> Shout.find(4)
> #<Shout id: 4, title: "four", description: "four", user_id: nil>
Creating an instance of user from the console, working:
> User.first.shouts.create(title: 'four', description: 'four')
>[["title", "four"], ["description", "four"], ["user_id", 1]
Migration file:
class AddUserRefToShouts < ActiveRecord::Migration
def change
add_reference :shouts, :user, index: true, foreign_key: true
end
end
Here are a couple admittedly hacky options (but they'll work) if you don't want to follow the approach suggested in the comments. You could pass the user_id as a hidden field so it'll be included in the params or you can expressly set it in the create action.
If you want to pass as a hidden field, on your shout form add:
<%= f.hidden_field :user_id, value: current_user.id %>
Alternatively, if you want to handle in your create action:
def create
#shout = Shout.new(shout_params)
#shout.user_id = current_user.id
#shout.save
end
I am new to Ruby and Rails, trying to fix an error I constantly get. Not sure how to fix it. Please help..
Route.rb
namespace :my do
namespace :account do
resource :details, :only => [:show, :update]
resources :addresses
end
end
AddressesController
class My::Account::AddressesController < MyController
def index
#addresses = current_user.addresses
end
def new
#address = current_user.addresses.new
end
....
end
Error - undefined method `addresses'
NoMethodError in My::Account::AddressesController#index
undefined method `addresses' for #<User:0x007fc955029380>
Schema.rb for customer addresses and users
create_table "customer_addresses", force: true do |t|
t.integer "customer_id"
t.string "name"
t.string "line_1"
t.string "line_2"
t.string "line_3"
t.string "line_4"
t.string "line_5"
t.string "postcode"
t.string "phone"
t.datetime "deleted_at"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "customer_addresses", ["customer_id"], name: "index_customer_addresses_on_customer_id"
create_table "users", force: true do |t|
t.string "first_name"
t.string "last_name"
t.string "email"
t.string "password_digest"
t.datetime "created_at"
t.datetime "updated_at"
t.string "password_reset_token"
t.datetime "password_reset_token_at"
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
User.rb
class User < ActiveRecord::Base
validates :email, :presence => true, :uniqueness => true
has_secure_password
...
end
Customer.rb
class Customer < User
has_many :addresses
def self.register(attributes)
customer = create!(attributes)
return customer
end
def full_name
"#{first_name} #{last_name}"
end
end
Address.rb
class Customer::Address < ActiveRecord::Base
belongs_to :customer
self.table_name = 'customer_addresses'
default_scope { where(:deleted_at => nil) }
validates :line_1, :postcode, :phone, :presence => true
end
You need to define the relationship on both models.
class User < ActiveRedord::Base
has_many :addresses, class_name: 'CustomerAddress', foreign_key: 'customer_id'
end
class Address < ActiveRecord::Base
belongs_to :user, foreign_key: 'customer_id'
end
Add a #current_customer method to your ApplicationController that return a Customer-instance instead of a User-instance:
class ApplicationController
#…
private
def current_customer
current_user && Customer.find_by_id(current_user.id)
end
end
then change your code like this:
class My::Account::AddressesController < MyController
def index
#addresses = current_customer.addresses
end
def new
#address = current_customer.addresses.new
end
#…
end