RAILS-4 - has_and_belongs_to_many - ruby-on-rails

I have two models, sellers and customers.
I want it to be a customer has a seller, many customer can have the same seller.
Ideally i want can do customer.seller = seller
With belongs_to association a seller can belongs to jsute one customer.
I use has_and_belongs_to_many association though a in my cas un customer can only have one seller.
# migration
create_table :sellers do |t|
t.string :name
t.timestamps null: false
end
create_table :customers do |t|
t.string :name
t.timestamps null: false
end
create_table :customers_sellers, id: false do |t|
t.belongs_to :customer, index: true
t.belongs_to :seller, index: true
end
# models/seller.rb
class Seller < ActiveRecord::Base
has_and_belongs_to_many :customers
end
# models/customer.rb
class Customer < ActiveRecord::Base
has_and_belongs_to_many :sellers
end
With that i can't do something like that:
customer = Customer.create(name: "John")
seller = Seller.create(name: "Dave")
customer.sellers = seller
I have an error
NoMethodError: undefined method `each' for #<Seller:0x0000000582fb18>
But i can:
customer.sellers<< seller
But if I change the name of the seller like that
Seller.first.name = "Bud"
I want it's to be also modified in my customer.sellers.name.
It's possible to make something like that?

Ok, so for a start, Seller.first.name = "Bud" does nothing to update the database, that name attribute is set on the Seller.first instance which is then lost because you've not assigned it to any variable.
So you'll need to change that to either:
Seller.first.update name: "Bud"
In order to update the DB with the new value, or (more likely) something like:
seller = Seller.first
seller.name = "Bud"
seller.save
That's step 1, actually getting that value saved into the database.
The second problem is that if you have already read customer.sellers from the database then your application already has the value for each of the names stored in memory, you need to reload at least that first record from the DB in order to get the new value:
customer.sellers.reload
And now (I'm assuming that Seller.first is also customer.sellers.first) customer.sellers.first.name will be "Bud"

Related

Rails - belongs_to has_many association error

Goal: A user can create a User account (devise), and subsequently a Group. Each User only belongs_to one group and a group has_many Users.
After creating and running the Migrations - If I attempt to create a User i’m being presented with the following error: “1 error prohibited this user from being saved: Group must exist”.
Clearly the current setup wants a group_id to exist when creating a user.
Is a belongs_to / has_many association correct for this situation? Should this be a has_one?
Should both migrations have a foreign key attribute?
Is setting #group.user_id = current_user.id in GroupsController#create a suitable way to assign the creating user to the group? I tried to do this in the Groups model, by using a callback but I wasn’t able to access the current_user variable.
I would also like to enforce (at the database level) that a user can only belong to one group - Is this achieved using unique => true in the schema?
How can I enforce (at the database level) that a group must have a user?
.
class Group < ApplicationRecord
has_many :users
validates :users, presence: true
end
class User < ApplicationRecord
...
belongs_to :group
...
end
class GroupsController < ApplicationController
...
def create
#group = Group.new(group_params)
#group.user_id = current_user.id
...
end
...
private
...
def group_params
params.require(:group).permit(:name, :user_id)
end
...
end
class AddGroupReferenceToUser < ActiveRecord::Migration[5.0]
def change
add_reference :users, :group, foreign_key: true
end
end
class AddUserReferenceToGroup < ActiveRecord::Migration[5.0]
def change
add_reference :groups, :user, foreign_key: true
end
end
ActiveRecord::Schema.define(version: 20160903125553) do
create_table "groups", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.index ["user_id"], name: "index_groups_on_user_id"
end
create_table "users", force: :cascade do |t|
...
t.integer "group_id"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["group_id"], name: "index_users_on_group_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
Is a belongs_to / has_many association correct for this situation? Should this be a has_one?
To setup a relationship where a user can only belong to a single group but groups have many users then you are on the right track.
class Group < ActiveRecord::Base
has_many :users
end
class User < ActiveRecord::Base
belongs_to :group
end
belongs_to places the foreign key column as users.group_id. Using has_one would place it on groups.user_id column which would only allow a one to one mapping - a group could only have a single user.
Should both migrations have a foreign key attribute?
No - only the the users table should contain a foreign key. The AddUserReferenceToGroup should be removed or you should write another migration which removes the groups.user_id column if you have pushed it to production.
Is setting #group.user_id = current_user.id in GroupsController#create a suitable way to assign the creating user
to the group?
No - since the group_id column is on the users column you need to update the users table - not groups.
if #group.save
current_user.update(group: #group)
end
I would also like to enforce (at the database level) that a user can only belong to one group - Is this achieved using unique => true in the schema?
No - since a row on the users table can only have one id in the groups_id column a user can only belong to one group anyways. Using unique => true would create a uniqueness index on the users.groups_id column which would only allow a single users to be associated with a group.
How can I enforce (at the database level) that a group must have a user?
This is not actually possible. To be able to associate a user with a group you must first insert the group into the database so that it is assigned a id.
Adding a validation on the software level would also create a "chicken vs egg" situation where a group cannot be valid since it has no users and a user cannot be associated with a group since it is not persisted.
You can however setup a constraint on the belongs_to end by declaring the foreign key column as NOT NULL.

Relation one to many with 2 foreigns keys of the same table Ruby on rails

I have a table called customers. These table has two addresses . One address of work and One direccion of house
Those 2 addresses belong to a table called addresses
I don't know how to relation those 2 tables
Migrations
class CreateCustomers < ActiveRecord::Migration
def change
create_table :customers do |t|
t.string :name
t.integer :address_id #Address of work
t.integer :address_id_1 #Address of home
t.timestamps
end
end
end
class CreateAdresses < ActiveRecord::Migration
def change
create_table :adresses do |t|
t.string :street
t.timestamps
end
end
end
I do not believe this is a good approach or database design. If you want to proceed this way and not get out of the rails convention just create two columns address_id and address_two_id
and in customer.rb
belongs_to :address, class_name: "Address"
belongs_to :address_two, class_name: "Address"
By default rails takes the name of the foreign key and stores it in a column called "name"+"_id"
The better way is two have a column customer_id in your Address model and create a relation in your customer class
customer.rb
has_many :addresses
And you can also validate that a customer has no more than two addresses by adding this validation to
address.rb
validate :validate_two_addresses
def validate_two_addresses
address_count = Address.where(customer_id: self.customer_id).count
errors.add(:base, "You cannot have more than 2 addresses.") unless address_count < 3
end

rails nested resource unknown attribute error

I have a Contract and a Task_Order model. I keep getting an unknown attribute error for contract_id Each Contract has many Task Orders. I have read other nested models unknown attribute error questions but they haven't been able to help me. Please keep in mind I am pretty new to Rails and would greatly appreciate any help I can get. I am using Rails 4.0
Contract Model:
has_many :task_orders
Contract schema:
create_table "contracts", force: true do |t|
t.string "contractId"
t.string "contractName"
end
Task Order Model:
belongs_to :contracts
Task Order Schema:
create_table "task_orders", force: true do |t|
t.string "contract_Id"
t.string "task_orderId"
t.string "task_orderName"
end
When I click Show Contract, I get the error:
unknown attribute: contract_id
This is the line that gets highlighted:
<%= form_for([#contract, #contract.task_orders.new]) do |f| %>
I can tell that Rails is trying to print out contract_id, which is not in my Contract model... so how can I get it to print out contractId instead - which is in my Contract model?
Thanks!!
Task Order Model should have this line belongs_to contract
belongs_to association should be declared as a singular of corresponding model
Also there should be contract_id column within task_orders table.
Diagram below explains default behavior of belongs_to in Rails
Something you need to be aware of is the foreign_key of Rails (and relational databases in general):
Foreign Key
Rails' standard foreign_key is to use snake_case (contract_id), however, you can use non-conventional foreign_keys like this:
#app/models/order.rb
belongs_to :contract, foreign_key: "contract_Id"
#schema SHOULD be:
create_table "orders", force: true do |t|
t.integer "contract_id" #-> should
t.string "contract_Id" #-> your current
end
Primary Key
create_table "contracts", force: true do |t|
t.string "contractId" #-> don't need
t.string "contractName" #-> your current
t.string "name" #-> should be
end
Your primary_key is almost always going to be the id column. You should remove your contractId column from the contracts db!
Task Orders
You'll need to do this:
#app/models/order.rb
belongs_to :contracts
has_many :task_orders
You'll then need another model at app/models/task_order.rb
Form
Your form is showing the error. This is because you're trying to create an ActiveRecord in the view itself. You'll be much better using the standard accepts_nested_attributes_for method of passing nested model data through a form:
#app/models/contract.rb
def new
#contract = Contract.new
#contract.task_orders.build
end
#app/views/contracts/new.html.erb
<%= form_for #contract do |f| %>
Firstly,use singular names for belongs_to
Class TaskOrder < ActiveRecord::Base
belongs_to :contract
end
Secondly,try changing your contract_Id in your task_orders table to contract_id.
Rails by default look for model_name_id(in your case contract_id) foreign key unless if any of the custom foreign keys defined in your model.
And finally,specify the data type integer for default foreign key.In your case it should be t.integer contract_id
However if you want contract_Id as foreign key,you should define it as custom foreign key in the Contract model itself like this
Class Contract < ActiveRecord:Base
has_many :task_orders,:foreign_key => "contract_Id"
end

Best practice to declare different user roles?

I want to declare different user roles on my site and I was wondering what is the best practice to do it in Rails? For now I have two options:
OPTION 1:
I create table Users and declare one string column where I can store names of user roles (SuperAdmin, Admin, Coach, Player)
create_table "users", force: true do |t|
t.string "username"
t.string "first_name"
t.string "last_name"
t.string "email"
t.string "role"
end
Inside User class I save the values like this:
class User < ActiveRecord::Base
ROLES = %w[SuperAdmin, Admin, Player, Coach]
end
OPTION 2:
I create a separate table only for roles. Inside Users table I have integer column for storage of role_id:
create_table "users", force: true do |t|
t.string "username"
t.string "first_name"
t.string "last_name"
t.string "email"
t.integer "role_id"
end
create_table "roles", force: true do |t|
t.string "role_name"
end
class User < ActiveRecord::Base
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :users
end
What would be a better option when if we take search speed, addition of new roles and future maintenance into the consideration?
Basic variant:
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
class CreateRolesUsersJoinTable < ActiveRecord::Migration
def change
create_table :roles_users, id: false do |t|
t.integer :user_id
t.integer :role_id
end
end
end
Here's why: you don't want has_many with roles, cause you won't be able to associate the same role with different users. It is a typical HABTM relationship. Yes, later it can become a performance problem, since it can be pretty hard to fetch all roles with associated records for every user. Then you will look into other variants for optimization: bitmaps, intensive caching or something else.
Hope you find it useful.
Create an other table to store the role is an over engineered solution :)
Use the gem cancan is a good approach, you need just to add a field to the user model.
With cancan you can even assign multiple roles to a user and store it into a single integer column using a bitmask.
IMO the best way is to use a standard has_many belongs_to relationship between a User and Role model:
#app/models/user.rb
Class User < ActiveRecord::Base
belongs_to :role
before_create :set_role
def set_role
role = self.role_id
role_id = "0" unless defined?(role)
end
end
#app/models/role.rb
Class Role < ActiveRecord::Base
has_many :users
end
users
id | role_id | name | created_at | updated_at
roles
id | name | created_at | updated_at
This will allow you to associate users to a specific role, and continue to add / adapt the roles as required

User models with different roles but discrete data?

I'm working on a Rails app where Users can have multiple roles, e.g. Manager, Employee, Admin, etc.
STI doesn't work in this situation, due to multiple roles, but I'd still like to keep role-related data in different tables if at all possible.
So for right now, my schema looks something like this:
create_table :roles do |t|
t.string :name
t.timestamps
end
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :email, :default => "", :null => false
t.timestamps
end
create_table :roles_users, :id => false do |t|
t.references :role, :user
end
And my User/Role models both have has_and_belongs_to_many relationships with each other.
So if, for example, I need the Manager to have_many Employees, is that possible with this setup? Is it possible for a User with the Manager role to have a Manager-specific attribute like secret_manager_information? Or do I need to re-think my approach?
Seeing as how Managers need to keep track of Employees (and in general other roles may need to keep track of other special data), I'd say that each role is different enough that they should get their own tables (assuming that you don't have too many roles).
For example, I would create a Manager and an Employee model:
class Manager
attr_accessible :user_id
has_many :employees
end
class Employee
attr_accessible :user_id, :manager_id
belongs_to :manager
end
Any user that is a Manager will have a record in the Manager table with user_id = user.id.
Any user that is an Employee will have a record in the Employee table with user_id = user.id and manager_id = (the id of the corresponding manager record)

Resources