I am using Ruby on Rails 3 and I successfully use nested models in order to save model\object associations.
In the user model file I have:
class User < ActiveRecord::Base
has_one :account
accepts_nested_attributes_for :account
validates_associated :account
end
After #user.save I would like to retrieve the account id just created and save that value in the user database table. I need that because I will use the account_id as the foreign key for the user class, but I don't know if it is possible. If so, how can I do that?
In my user model I also tryed the following:
before_create :initialize_user
def initialize_user
user_account = Account.create
self.account_id = user_account.id
end
but it doesn't work.
UPDATE
I tryed this
class User < ActiveRecord::Base
belongs_to :account,
:class_name => "Account",
:foreign_key => "users_account_id"
end
class Account < ActiveRecord::Base
has_one :user,
:class_name => "User",
:foreign_key => "users_account_id"
end
and it save the new account. Anyway in the user database table the column users_account_id is null and so the foreign_key value isn't saved automatically.
The Approach is wrong. When you have a "has_one" relationship, they foreign key is in the associated model. So in your case it will in account. And if its accepting nested attributes for account. That should be taken care of by default if you are doing it write.
Take a look http://railscasts.com/episodes/196-nested-model-form-part-1 and the other part as well, to see how nested forms work
def initialize_user
user_account = Account.create
self.account_id = user_account.id
end
should be
def initialize_user
self.account.create
end
When the new Account instance is being created, it will use the information about the current user automatically. Your method would have worked, but you'd have needed to add an extra "save" call.
Related
I have two models.
user.rb
class User < ActiveRecord::Base
has_many :trial_subscriptions
attr_accessible :trial_subscriptions_attributes
end
in my trial_subscription.rb
the model is
class TrialSubscription < ManualSubscription
end
Please note that the ManualSubscription model inherits from the subscription model and this model has a
belongs_to :user
I am trying to use the 4.1.1.3 build_association(attributes = {}) from the active record guide to build the user. I need some more detailed explanation.
in my rails console I am doing the following and my reasoning for the following
#subscription = TrialSubscription.new() #creating a new object
#user = #subscription.build_user(email: 'blahblahblah#gmail.com', password:'eightcharacterslong')#building the user
#user.save #saving the user permanently
User.last #saved user shows up
but when I do the following
#a = User.last
#a.trial_subscriptions
I am getting an empty array.
When I do
Trial_Subscription.last, there is no extra record (the TrialSubscription.new ? )
I would expect the last trial_subscription record to have its
user_id #be filled in with the build user that I just created.
some explanation would be great!
these code will be going to my trial_subscriptions_controller.rb and there will be a form posting user inputted information.
Also I just noticed that I can do a
#user.trial_subscriptions.create!() #and then maybe a #user.save
but I feel like the first way should work.
Let's look at your two models first:
# app/models/user.rb
class User < ActiveRecord::Base
has_many :trial_subscriptions
attr_accessible :trial_subscriptions_attributes
end
# app/models/trial_subscription.rb
class TrialSubscription < ManualSubscription
end
The point of discussion should be your usage of attr_accessible :trial_subscriptions_attributes, what this does is allows trial_subscriptions_attributes for mass assignment. Now the point to note here is, whichever model you define attr_accessible :association_attributes in should be the model you use to create the association.
The next point point of discussion would be around mass_assignment. You have allowed trial_subscriptions_attributes for mass assignment, but trial_subscriptions_attributes is not a member attribute of the model class User. You have trial_subscriptions but not trial_subscriptions_attributes. You could simply define a getter and a setter for trial_subscriptions_attributes, but Rails already provides accepts_nested_attributes_for helper for doing so for associations. So update your model User to add accepts_nested_attributes_for :trial_subscriptions:
# app/models/user.rb
class User < ActiveRecord::Base
has_many :trial_subscriptions
accepts_nested_attributes_for :trial_subscriptions
attr_accessible :trial_subscriptions_attributes
end
With this setup you should be able to create user with associated trial subscriptions as:
#user = User.new(email: 'blahblahblah#gmail.com', password:'eightcharacterslong')
#subscription = #user.trial_subscriptions.build
#user.save #saving the user permanently
User.last // Shows last user
User.last.trial_subscriptions #shows last users trial subscriptions
With the approach you're taking to create users through trial subscription, you need to define accepts_nested_attributes_for and attr_accessible in trial_subscription model instead of user model.
Hope this is helpful.
First, thanks for taking the time to read. I'm new to Rails and have been stuck on this one for many hours.
In my Rails 3.2 app, I have three models: User, Organization, and Membership (the last is a join model between User and Organization).
When a user creates an organization, he/she should become a member upon create. So, in my Organization model, I've included a before_create callback that builds a Membership. The problem is that while the Membership builds when the new Organization is created, the user_id on the Membership object is set to "nil.," and therefore the current user is not a member.
Hardcoding in the user_id attribute in the callback actually does correctly build the membership, i.e. (:user_id => "1"), but in general asking the Organization model to be aware of current user state seems like bad MVC practice.
What's the proper way to set the current user ID on the new Membership? It seems like my associations should handle that, but I might be wrong.
Here are my models — I'm leaving out some validation lines for readability's sake. Thanks so much in advance.
user.rb
class User < ActiveRecord::Base
has_many :memberships
has_many :organizations, :through => :memberships
end
membership.rb
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :organization
end
organization.rb
class Organization < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
accepts_nested_attributes_for :memberships, :allow_destroy => true
...
before_create :add_membership
protected
def add_membership
self.memberships.build
end
end
You are right in the fact that allowing your model to magically know about the current user is bad MVC practice. So you have to somehow pass the current user id during creation. you can do this in many ways ; for example in the controller :
def create
#organization = Organization.new( params[:organization] ) do |org|
org.memberships.build( user_id: current_user.id )
end
# save, etc.
end
Doing this in the controller is fine, but it would be better if your business logic would reflect the fact that a user creating an organization should automatically belong to it. You could override new and / or create on Organization (or create your own method if you fear overrides) :
def new( params = {}, options = {} )
creator = options.delete( :creator )
super( params, options ) do |org|
org.memberships.build( user_id: creator.id ) if creator
yield org if block_given?
end
end
passing the user is easy now :
def create
#organization = Organization.new(params[:organization], creator: current_user)
end
If you don't like this approach, or if you don't want to override new or create a specific factory method, you can also make something similar to nested_attributes :
attr_accessible :creator_id
def creator_id=( user_id )
memberships.build user_id: user_id
end
then in your view :
f.hidden_field :creator_id, current_user.id
optional :
with first approach, for additional clarity / ease of use, you can also create a method on User :
def new_organization( params = {}, options = {}, &block )
Organization.new( params, options.merge(creator: self), &block )
end
... ok, Organization is hardcoded here (bad !) but your workflow is now quite understandable :
def create
# we know at first glance that the user is responsible for the organization
# creation, and that there must be specific logic associated to this
#organization = current_user.new_organization( params[:organization] )
# etc
end
with a little more thinking, it should be possible to avoid hardcoding Organization into User (using an association extension for instance)
EDIT
To be able to setup a validation on membership's organization presence, you need to do this :
class Organization < ActiveRecord::Base
has_many :memberships, inverse_of: :organization
end
class Membership < ActiveRecord::Base
belongs_to :organization, inverse_of: :memberships
validates :organization, presence: true
end
Let's explain this :
inverse_of sets up your associations to be bidirectional. By default, associations are one-way, which means that when you do organization.memberships.first.organization, rails tries to load the organisation again because it does not know how to "climb back" the association. When using inverse_of, rails knows it does not have to reload the organization.
validates MUST be setup on organization and NOT on organization_id. This way the validator knows we're "climbing back" the association, it knows that organization is a "parent" record and that it's in the process of being saved - so it does not complain.
Is it possible to build an object through two has_many associations? For example:
# posts_controller.rb
def create
#post = current_account.posts.build(params[:post])
#post.author = current_user # I want to compact this line into the previous one
end
I did some research and found this:
#article = current_account.posts.build(params[:post], user_id: current_user.id)
However, that did not work. In console, I kept getting user_id: nil whenever I built a new object.
Another potential solution I could not implement:
#post = current_account.post_with_user(current_user).build(params[:post])
But every implementation of post_with_user I wrote failed.
My associations are as follows:
class Discussion < ActiveRecord::Base
belongs_to :account
belongs_to :author, class_name: 'User', foreign_key: 'user_id', inverse_of: :discussions
end
class User < ActiveRecord::Base
belongs_to :account
has_many :discussions, inverse_of: :author
end
class Account < ActiveRecord::Base
has_many :users, inverse_of: :account
has_many :discussions
end
The thing your code shows you trying to do, you should be able to do. It should look something like this:
#article = current_account.posts.build(params[:post])
Because you're building off of the list of the current account's posts, you don't have to pass the current account's ID. (I'm not sure if your current_user is the same as your current_account, you may wish to clarify this).
To compact your post creation into one line, you can do one of two things.
Turn the relationship between a user/author and a post into a two-way relationship. Check out the documentation http://guides.rubyonrails.org/association_basics.html where an order belongs_to a customer, and a customer has_many orders. You can customize the name of the relationship so that a post has an "author" instead of a user, by calling it "author" but then using the class_name parameter which I assume would take the value :user.
Add a after-create hook to the Post class, and set the author value to the same as the current user. I can't fill in much more detail about this without knowing anything about your user subsystem.
The params variable is just a hash, so something along these lines should work to give you a one liner:
#post = current_account.posts.build params[:post].merge({ :user_id => current_user.id })
I have the following setup and I want to ensure that all brands in my brand model belong to all users in my user model. I would also like to ensure that once a brand has been created, and it belongs to all users, it will also belong to future users that sign up down the line.
Brand model
has_many :brand_users
has_many :users, :through => :brand_users
after_create :associate_with_all_users
def associate_with_all_users
User.find(:all).each do |user|
users << user
end
save
end
BrandUser model
belongs_to :brand
belongs_to :user
User model
has_many :brand_users
has_many :brands, :through => :brand_users
When I try the following in the console, it shows that currently the last brand instance only belongs to a single user and not both (there are currently 2 users that exist).
>> User.all.count
=> 2
>>BrandUser.last.user_id
=>1 #Should not belong to just the one user but both
Your models look correct, you may be able to clean up your Brand association call to:
def associate_with_all_users
self.users = User.all
# save I don't believe this is necessary anymore if you assign the association
end
As for ensuring all newly created users receive all Brand's, you could do a
class User
after_create :associate_with_brands
def associate_with_brands
self.brands = Brand.all
end
end
or maybe look at an http://api.rubyonrails.org/classes/ActiveRecord/Observer.html
Your code should work, if you try Brand.first.users don't you get all of your users?
Either way, if every brand is associated with every user and viceversa, why don't you do try something like this:
def User > ActiveRecord::Base
def brands
Brand.all
end
end
def Brand > ActiveRecord::Base
def users
User.all
end
end
I'm using Devise, and for each User account created I want to generate a relationship where:
class User < ActiveRecord::Base
belongs_to :business
end
class Business < ActiveRecord::Base
has_many :users
has_one :apt_setting
has_many :hours, :as => :hourable
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
end
So upon registration an associated Business object is created, and with each Business object an associated ApptSettings and BusinessHour object is created.
I currently have this implemented like this:
class Admin
before_create :create_associated_records
def create_associated_records
# create the associated business object
business = Business.create(:business_name => business_name, :subdomain => subdomain, :initial_plan => initial_plan)
# retrieve the id of the new business object
self.business_id = business.id
# create the associated records
BusinessHour.default_values(business_id)
ApptSetting.default_values(business_id)
end
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
def self.default_values(business_id)
# ... create record with default values
end
end
class BusinessHour < Hour
belongs_to :hourable, :polymorphic => true
def self.default_values(business_id)
# ... create record with default values
end
end
This does work, but does it seem like the best design?
One alternative I'm considering is handling removing Admin -> create_associated_records, and instead do that work in Users::Accounts::RegistrationsController where I override the 'create' method. There I could build all the associated records, set :accepts_nested_attributes where appropriate, then call 'save' on the Business object, which should then cause all the associated records to be generated.
Thoughts on the best design, or any other ideas?
you don't need the default_values methods. In your create_associated_records you can change those calls to:
ApptSetting.create(:business_id => business_id)
Don't override the create method. before_create callbacks are a better way to go. In either case, If a business has many users, do you really want to create a new business every time a new user is created? How does a second user ever get added to a business? add something like,
def create_associated_records
return unless self.business_id.nil?
....
Also where are the business_name, subdomain, and initial_plan variables coming from in your method? Do you have them as attributes of the admin user? Seems like they should be only values of the business.
I think the biggest question here is, does a user really need a business in order to exist? Why can't the user just create their Business after they create their account?
** Edit: Being more clear / cleaner version using rails association methods:
class Admin
before_create :create_associated_records
private
def create_associated_records
return unless self.business_id.nil?
self.create_business
self.business.create_appt_setting
self.business.hours.create
end
end