find by reference - ruby-on-rails

so i have 2 models:
create_table "holders", :force => true do |t|
t.string "faceid"
t.integer "badges_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "holders", ["badges_id"], :name => "index_holders_on_badges_id"
create_table "badges", :force => true do |t|
t.string "name"
t.text "description"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
i need 2 things:
to get all the badges of a certain faceid holder
to get all the holders of a certain badge.
i know its really noobs question but until now i didnt work with references so i dont really understood from the literature how to make the connection.

You actually need a many to many association on your holder and badge models. So you have to options either use has many :through or use has_and_belongs_to_many. The difference between the two can be found here. I am taking the example for has_many :through.
You need to create three models.
class Holder < ActiveRecord:Base
has_many :badges_holders
has_many :badges, :through => :badges_holders
end
class Badge < ActiveRecord:Base
has_many :badges_holders
has_many :holders, :through => :badges_holders
end
class BadgesHolder < ActiveRecord:Base
belongs :badge
belongs :holder
end
And your migration files needs to be:
create_table "holders", :force => true do |t|
t.string "faceid"
t.timestamps
end
add_index "holders", ["badges_id"], :name => "index_holders_on_badges_id"
create_table "badges", :force => true do |t|
t.string "name"
t.text "description"
t.timestamps
end
create_table "badges_holders", :force => true do |t|
t.integer "holder_id"
t.integer "badge_id"
t.timestamps
end
Now you can easily use Holder.find_by_faceid('xyz').badges to find the all hedges held by the holder whose faced is xyz. And Badge.first.holders to get all the holders for the first bedge.
For your question HABTM will be a good option as you do not need any extra field in the join table, so you can just use has_and_belongs_to_many in both of your models and you don't need BadgesHolder model in that case. And for the migration of the join table, replace first line with create_table "badges_holders", :id => false, :force => true do |t| a and remove t.timestamps as the join table for HABTM should not have any other column than the foreign keys.

If it's some Ruby on Rails, you must have 2 models :
class Holder < ActiveRecord:Base
has_many :badges
end
class Badge < ActiveRecord:Base
belongs_to :holder
end
Your entry called badges_id should not be in your holders table ; you should have a holder_id on your "badges" table.
Then, you can simply call
Holder.find_by_faceid('foobar').badges
and
Badge.find(1337).holder
If your badge can belongs to many holders, then you have to write a has_and_belongs_to_many relation.

Related

Do I need to create an assocation table for a :has_many :through association?

I'm trying to use a :has_many :through type association, but I'm getting the following error:
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: work_units.developer_id:
Many other posts about this sort of thing have just had spelling mistakes, but I've checked mine.
class Developer < ActiveRecord::Base
attr_accessible :skype_name, :language_ids, :user_attributes
has_many :work_units
has_many :projects, :through => :work_units
...
end
class Project < ActiveRecord::Base
attr_accessible :complete, :description, :finalised, :price
has_many :work_units
has_many :developers, :through => :work_units
...
end
class WorkUnit < ActiveRecord::Base
attr_accessible :hours_worked
belongs_to :project
belongs_to :developer
end
I've run db:migrate and it didn't complain. I did make a mistake and had to rollback the db then re-migrate, but I think I did it right. I use the annotate gem and it doesn't show any of the relationship ids I'd expect. So, do I need to create a WorkUnits table or am I missing something? The rails guide didn't mention manually making tables.
Edit
Here's the migration I used to create the WorkUnit model and stuff:
class CreateWorkUnits < ActiveRecord::Migration
def change
create_table :work_units do |t|
t.integer :hours_worked, :default => 0
t.timestamps
end
end
end
Edit 2
Snippets from my schema.rb:
create_table "work_units", :force => true do |t|
t.integer "hours_worked", :default => 0
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "projects", :force => true do |t|
t.string "description"
t.decimal "price", :precision => 8, :scale => 2
t.boolean "complete", :default => false
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
Similarly for :developers. So, why doesn't my migration add the association information for me?
Your WorkUnit migration should look like this:
class CreateWorkUnits < ActiveRecord::Migration
def change
create_table :work_units do |t|
t.integer :hours_worked, :default => 0
t.references :developer
t.references :project
t.timestamps
end
add_index :work_units, :developer_id
add_index :work_units, :project_id
end
end
You need to add the foreign keys to your work_units table.
class CreateWorkUnits < ActiveRecord::Migration
def change
create_table :work_units do |t|
t.integer :hours_worked, :default => 0
t.integer :project_id, null: false
t.integer :developer_id, null: false
t.timestamps
end
add_index :work_units, :project_id
add_index :work_units, :developer_id
end
end
Another way:
class CreateWorkUnits < ActiveRecord::Migration
def change
create_table :work_units do |t|
t.integer :hours_worked, :default => 0
t.belongs_to :project
t.belongs_to :developer
t.timestamps
end
add_index :work_units, :project_id
add_index :work_units, :developer_id
end
end
You can also define these fields when generating your model, then they'll be added to the migration automatically as show in the second snippet.
$ rails g model WorkUnit hours_worked:integer project:belongs_to developer:belongs_to
Hope that helps.
A table for WorkUnit needs to exist, whether that means it migration was automatically generated via scaffolding or if the migration was manually written by you.
If you don't have a migration yet that creates that table, you'll need to create that migration because the table does need to exist.
You do need a work_units table with a project_id and developer_id column.
Have a look at http://xyzpub.com/en/ruby-on-rails/3.2/activerecord_datenbank_anlegen.html if you don't know how to create a table.

