I am trying to set up an attribute of a model as soon as it is initialised. I have tried before_initialise, after_initialise, before_create, after_create.
What am I doing wrong?
describe "turn based system" do
before(:each) do
#user1 = Factory :user
#user2 = Factory :user
#rota = #user1.rotas.create
#rota.users << #user2
end
it "should be user1's turn at the beginning" do
#rota.current_turn.should == #user1.id --> fails
end
Implementation:
class Rota < ActiveRecord::Base
has_many :rotazations
has_many :users, :through => :rotazations
has_many :invitations
after_create :set_current_turn
private
def set_current_turn
if self.users.count > 0
self.current_turn = self.users[0].id
end
end
end
EDIT
If I construct the rota model using:
#rota = #user1.rotas.create(:current_turn => #user1.id)
it works - however, I feel like im duplicating code - I always want the current_turn field to be the object creator's id.
Thanks!
You have a typo: change before_filter to after_create
1) before_filter is the controller callback function.
2) You want to use after_create so the user has already been associated and you can use the association.
Related
In my Rails app I have Clients and Users. And Users can have many Clients.
The models are setup as so:
class Client < ApplicationRecord
has_many :client_users, dependent: :destroy
has_many :users, through: :client_users
end
class User < ApplicationRecord
has_many :client_users, dependent: :destroy
has_many :clients, through: :client_users
end
class ClientUser < ApplicationRecord
belongs_to :user
belongs_to :client
end
So if I wanted to create a new client that had the first two users associated with it how would I do it?
e.g.
Client.create!(name: 'Client1', client_users: [User.first, User.second])
Trying that gives me the error:
ActiveRecord::AssociationTypeMismatch: ClientUser(#70142396623360) expected, got #<User id: 1,...
I also want to do this for an RSpec test. e.g.
user1 = create(:user)
user2 = create(:user)
client1 = create(:client, client_users: [user1, user2])
How do I create a client with associated users for in both the Rails console and in an RSpec test?
If you do not want to accept_nested_attributes for anything, as documented here you can also pass block to create.
Client.create!(name: 'Client1') do |client1|
client1.users << [User.find(1), User.find(2), User.find(3)]
end
Try this. It should work
Client.create!(name: 'Client1').client_users.new([{user_id:
User.first},{user_id: User.second}])
You can do this with the following code:
user1 = create(:user)
user2 = create(:user)
client1 = create(:client, users: [user1, user2])
See ClassMethods/has_many for the documentation
collection=objects
Replaces the collections content by deleting and adding objects as
appropriate. If the :through option is true callbacks in the join
models are triggered except destroy callbacks, since deletion is
direct.
If you are using factory_girl you can add trait :with_users like this:
FactoryGirl.define do
factory :client do
trait :with_two_users do
after(:create) do |client|
client.users = create_list :user, 2
end
end
end
end
Now you can create a client with users in test like this:
client = create :client, :with_two_users
accepts_nested_attributes_for :users
and do as so:
Client.create!(name: 'Client1', users_attributes: { ........ })
hope this would work for you.
You can make use of after_create callback
class Client < ApplicationRecord
has_many :client_users, dependent: :destroy
has_many :users, through: :client_users
after_create :add_users
private def add_users
sef.users << [User.first, User.second]
end
end
Alternatively, A simpler approach would be
Client.create!(name: 'Client1', user_ids: [User.first.id, User.second.id])
The reason you're getting a mismatch is because you're specifying the client_users association that expects ClientUser instances, but you're passing in User instances:
# this won't work
Client.create!(client_users: [User.first, User.second])
Instead, since you already specified a users association, you can do this:
Client.create!(users: [User.first, User.second])
There's a simpler way to handle this, though: ditch the join model and use a has_and_belongs_to_many relationship. You still need a clients_users join table in the database, but you don't need a ClientUser model. Rails will handle this automatically under the covers.
class Client < ApplicationRecord
has_and_belongs_to_many :users
end
class User
has_and_belongs_to_many :clients
end
# Any of these work:
client = Client.new(name: "Kung Fu")
user = client.users.new(name: "Panda")
client.users << User.new(name: "Nemo")
client.save # => this will create two users and a client, and add two records to the `clients_users` join table
I am trying to prevent users from creating duplicate chatrooms (chatroom includes 2 users). But I have no idea how to validate if chatroom with the same users already exists before save.
Thats my create method in chatroom controller:
def create
#chatroom = Chatroom.new
#friend = User.where(username: params[:friend]).last
#chatroom.chatroom_users.build(user: #friend, chatroom: #chatroom)
#chatroom.chatroom_users.build(user: current_user, chatroom: #chatroom)
if #chatroom.save
flash[:notice] = "chatrooom created"
redirect_to #chatroom
else
flash[:notice] = "chatrooom not created lol"
redirect_to authenticated_root_path
end
end
And that is how I am trying to validate if there is no chatroom with 2 users like the new one:
In my class Chatroom
after_save :duplicate?
# Checks if chatroom containing specific users exists.
def duplicate?
user = self.users.first
friend = self.users.second
# lines below work fine - they check if there is already such chatroom
(Chatroom.all - [self]).each do |chatroom|
if ((chatroom.users & [user, friend]) - [user, friend]).empty?
self.errors.add(:chatroom, "Such chatroom already exists.")
end
end
end
The problem is: if I use after_save in validating method I can get the self.users.first to set user and friend variables, but then It does not stop from creating that record and I am not sure If deleting it there is a good idea. Secondly - I use validate instead of after_save self.users.first and self.users.second returns nil, so I can't check for duplicates.
PS: I do not want to have users id as the attributes in the chatrooms table because I want to add ability to connect to chat for as many ppl as you want.
How about something like this?
def duplicate?
is_duplicate = (Chatroom.all.to_a - [self]).any? do |chatroom|
chatroom.users.map(&:id).sort == self.chatroom_users.map(&:user_id).sort
end
if is_duplicate
errors.add(:chatroom, "Such chatroom already exists.")
end
end
Here are all of the models.
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
class Chatroom < ApplicationRecord
has_many :chatroom_users, :dependent => :destroy
has_many :users, :through => :chatroom_users
before_validation :duplicate?
def duplicate?
is_duplicate = (Chatroom.all.to_a - [self]).any? do |chatroom|
chatroom.users.map(&:id).sort == self.chatroom_users.map(&:user_id).sort
end
if is_duplicate
errors.add(:chatroom, "Such chatroom already exists.")
end
end
end
class ChatroomUser < ApplicationRecord
belongs_to :chatroom
belongs_to :user
end
class User < ApplicationRecord
has_many :chatroom_users, :dependent => :destroy
has_many :chatrooms, :through => :chatroom_users
end
And here is a test
require 'test_helper'
class ChatroomTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
test 'does not allow duplicate chatrooms' do
first_user = User.create!
second_user = User.create!
chatroom = Chatroom.create!
chatroom.chatroom_users.build(user: first_user, chatroom: chatroom)
chatroom.chatroom_users.build(user: second_user, chatroom: chatroom)
chatroom.save!
duplicate_chatroom = Chatroom.create!
duplicate_chatroom.chatroom_users.build(user: first_user, chatroom: chatroom)
duplicate_chatroom.chatroom_users.build(user: second_user, chatroom: chatroom)
assert !duplicate_chatroom.valid?
end
end
Note: This code is in Rails 5.
I am using rails 2.3.17 and have this relationship setup
class Item < ActiveRecord::Base
belongs_to :order
end
class Order < ActiveRecord::Base
has_many :items, :dependent => :delete_all
end
Now i need to do a validation on an item, by accessing order object attributes, how can I do this?
When I write
validate :checkXYZ
def checkXYZ
Rails.logger.debug self.order // I AM GETTING NIL
end
but when I write
before_save :checkXYZ
def checkXYZ
Rails.logger.debug self.order // I AM ORDER OBJECT
end
This is my controller logic
#order = #otherObj.orders.create(params[:order])
item = #order.items.create(params[:item])
I need to get the order object in validate of item class, how can I do that?
In before_validate section, the parent(order) is not yet connected to the item object. Hence it'll definitely show nil.
But after the validation is passed & in before_save stage, the order & item are connected hence you are able to access the parent order of the selected item.
You can have below approach to validate your object.
class Order < ActiveRecord::Base
has_many :items, dependent: :delete_all
end
class Item < ActiveRecord::Base
before_save :something_missing?
belongs_to :order
private
def something_missing?
your_order = self.order
if (add_your_condition_which_is_violated)
errors[:base] << "Your error message"
return false
end
# When you are returning false here, the record won't be saved.
# And the respective error message you can use to show in the view.
end
end
To do this reliably when creating or updating an order you should call the validation on the parent object's (order's) model like this:
class Order < ActiveRecord::Base
has_many :items, :dependent => :delete_all
validate :checkXYZ
private
def checkXYZ
Rails.logger.debug self // Here you will have the Order object
for i in items do
if (vehicle == 7 and i.distance <= 500) then // vehicle is an attribute of order
errors.add(:error, "You're driving by car, distance must be larger than 500")
end
end
end
end
When a conversation is created, I want that conversation to have its creator automatically following it:
class Conversation < ActiveRecord::Base
belongs_to :user
has_many :followers
has_many :users, through: :followers
alias_method :user, :creator
before_create { add_follower(self.creator) }
def add_follower(user)
unless self.followers.exists?(user_id: user.id)
self.transaction do
self.update_attributes(follower_count: follower_count + 1)
self.followers.create(user_id: user.id)
end
end
end
end
However, when a user attempts to create a conversation I get a stack level too deep
. I'm creating an infinite loop, and I think this is because the before_create callback is being triggered by the self.update_attributes call.
So how should I efficiently update attributes before creation to stop this loop happening?
Option 1 (preferred)
Rename your column follower_count to followers_count and add:
class Follower
belongs_to :user, counter_cache: true
# you can avoid renaming the column with "counter_cache: :follower_count"
# rest of your code
end
Rails will handle updating followers_count for you.
Then change your add_follower method to:
def add_follower(user)
return if followers.exists?(user_id: user.id)
followers.build(user_id: user.id)
end
Option 2
If you don't want to use counter_cache, use update_column(:follower_count, follower_count + 1). update_column does not trigger any validations or callbacks.
Option 3
Finally you don't need to save anything at this point, just update the values and they will be saved when callback finishes:
def add_follower(user)
return if followers.exists?(user_id: user.id)
followers.build(user_id: user.id)
self.follower_count = follower_count + 1
end
I want to limit the amount of records a user can add to the database.
I'm not sure of the 'Rails' way to go about this...
I am using Devise and thought of creating a custom validation method but you can't access current_user from within a model and it isn't correct.
How can I do this from the controller and still return an error message to my users?
I had this
validate :post_count
def post_count
current = current_user.posts.count
limit = current_user.roles.first.posts.count
if current > limit
errors.add(:post, "Post limit reached!")
end
end
but it isn't the correct way to go about it as it would be hacky to get the current_user into the model
You could do something like this:
class User < ActiveRecord::Base
has_many :domains
has_many :posts, through: :domains
def post_limit
roles.first.posts.count
end
end
class Domain < ActiveRecord::Base
belongs_to :user
has_many :posts
end
class Post < ActiveRecord::Base
belongs_to :domain
delegate :user, to: :domain
validate :posts_count_within_limit, on: :create
def posts_count_within_limit
if self.user.posts(:reload).count >= self.user.post_limit # self is optional
errors.add(:base, 'Exceeded posts limit')
end
end
end
Based on this answer.