I am beginning Ruby On Rails through a purchase/resale platform project at school. I'm having an issue with my models when I try to translate them from my relational model.
Firstly, I've modelled my database. Here is simplified the entity-relationship model :
I've then translated it in a relational model :
Finally, I've implemented it in Ruby On Rails.
I've implemented a model Client :
class Client < ApplicationRecord
attr_accessor :name
validates :name, :presence => true
has_many :purchasings, :dependent => :destroy
has_many :sellers, :through => :purchasings
has_many :articles, :through => :purchasings
end
I've implemented a model Seller :
class Seller < ApplicationRecord
attr_accessor :name
validates :name, :presence => true
has_many :purchasings, :dependent => :destroy
has_many :sellers, :through => :purchasings
has_many :articles, :through => :purchasings
end
I've implemented a model Article
class Article < ApplicationRecord
attr_accessor :quantity
validates :quantity, :presence => true
has_one :purchasing, :dependent => :destroy
has_one :client, :through => :purchasings
has_one :seller, :through => :purchasings
end
I've implemented a model Purchasing :
class Purchasing < ApplicationRecord
attr_accessor :client_id, :seller_id, :article_id
belongs_to :client, :class_name => "Client"
belongs_to :seller, :class_name => "Seller"
belongs_to :article, :class_name => "Article"
validates :client_id, :presence => true
validates :seller_id, :presence => true
validates :article_id, :presence => true
end
I've modified the Purchasing database migration :
class CreatePurchasing < ActiveRecord::Migration[5.1]
def change
[...]
add_index :purchasings, :client_id
add_index :purchasings, :seller_id
add_index :purchasings, :article_id
add_index :purchasings, [:client_id, :seller_id], :unique => true
end
def down
[...]
end
end
I know this is incorrect because when I execute the following code on the Rails console :
cl1 = Client.create(:name => "John")
cl2 = Client.create(:name => "James")
sel1 = Seller.create(:nom => "Jack")
sel2 = Seller.create(:nom => "Jil")
a1 = Article.create(:quantity => 5)
p1 = Purchasing.new(:client => cl1, :client_id => cl1.id, :seller => sel1, :seller_id => sel1.id, :article => a1, :article_id => a1.id)
p1.save
p2 = Purchasing.new(:client => cl2, :client_id => cl2.id, :seller => sel1, :seller_id => sel1.id, :article => a1, :article_id => a1.id)
p2.save
p2.save returns true whereas an article can't be sold by a same seller and bought by two clients different.
You are adding the index on incorrect columns on purchasings table. As per the requirement, the article_id and seller_id should not get repeated ideally. So you actually need is a uniqueness constraint on seller_id, and article_id column. You can do so by creating an unique index on the composition of two columns seller_id, and article id on the database layer. You should also add the application layer validation on the purchasing model.
class Purchasing < ApplicationRecord
attr_accessor :client_id, :seller_id, :article_id
belongs_to :client, :class_name => "Client"
belongs_to :seller, :class_name => "Seller"
belongs_to :article, :class_name => "Article"
validates :client_id, :presence => true
validates :seller_id, :presence => true
validates :article_id, :presence => true
validates :article_id, uniqueness: {scope: :seller_id}
end
Now you should also write a database migration to add the unique index on these two columns.
class AddUniquenessConstraintInPurshasing < ActiveRecord::Migration
def change
add_index :purchasings, [:article_id, :seller_id], :unique => true
end
end
Related
This is my favorite designer model
favorite_designer.rb
class FavoriteDesigner < ActiveRecord::Base
belongs_to :user, :counter_cache => true
belongs_to :designer, :class_name => "User", :foreign_key => :designer_id
validates_presence_of :user_id
validates_presence_of :designer_id
validates_numericality_of :user_id, :unless => Proc.new{|f| f.user_id.blank?}
validates_numericality_of :designer_id, :unless => Proc.new{|f| f.designer_id.blank?}
end
How can I test :counter_cache => true and :foreign_key => :designer_id in rspec with shoulda?
You can use counter_cache and with_foreign_key shoulda matchers :
it { should belong_to(:organization).counter_cache(true) }
it { should have_many(:worries).with_foreign_key('worrier_id') }
For full doc and examples look at :
Counter cache doc
Foreign key doc
class User < ActiveRecord::Base
attr_accessible :email, :password, :picture, :username
has_many :entries, :class_name => "Entry", :inverse_of => :user
has_many :sent_messages, :inverse_of => :sender, :class_name => "Message", :foreign_key => "sender_id"
has_many :recieved_messages, :inverse_of => :target, :class_name => "Message", :foreign_key => "target_id"
has_many :mails, :inverse_of => :user
...
end
class Message < ActiveRecord::Base
attr_accessible :message, :seen, :title
belongs_to :sender, class_name => "User", :inverse_of => :sent_messages
belongs_to :target, class_name => "User", :inverse_of => :recieved_messages
end
class CreateMessages < ActiveRecord::Migration
def change
create_table :messages do |t|
t.references :sender
t.references :target
t.string :title
t.string :message
t.boolean :seen
t.timestamps
end
end
end
I have these two models. When I try to get user's sent messages (user.sent_messages) I get an error like this:
Could not find the inverse association for sent_messages (:sender in Message)
It's my second day using Rails so it may be trivial, but I don't see it.
PS: If I delete the :inverse_of => :sender from class User, the user.sent_messages works but as I understand :inverse_of is important.
PPS: Found the error! In the Message class I was using class_name instead of :class_name.
That's why i prefer to use the new style hash wherever possible
class Message < ActiveRecord::Base
attr_accessible :message, :seen, :title
belongs_to :sender, class_name: 'User', inverse_of: :sent_messages
belongs_to :target, class_name: 'User', inverse_of: :recieved_messages
end
Though as with many things, it's often a question of personal taste :)
I have products and brands
products model:
class Product < ActiveRecord::Base
attr_accessible :brand_id, :title
belongs_to :brand
validates :title, :presence => true
validates :brand, :presence => {:message => 'The brand no exists'}
end
and the brands model
class Brand < ActiveRecord::Base
attr_accessible :name
validates :name, :presence => true
has_many :products, :dependent => :destroy
end
I want to validate if exist a product with a name in this brand.
I mean I could have 2 products with the same name in different brands but not in the same brand.
You could use the uniqueness validation with a scope:
validates :name, :uniqueness => { :scope => :brand_id }
Note that you have to specify :brand_id instead of :brand, because the validation can't be made on the relation.
If you don't know it, I suggest you to read the Active Record Validations and Callbacks guide.
NB: the syntax {:foo => 'bar'} is replaced (since Ruby 1.9.2) with {foo: 'bar'}.
Model Code (Attempting to require uniqueness for embed_code)
https://gist.github.com/1427851:
class Link < ActiveRecord::Base
validates :embed_code,
:format => {:with => /^<object type|^<embed src|^<object width|^<iframe src|^<object height|^<iframe width|^<embed id|^<embed width|^<object data|^<div|^<object id/i, :message => "Invalid Input"},
:uniqueness => true
attr_accessible :title, :embed_code, :score
after_initialize :calculate_score, :favs_count
attr_accessor :score, :fav_count
validates :title, :length => { :in => 4..45, :message => "Must be between 4 & 45 characters"}
before_save :resize
has_many :favorites
has_many :favorited, :through => :favorites, :source => :user
belongs_to :user
I've tried validates_uniqueness_of :embed_code and disabling (commenting out) non-critical components of the model such as :before_save :resize
I have users and issues joined by a votership model. Users can vote on issues. They can either vote up or down (which is recorded in the votership model). First, I want to be able to prevent users from casting multiple votes in one direction. Second, I want to allow users to cast the opposite vote. So, if they voted up, they should still be able to vote down which will replace the up vote. Users should never be able to vote on an issue twice. Here are my files:
class Issue < ActiveRecord::Base
has_many :associations, :dependent => :destroy
has_many :users, :through => :associations
has_many :voterships, :dependent => :destroy
has_many :users, :through => :voterships
belongs_to :app
STATUS = ['Open', 'Closed']
validates :subject, :presence => true,
:length => { :maximum => 50 }
validates :description, :presence => true,
:length => { :maximum => 200 }
validates :type, :presence => true
validates :status, :presence => true
def cast_vote_up!(user_id, direction)
voterships.create!(:issue_id => self.id, :user_id => user_id,
:direction => direction)
end
end
class Votership < ActiveRecord::Base
belongs_to :user
belongs_to :issue
end
class VotershipsController < ApplicationController
def create
session[:return_to] = request.referrer
#issue = Issue.find(params[:votership][:issue_id])
#issue.cast_vote_up!(current_user.id, "up")
redirect_to session[:return_to]
end
end
class User < ActiveRecord::Base
authenticates_with_sorcery!
attr_accessible :email, :password, :password_confirmation
validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email
validates_uniqueness_of :email
has_many :associations, :dependent => :destroy
has_many :issues, :through => :associations
has_many :voterships, :dependent => :destroy
has_many :issues, :through => :voterships
end
You would put the uniqueness constraint on the Votership model. You don't need to put validations on the association itself.
class Votership < ActiveRecord::Base
belongs_to :user
belongs_to :issue
validates :issue_id, :uniqueness => {:scope=>:user_id}
end
This means a user can only have a single vote on a given issue (up or down).
Relationship models:
class Person
has_many :accounts
has_many :computers, through: :accounts
end
class Account
belongs_to :person
belongs_to :computer
scope :administrators, -> { where(role: 'administrator') }
end
class Computer
has_many :accounts
has_many :people, through: :accounts
end
This is how it is called
person.accounts.administrators.map(&:computer)
We can do this better using ActiveRecord::SpawnMethods#merge!
person.computers.merge(Account.administrators)
Ref: https://coderwall.com/p/9xk6ra/rails-filter-using-join-model-on-has_many-through