t.references in Rails migration (n | n relationship)

I have three models in my Rails 3 application, DailyData and DailyDataVehicle and Vehicle, a many to many relationship.
I just learned that if you update the model with the associations, that it doesn't update the database, so I am going back and adding those migrations. I am also lucky enough to confidently think I know the differences between belongs_to and has_many, however, in my migration file, I am not sure that t.references does.
So I named the migration model AddDailyDataToDailyDataVehicle, and want to add the dailyData_id to the daily_data_vehicles table. This is a many to many relationship, so I want the id key to be in the relationship table DailyDataVehicles, but I'm not quite sure that t.references will know that.
Maybe I am slightly mixing up the class associations and the database relationships, and if I am, then please clarify this.
If t.references is not what I want, do I have to declare the relationship manually with has_many, and if so, what is the syntax for that?
schema file currently:
create_table "daily_data_vehicles", :force => true do |t|
t.integer "vehicle_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "daily_data_vehicles", ["vehicle_id"], :name => "index_daily_data_vehicles_on_vehicle_id"
Migration (attempt):
class AddDailyDataToDailyDataVehicle < ActiveRecord::Migration
def change
change_table :dailyDataVehicles do |t|
t.references :dailyData
end
add_index :dailyDataVehicles, :dailyData_id
end
end
What I think the schema file should look like if the migration works correctly:
create_table "daily_data_vehicles", :force => true do |t|
t.integer "vehicle_id"
t.integer "dailyData_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "daily_data_vehicles", ["vehicle_id"], :name => "index_daily_data_vehicles_on_vehicle_id"
add_index "daily_data_vehicles", ["dailyData_id"], :name => "index_daily_data_vehicles_on_daily_data_id"
with the difference being t.integer "dailyData_id" and add_index "daily_data_vehicles", ["dailyData_id"], :name => "index_daily_data_vehicles_on_daily_data_id"
class AddDailyDataToDailyDataVehicle < ActiveRecord::Migration
def change
add_column :daily_data_vehicles, : daily_data_id, :integer
add_index :daily_data_vehicles, :daily_data_id
end
end
class DailyDataVehicle < ActiveRecord::Base
belongs_to :daily_data
end
class DailyData < ActiveRecord::Base
has_many :daily_data_vehicles
end

unknown attribute: user_id

