How to keep attributes from two different tables synchronized in RoR? - ruby-on-rails

What I want is to be able to easily be able to find the team name associated with a membership without having to send another request to the database. My plan was to just add a team_name attribute to the Memberships table, but I can't think of a way to have this new attribute stay in sync with the name attribute in the Teams table. In other words, I want it to be the case that, if the owner of a team changes the team's name, then all memberships to that team will be updated (with the new team name) as well.
Here is what my setup looks like. I've fairly new to Rails.
/app/models/membership.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :team
end
/app/models/team.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :team
end
/app/db/schema.rb
ActiveRecord::Schema.define(version: 20161022002620) do
create_table "memberships", force: :cascade do |t|
t.integer "user_id"
t.integer "team_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "teams", force: :cascade do |t|
t.string "name"
t.integer "user_id"
end
end
If there is a better way to achieve what I am asking, then please let me know as well.

With this relational data your membership doesn't need a team name attribute - it is already available through the team association.
Generally there's no reason to keep data 'in sync' in this way unless you're performing some sort of computation. You don't need to store a name attribute on Membership - you can just use the existing one in Team.
I have seen people add duplicate database columns because they don't know how to traverse through associations. But unless you're using some noSql system, this isn't the 'right way' to do it - there is an underlying SQL API (through ActiveRecord) that performs lookups very efficiently.
in response to your comment. Do this:
class Membership < ActiveRecord::Base
def name
team.name
end
end

Related

Associating multiple existing records

I'm creating an application where one user becomes the account_manager of an account. What I want to do is to add other users to the account. A user can only have one account but an account can have many users.
class Account < ActiveRecord::Base
has_many :users
belongs_to :account_manager, :class_name => 'User', :foreign_key => 'account_manager_id'
end
class User < ActiveRecord::Base
has_one :account
What I'm totally stuck on is having a place where the account manager can either select the user from a dropdown, type in their name, or use some other type of selection. If I try to do this in console each new user I add replaces the last instead of adding to it. here is my schema for accounts:
create_table "accounts", force: :cascade do |t|
t.integer "user_id"
t.integer "account_manager_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
I've tried using collection_select but I think that is only for :though associations. I'm also thinking I probably need a join table but I don't know how to set it up. The thing that is tripping me up most is that I won't be creating new objects, I only want to add existing users to existing accounts. I'm just looking for someone who can talk through this with me.
In your User model:
class User < ActiveRecord::Base
belongs_to :account
end
While in your question, you are writing has_one. You can't write has_many in one model, and has_one in other model. There needs to be belongs_to in one model.
Edit:
The model that belongs_to, always saves the foreign keys in its table. So users would save account_id in it. In order to get all the users of an account, you would simply do:
Account.first.users # As an account `has_many` users

Relations not working as expected

