I have two models Classification and ClassificationRelationships. I want to create a hierarchy of classifications using supperclass and subclass so that each classification can have many subclasses but only one superclass.
my migrations look like this
class CreateClassifications < ActiveRecord::Migration[5.0]
def change
create_table :classifications do |t|
t.string :symbol
t.string :title
t.integer :level
t.timestamps
end
add_index :classifications, :symbol
add_index :classifications, :level
end
end
class CreateClassificationRelationships < ActiveRecord::Migration[5.0]
def change
create_table :classification_relationships do |t|
t.integer :superclass_id
t.integer :subclass_id
t.timestamps
end
add_index :classification_relationships, :superclass_id
add_index :classification_relationships, :subclass_id
add_index :classification_relationships, [:superclass_id, :subclass_id], unique: true, name: 'unique_relationship'
end
end
so far with my models I have
class ClassificationRelationship < ApplicationRecord
belongs_to :superclass, :class_name => "Classification"
belongs_to :subclass, :class_name => "Classification"
end
class Classification < ApplicationRecord
has_many :classification_relationships
has_many :subclasses, through => :classification_relationships
has_one :superclass, through => :classification_relationships
end
I read quite a few other posts but am still unsure how to finish the associations. I am pretty sure I need to specify the foreign keys but am not clear on how I should do that. Thanks for the help!
Get rid of ClassificationRelationship.
All you need is for Classification to have a parent_id which, in the root instances, is allowed to be null.
Add:
belongs_to :parent, class_name: 'Classification', foreign_key: :parent_id
def children
Classification.where(:parent_id => self.id)
end
Some operations will not be optimal. e.g. Find all descendants. That's because this will require repeated queries to find children, their children, etc...
This may not be a concern for you.
If it is, I recommend storing a path as such:
after_create :set_path
def set_path
path = parent ? "#{parent.path}#{self.id}/" : "#{self.id}/"
self.update_attributes!(:path => path)
end
Then you can do things like:
def descendants
Classification.where("classifications.path LIKE '#{self.path}%' AND classifications.path <> '#{self.path}'")
end
Of course, make sure path is indexed if you'll be doing queries like that.
Related
I'm trying to set up a simple domain to practice getting better at modeling Ruby relationships. In this, an author can have many books, but a book can only have one author. Books have many characters, which can appear in multiple books. That works fine. It's the Author to Character relationship that I'm struggling with. When I try to access character.author I get nil. author.characters returns a list with duplicates--seven Harry Potters, one for each book. Ideally I'd want rowling.characters to return a list of just one Harry Potter (we're not fleeing Privet Drive) and obviously harry.author to return the instance of JK Rowling.
author.rb
class Author < ActiveRecord::Base
has_many :books
has_many :characters, through: :books
end
book.rb
class Book < ActiveRecord::Base
belongs_to :author
has_and_belongs_to_many :characters
end
character.rb
class Character < ActiveRecord::Base
has_and_belongs_to_many :books
belongs_to :author, through: :books
end
schema.rb
ActiveRecord::Schema.define(version: 2018_06_18_134335) do
create_table "authors", force: :cascade do |t|
t.string "name"
t.string "pen_name"
end
create_table "books", force: :cascade do |t|
t.string "title"
t.integer "author_id"
end
create_table "books_characters", force: :cascade do |t|
t.integer "book_id"
t.integer "character_id"
end
create_table "characters", force: :cascade do |t|
t.string "name"
t.string "author_id"
end
end
Here's what you probably want:
author.rb
class Author < ActiveRecord::Base
has_many :books
has_many :characters, -> {distinct}, through: :books
end
If on rails 4 you would use uniq instead of distinct.
book.rb
class Book < ActiveRecord::Base
belongs_to :author
has_many :book_characters
has_many :characters, through: :book_characters
end
book_character.rb
class BookCharacter < ActiveRecord::Base
belongs_to :book
belongs_to :character
end
character.rb
class Character < ActiveRecord::Base
has_many :book_characters
has_many :books, through: :book_characters
has_many :authors, through: :books
end
This actually accounts for all of the comments that you received and does so in a cleaner way. It allows for characters to have many authors, and avoids the messiness of the has_and_belongs_to_many relationships.
Furthermore, if you want, you can pretty much just pretend that the BookCharacter table doesn't exist. You can call Book.characters.create(attributes) and rails will create the intermediate object for you. However, the BookCharacter model could prove super useful in the future. You never know when you might want to differentiate between Harry Potter in book 6 and Harry Potter in book 7.
i'm learning Rails and i'm doing an exercise to practice associations and migration files.
Currently, trying to make a models between users, auction item, and bids.
So far for the migrate files I have the following:
class CreateItem < ActiveRecord::Migration
def change
create_table :auction do |t|
t.string :item_name
t.string :condition
t.date :start_date
t.date :end_date
t.text :description
t.timestamps
end
end
end
class CreateBids < ActiveRecord::Migration
def change
create_table :bids do |t|
t.integer :user_id
t.integer :auction_id
t.timestamps
end
end
end
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :email
t.string :username
t.string :password_digest
t.timestamps
end
end
end
These are the following models:
class Bid < ActiveRecord::Base
belongs_to :bidder, class_name: "User", foreign_key: "bidder_id"
belongs_to :auction
end
class User < ActiveRecord::Base
has_many :bids
has_many :auctions, :foreign_key => 'bidder_id'
has_secure_password
end
class Auction < ActiveRecord::Base
belongs_to :seller, class_name: "User", foreign_key: :user_id
has_many :bids
has_many :bidders, through: :bids
end
Any suggestions or opinions? I'm currently trying to test the tables but auctions doesn't seem to be working...
Specifically, my auction table can't seem to find a user_id and therefore a user doesn't have any auctions.
foreign_key refers to the _id (by default) or any unique attribute used to associate the models.
I can't see bidder model, you need to replace them with user_id as they are associated to user model.
Refer for more details belongs_to
class CreateBids < ActiveRecord::Migration
def change
create_table :bids do |t|
t.integer :user_id **do not think this is correct**
t.integer :auction_id **or this one**
t.timestamps
end
end
end
You want to use something more along the lines of the following
class CreateGames < ActiveRecord::Migration[5.0]
def change
create_table :games do |t|
t.integer :total_time
t.references :version, foreign_key: true **#this is how a foreign key should be declared**
t.integer :total_points
t.timestamps
end
end
end
Alternatively, if you want to change things in future migrations you can always add a reference:
def change
add_reference :levels, :version, foreign_key: true
end
I'm new in Ruby on Rails. I don't understand how rails behave using foreign Key, I've researched it for some days but I didn't get the answer.
Simple sample:
I created two tables:
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.text :content
t.timestamps null: false
end
end
end
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :author
t.text :content
t.references :post, index: true, foreign_key: true
t.timestamps null: false
end
end
end
My models are:
class Post < ActiveRecord::Base
has_many :comments
end
class Comments < ActiveRecord::Base
belongs_to :post
end
My doubt is: As I have a Foreign Key in my table COMMENTS (.references :post, index: true, foreign_key: true) I guess that I wouldn't be able to destroy any post which has any COMMENTS associated to them, isn't it ?
I did as above but I am still able to destroy the posts, even when I have the comments associated. How can I treat it? What am I doing wrong?
Cheers
I'd refine your migrations to use the :on_delete options on your foreign keys. It can take one of those values : :nullify, :cascade, :restrict
From what I understand, you need to set this value to :restrict on your post_id column in your comments table, so that posts with associated comments can't be deleted.
Update:
Or, you could also directly set it on the association in your Post model:
has_many :comment, dependent: :restrict_With_error
Please take a look at:
http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many -> See the Options: Section
From what i understand, you dont want to destroy a post if there are associated comments?
Why not put a if statement encapsulating the delete button for a post
So something like:
psudo code
if #post.comments exists
cant delete post
else
delete post
end
I'm building a rails app where I have models users, images, and image_pairs. I want each image_pair to have 2 images, named :before_image and :after_image.
So:
users have many images (many to one)
users have many image_pairs (many to one)
images may have only one image_pair
image_pairs always have 2 images
I have everything working, except I can't call #image.image_pair. I get:
PG::UndefinedColumn: ERROR: column image_pairs.image_id does not exist
How can I set this up so I can get an image_pair from its image?
schema.rb (some irrelevant fields removed)
create_table "image_pairs", force: true do |t|
t.integer "before_image_id"
t.integer "after_image_id"
end
add_index "image_pairs", ["before_image_id"], name: "index_image_pairs_on_before_image_id", using: :btree
add_index "image_pairs", ["after_image_id"], name: "index_image_pairs_on_after_image_id", using: :btree
add_index "image_pairs", ["user_id"], name: "index_image_pairs_on_user_id", using: :btree
create_table "images", force: true do |t|
t.integer "user_id"
t.integer "image_pair_id"
end
add_index "images", ["image_pair_id"], name: "index_images_on_image_pair_id", using: :btree
add_index "images", ["user_id"], name: "index_images_on_user_id", using: :btree
create_table "users", force: true do |t|
t.string "name"
end
user.rb
class User < ActiveRecord::Base
has_many :images, dependent: :destroy
has_many :image_pairs, dependent: :destroy
end
image.rb
class Image < ActiveRecord::Base
belongs_to :user
has_one :image_pair
end
image_pair.rb
class ImagePair < ActiveRecord::Base
belongs_to :user
belongs_to :before_image, :class_name => "image"
belongs_to :after_image, :class_name => "image"
end
You might add a field to image_pairs called image_id that points to the original image id. Or rename the before_image to image_id and add an alias or a method if you want to call it by the name before_image.
def before_image
self.image_id
end
I am working on a small Android project with RoR server.
Here are the three models:
class User < ActiveRecord::Base
has_many :relations
has_many :friends, :through => :relations
attr_accessor :friend_ids
end
class Relation < ActiveRecord::Base
belongs_to :user
belongs_to :friend
end
class Friend < ActiveRecord::Base
has_many :relations
has_many :users, :through => :relations
end
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :user_name
t.string :password
t.integer :city_id
t.integer :travelstyle_id
t.boolean :online
t.string :self_description
t.string :sex
t.integer :head_id
t.timestamps
end
end
def self.down
drop_table :users
end
end
class CreateFriends < ActiveRecord::Migration
def self.up
create_table :friends do |t|
t.string :user_name
t.integer :city_id
t.integer :travelstyle_id
t.string :self_description
t.string :sex
t.integer :head_id
t.timestamps
end
end
class CreateRelations < ActiveRecord::Migration
def self.up
create_table :relations do |t|
t.integer :user_id
t.integer :friend_id
t.timestamps
end
end
Model User uses model Relation to connect with model Friend. I use scaffold to creat the three models and add the relationship code in their model files. I also create a API controller to send xml file to Android application. Here is the controller code:
def find_friend
#user=User.where("id=?",params[:id])
#friend=#user.friends
respond_to do |format|
format.html
format.xml
end
end
Here is the problem, when I use the api(type in http://localhost:3000/api/find_friend/1.xml), the server throws a mistake:
NoMethodError in ApiController#find_friend
undefined method `friends' for #<ActiveRecord::Relation:0x3478a28>
app/controllers/api_controller.rb:21:in `find_friend'
I am new to Rails and have no idea where the wrong is. Do I have to add something in the "route.rb" or change the migration file?
In the rails console mode, I type in "user=User.find(1), friend=user.friends" and get the correct result.
~~~~(>_<)~~~~
The problem is the controller method "#user=User.where("id=?",params[:id])". The "where" method can not tell whether the result is an array or actually one object. If I use "#user=User.find(params[:id])", rails will be "smart enough" to know that "Oh, yes, this is just one object and it has a method called Friends because someone connects the two models together".
Learning Rails likes a marriage, you think you know well about her but sometimes you think "God actually I know nothing about the mysterious guy."