I'm getting the error unknown attribute: user_id durring execution of current_user.stories.build
class User < ActiveRecord::Base
has_many :stories, class_name: 'Story', foreign_key: 'user_id', dependent: :destroy
...
class Story < ActiveRecord::Base
belongs_to :user, class_name: 'User', foreign_key: 'user_id'
...
schema.rb
create_table "stories", :force => true do |t|
t.string "responsible"
t.string "descr"
t.string "state"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "users", :force => true do |t|
t.string "email"
t.string "password_digest"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "name"
end
It doesn't contain 'user_id' field. Any ideas?
Kulbir is correct that you need to define a user_id column in your stories table, but doesn't explain the way to do that.
The correct way to make that change is to create a new migration. By convention, it should be called add_user_id_to_stories and would be created as follows (assuming you're using Rails 3+):
rails generate migration add_user_id_to_stories
If you run that, it should actually generate a migration that already contains the change you need to make, which should be something like:
add_column :stories, :user_id, :integer
As an aside when you're following the Rails conventions concerning association naming, which you are, you can actually skip a lot of the extra specification. In the User model, you can specify just has_many :stories and in the Story model specify belongs_to :user. Rails will assume the same class names and foreign keys you've specified.
You should have a user_id field in your stories table like below to define the association in your models.
create_table "stories", :force => true do |t|
t.integer "user_id"
t.string "responsible"
t.string "descr"
t.string "state"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
...
end
Edit
Check Emily's answer for detailed explanation.
you should use the new syntax and pass the fieldtype as symbol
add_column :stories, :user_id, :integer

Associated model isn't being created

I'm a complete rails newbie, so forgive me if this is trivial.
I have an Inventory model that either belongs_to a Store or a Traveling Party:
class Inventory < ActiveRecord::Base
belongs_to :trader, :polymorphic => true
end
class Store < ActiveRecord::Base
has_one :inventory, :as => :trader, :dependent => :destroy
end
class TravelingParty < ActiveRecord::Base
has_many :travelers, :dependent => :destroy
has_one :inventory, :as => :trader, :dependent => :destroy
validates_presence_of :speed, :ration, :position
accepts_nested_attributes_for :travelers, :reject_if => :reject_traveler, :allow_destroy => true
accepts_nested_attributes_for :inventory, :allow_destroy => true
def reject_traveler(attributes)
attributes['profession'].blank? and attributes['name'].blank?
end
end
I created a form that, when submitted, creates a Traveling Party and a number of Travelers. Now I'd like the form to also create an Inventory and initialize all the variables to 0. I know the following doesn't address variable initialization, but it doesn't even seem to put a row of null values into the Inventory database table.
class TravelingPartiesController < ApplicationController
def new
#traveling_party = TravelingParty.new
5.times do
traveler = #traveling_party.travelers.build
end
#inventory = #traveling_party.inventory.create
end
def create
#traveling_party = TravelingParty.new(params[:traveling_party])
if #traveling_party.save
flash[:notice] = "Successfully created traveling party and travelers."
redirect_to '/store/'
else
flash[:error] = "Please specify a leader."
redirect_to '/new/'
end
end
def index
end
end
For good measure, here is what the database schema looks like:
ActiveRecord::Schema.define(:version => 20111018224808) do
create_table "inventories", :force => true do |t|
t.integer "ox"
t.integer "food"
t.integer "clothing"
t.integer "ammunition"
t.integer "money"
t.integer "axle"
t.integer "wheel"
t.integer "tongue"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "trader_id"
end
create_table "stores", :force => true do |t|
t.string "name"
t.integer "location"
t.integer "priceScale"
t.datetime "created_at"
t.datetime "updated_at"
end
# Could not dump table "travelers" because of following StandardError
# Unknown type 'relations' for column 'traveling_party_id'
create_table "traveling_parties", :force => true do |t|
t.integer "speed"
t.integer "ration"
t.integer "position"
t.datetime "created_at"
t.datetime "updated_at"
end
end
Is there a reason the inventory database table isn't being affected at all? And once that works, what would be the best way to initialize a traveling_party.inventory to have all 0s? (i.e., values for ox, food, clothing, etc).
This may because your inventories table does not include a 'trader_type'. This is required for polymorphic associations.
create_table "inventories", :force => true do |t|
t.integer "trader_id"
t.string "trader_type"
end
Edit:
To set all the values initially to 0, the best way would be to put a default value onto the fields in the table. (If you want it to always be initialized to 0 if there is no other option, otherwise they will default to nil)
I believe you can create a migration with
change_table(:inventories) do |t|
t.change :ox, :integer, :default => 0
end

Using build with has_many :through

I have an Entry model and a Category model, where an Entry can have many Categories (through EntryCategories):
class Entry < ActiveRecord::Base
belongs_to :journal
has_many :entry_categories
has_many :categories, :through => :entry_categories
end
class Category < ActiveRecord::Base
has_many :entry_categories, :dependent => :destroy
has_many :entries, :through => :entry_categories
end
class EntryCategory < ActiveRecord::Base
belongs_to :category
belongs_to :entry
end
When creating a new Entry, I create it by calling #journal.entries.build(entry_params), where entry_params is the parameters from the entry form. If any categories are selected, however, I get this error:
ActiveRecord::HasManyThroughCantDissociateNewRecords in Admin/entriesController#create
Cannot dissociate new records through 'Entry#entry_categories' on '#'. Both records must have an id in order to delete the has_many :through record associating them.
Note that the '#' on the second line is verbatim; it doesn't output an object.
I have tried naming my categories selectbox on the form to categories and category_ids but neither make a difference; if either is in the entry_params, the save will fail. If no categories are selected, or I remove categories from entry_params (#entry_attrs.delete(:category_ids)), the save works properly, but the categories don't save, obviously.
It seems to me that the problem is that an EntryCategory record is attempting to be made before the Entry record is saved? Shouldn't build be taking care of that?
Update:
Here's the relevant parts of schema.rb, as requested:
ActiveRecord::Schema.define(:version => 20090516204736) do
create_table "categories", :force => true do |t|
t.integer "journal_id", :null => false
t.string "name", :limit => 200, :null => false
t.integer "parent_id"
t.integer "lft"
t.integer "rgt"
end
add_index "categories", ["journal_id", "parent_id", "name"], :name => "index_categories_on_journal_id_and_parent_id_and_name", :unique => true
create_table "entries", :force => true do |t|
t.integer "journal_id", :null => false
t.string "title", :null => false
t.string "permaname", :limit => 60, :null => false
t.text "raw_body", :limit => 2147483647
t.datetime "created_at", :null => false
t.datetime "posted_at"
t.datetime "updated_at", :null => false
end
create_table "entry_categories", :force => true do |t|
t.integer "entry_id", :null => false
t.integer "category_id", :null => false
end
add_index "entry_categories", ["entry_id", "category_id"], :name => "index_entry_categories_on_entry_id_and_category_id", :unique => true
end
Also, saving an entry with categories works fine in the update action (by calling #entry.attributes = entry_params), so it does seem to me that the problem is only based on the Entry not existing at the point that the EntryCategory records are attempted to be created.
I tracked down the cause of this error to be within the nested_has_many_through plugin. It seems that the version I had installed was buggy; after updating to the most recent version, my build works again.
Why do you call
self.journal.build(entry_params)
instead of
Entry.new(entry_params)
If you need to create a new entry associated to a specific Journal, given a #journal, you can do
#yournal.entries.build(entry_params)

Resources