This will be fairly quick and easy for most of you...I have a table called types, and another called projects. A project can only have one type, but a type can have many projects. For instance a community garden project and a playground project can both have the type of 'greenspace'. So I have set up a has_many association. In my types model I have this:
has_many :projects
and in my projects model I don't have anything (I previously had has_one in it but upon looking at the docs it seemed incorrect). In the projects#show view I would like the name of the type to display. The parks project's view should say 'greenspace'. but I am getting the error
undefined method `type' for #<Project:0x007ffdd14fcde8>
I am trying to access that name using:
<h3>Type: <%= #project.type.project_type %> </h3>
i have also tried:
<h3>Type: <%= #project.type_id.project_type %> </h3>
but of course type_id gives a number, and there is no project_type for a number. project_type being the name of the column which holds the string data 'greenspace'. Am I accessing it wrong? Or have I set it up incorrectly?
Also in my schema, projects looks like this:
create_table "projects", force: :cascade do |t|
t.string "type_id"
t.text "description"
t.integer "money_needed"
t.integer "money_raised"
t.float "interest_offered"
t.datetime "end_date"
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
t.text "url"
end
Project can belong_to both. Like this
#app/models/project.rb
class Project < ActiveRecord::Base
belongs_to :type
belongs_to :user
#...
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :projects
#...
end
#app/models/type.rb
class Type < ActiveRecord::Base
has_many :projects
#...
end
In the Project model you should state:
belongs_to => :type
In general, for most associations there is going to be an inverse. Not always, as you might have multiple associations in Type for Project. For example as well as your current has_many :projects, you might have others to return only projects that are unfinished, and such an association would not need an inverse.
Bear in mind that when you state: #project.type Rails is going to look for a method on #project. The association is what provides this method, and effectively the result is then the Type object that is referenced by the Project. It's important to realise that #project.type only returns a Type because the association tells it to -- the magic does not extent to just inferring that that is what is wanted.

Model style for Ruby on Rails application

So currently my application has a model called Vendors. I want each Vendor to have one or many owners, one or many members, and then people who track or follow the Vendor. I'm trying to decide the best layout for this and I've messed around with a few things. All of the Owners, members, and followers would also be coming from my User model. Therefore I'm thinking of making one association table with booleans as to whether the User is a follower, owner, or member.
For clarification - Owner and member would be people who work at the vendor
schema.db
create_table "vendor_relationships", force: true do |t|
t.integer "user_id"
t.integer "vendor_id"
t.boolean "owner"
t.boolean "member"
t.boolean "follower"
t.datetime "created_at"
t.datetime "updated_at"
end
but I'm wondering if this is the best way to go about this when it comes to speed and/or quality of code. Previously I had created separate tables for vendor_owners, vendor_members, and vendor_followers. Then I associated them with a has_many :through relationship, but I'm stuck onto which way is the best.
They would all have separate logic, or rather separate permissions as to what they were allowed to view and edit with respect to the vendor. I looking at cancan and pundit for role based authorization, but I didn't think it really applied for this situation.
A role based authorization could work if I build the schema like this
create_table "vendor_relationships", force: true do |t|
t.integer "user_id"
t.integer "vendor_id"
t.string "role"
t.datetime "created_at"
t.datetime "updated_at"
end
And then check for possible user roles against the string but I'm not sure which of these is the better option.
EDIT 1:
I'm going with the t.string "role" route, but I'm wondering how I can write the code into my model so that the associations with role = "owner" would be accessible by doing Vendor.owners... etc with Vendor.members and Vendor.followers.
This is what my code looks like currently.
has_many :owners, through: :vendor_relationships, class_name: "User", -> { where role: owner }
This for each variation - owners, members, followers. I've also tried using
has_many :owners, through: :vendor_relationships, class_name: "User", conditions: => ['vendor_relationship.role = "owner"']
but everything is giving me syntax errors here. Would appreciate some help thanks.
I think you are on the right track. I would do the following:
Model your owners, members, and followers all as User, but then give them each a role. Start with a single role per user to keep it simple. See: https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
In the many-to-many joins table, I would remove the booleans and instead let your association handle that logic.
has_many :owners, through: :vendor_members, class_name: "User", conditions: {"users.role = 'owner'"}
Then you can just interact with owners/members/followers like so: owners = vendor.owners
I would also consider changing the name of the vendor_members table since one of the types is also members. Maybe something like vendor_relationships

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

Database structure - associations in a multiuser system

I am in the process of building a multiclient system in ROR. (I am looking at http://guides.rubyonrails.org/association_basics.html#polymorphic-associations)
The structure is that a client has a contract, so when he logs in with his username, password and contract, he will have access to the system.
We have the contract id as a “master key”, which has to be in every table in the system.
class CreateContracts < ActiveRecord::Migration
def change
create_table :contracts do |t|
t.integer :contract_id
end
end
end
(chart of accounts)
class CreateCoas < ActiveRecord::Migration
def change
create_table :coas do |t|
t.integer :account_id
t.string :account_name
end
end
end
class CreateCustGroups < ActiveRecord::Migration
def change
create_table :custgroups do |t|
t.integer :account_id1
t.integer :account_id2
t.integer :account_id3
end
end
end
Q1: How do I define the contract with belongs_to? There has to be a relation in every table in the system to the contract table. Do I have to have a relation to all tables? (I think so)
class Contracts < ActiveRecord::Base
has_and_belongs_to_many :Coas
has_many:xxx
belongs:to
end
Q2: How do I define the association on the custgroup? Here we have a record where I have 3 or more fields that link to the same table (COA).
As Jesper said, it's quite hard to follow what you're trying to achieve, but I'll try to reply to your questions :
Q1 : If you want all your tables to reference a contract, you'll need to add to all those tables a foreign_key such as contract_id
so each create_table call will have the contract_id key
create_table :new_models do |t|
t.belongs_to :contract # this will create a contract_id field
end
you can also add an index on the column
add_index :new_models, :contract_id
then in all you models you'll add the belongs_to association :
class NewModel
...
belongs_to :contract
...
end
so if your Coas & CustGroups needs to reference the contract table, you'll have to change both migrations to include the contract_id key and then the models to add the belongs_to association
If a contract needs to have access to all Coas that references it, then you need to use the has_many association
class Contracts < ActiveRecord::Base
...
has_many :coas
...
end
It doesn't look like you need a has_and_belongs_to_many here, but i might be wrong about that.
if a contract also needs to access to CustGroups, you'll add :
has_many :cust_groups in the Contract model.
Q2 : I really didn't get understand what you want to do. Please explain what is the relation between Coas and Custgroups and I'll try to help you

Resources