Has_one association should be a has "only" one association - ruby-on-rails

I have a User and a Gallery model and the following associations:
gallery.rb
attr_accessible :name, :description
belongs_to :user
user.rb
has_one :gallery
A gallery is created through a form and it is not build on the user creation
(I do this because some user won't be allowed to create a gallery)
Here is the gallery controller with the create action:
galleries_controller.rb
def create
#gallery = Gallery.new(params[:gallery])
#gallery.user_id = current_user.id # save user_id to gallery
if #gallery.save
redirect_to #gallery, :notice => "Your gallery has been successfully created."
else
render :action => 'new'
end
end
1.) My first question is:
when I set up a 1-to-1 association like this one, A user can create as many gallery as he wants. So is it not truly a has "only" one association? (I don't think I get the concept on this. Why is there no error raised?)
2.) My second question:
In order to have only one gallery per user I had a validation on the user_id in the gallery model
validates :user_id, :uniqueness => true
Is it the correct way to avoid many gallery records associated to one user?
EDIT
Thanks to Reuben, I dit it like this:
controller
def new
if current_user.gallery == nil
#gallery = current_user.build_gallery
else
flash[:error] = "You already have a gallery"
end
end
def create
#gallery = current_user.build_gallery(params[:gallery])
if #gallery.save
redirect_to #gallery, :notice => "Your gallery has been successfully created."
else
render :action => 'new'
end
end
In the view (new.html.erb)
<% if current_user.gallery == nil %>
<%= form ... %>
<% end %>
No user_id validation needed

Re your first question: What has_one really does is that it appends the LIMIT 1 clause to the corresponding sql query, and nothing more. That's why, in your case, the user can create as many galleries as they want.

You can put a check in the new action to see if the user already has a gallery and either redirect the user saying they already have a gallery or alert the user that creating a new gallery will destroy their existing one. In the latter case you would also need to check for the existing gallery in the create action and delete it before saving the new one otherwise you will have ownerless galleries filling your database.
In your views you could check the same and only show the new link if the user does not have a gallery.

To answer your second question, you could set unique to true for your migration that creates the foreign key:
add_index :gallery, :user_id, unique: true

Related

Rails: first_or_create not saving

My goal for my application is to only show a form page with existing data or a blank form if new. I've accomplished this by using a callback that created a blank record when the user is created.
User model:
before_create :build_health_profile
However, if for whatever reason a users "health_profile" were to be destroyed or non-existant, it breaks my entire app with:
"undefined method `health_profile' for nil:NilClass"
It was mentioned to me that the "first_or_create" method could solve this by show a new form or finding the existing one, but I can't get it to save the fields. It directs to my root with my save alert like it saved, but nothing gets actually saved.
Controller:
class HealthProfilesController < ApplicationController
def new
#health_profile = current_user.build_health_profile
end
def create
#health_profile = HealthProfile.where(user_id: current_user).first_or_create(health_profile_params)
if #health_profile.save
flash[:success] = "Health profile saved."
redirect_to root_path
else
render 'new'
end
end
private
def health_profile_params
params.require(:health_profile).permit(
:age,
:weight,
:height,
:gender
)
end
end
I've seen where I could use a block for "first_or_create", but no luck getting that to work.
View:
<%= link_to "Health Profile", new_health_profile_path %>
Models:
class User < ActiveRecord::Base
has_one :health_profile, dependent: :destroy
end
class HealthProfile < ActiveRecord::Base
belongs_to :user
end
If you use first_or_create then that calls the save method as part of it on the record and tries to save that in the database. If it can't save the record, then the transaction is rolled back. So, you want to use: first_or_initialize here which is like new and does not save the record in the database immediately. It just loads the data. So, you can call save on it in the next line of your code.
So, in your code, where you have:
#health_profile = HealthProfile.where(user_id: current_user).first_or_create(health_profile_params)
Here you are not controlling the save part, that's already being done by the first_or_create method.
So, you actually want to just load the object (NOT save yet) by using first_or_initialize:
#health_profile = HealthProfile.where(user_id: current_user).first_or_initialize(health_profile_params)
and then, in the next line, you can call the save and based on it's return value you can take the decision:
if #health_profile.save
# do stuff if successfully saved health_profile
else
# otherwise
render 'new'
end
Because you have #health_profile.save,
You should change first_or_create into first_or_initialize
first_or_create immediately trigger save, whereas first_or_initialize would just assign the values to a New record or to an already existing record if record exists already
I was able to fix the problem of the record resetting itself when going back to the form by adjusting the new action. Thats everyone for the help.
def new
#health_profile = current_user.health_profile || HealthProfile.new
end
def create
#health_profile = HealthProfile.where(user_id: current_user).first_or_initialize(health_profile_params)
if #health_profile.save
flash[:success] = "Health profile saved."
redirect_to root_path
else
render 'new'
end
end

rails autocomplete how to use exising record

I'm new to rails , and I have a problem with the nested forms and all of that.
I have a User model, and an Organization model.
When I want to create a user, I want to specify from which organization does he comes from.
Either the organization name is already in the database or if it's not, I want to create a new record and associate that record the User model.
I have hard time understanding all the relations (many-to-many etc) implications in the rails framework, but so far I've got this.
model/organization.rb
class Organization < ActiveRecord::Base
has_many :user
validates_presence_of :name
end
model/user.rb (short)
class User < ActiveRecord::Base
belongs_to :organization
accepts_nested_attributes_for :organization
#####
end
From this, in the console, I can create user and specify and organization name , and it will create a new record for the user and a new record for the organization.
The problem is that it creates a new organization each time.
I want to be able to associate an already existing organization to a new user.
I can get the list of organization with things like typeahead.js for the form, so the name will be the same when the user selects one. But I don't know how to relate the two (the newly created user and already existing organization).
I thought of putting a hidden field with the id of the organization, and check in the controller if this id exists. If it does, put this id, if it doesn't create a new one. But I don't even know how to do this. In the console, when I update the attributes of a user, for example , with an organization_id = 3 which exists :
u.update_attributes( :organization_attributes => { id: 3 } )
It rejects saying he didn't find a user with ID=... with Organization.id = 3 ...
I don't understand.
I suppose since this is a common case, that this should be easy , but it's messing with my head.
If someone is willing to explain to me, I'd be very grateful.
Thank you.
EDIT
i've just tried something in my controller but that doesn't work either.
def create
#user = User.new(user_params) # :user object built from user inputform
org = Organization.find_by(name:user_params[:organization_attributes][:name])
if org
#user.organization.id = org.id
end
if #user.save
# signin the user (token etc)
sign_in #user
flash[:success] = "Registration sucessfull !"
redirect_to #user
else
render 'new'
end
end
+user_controller (strong params)
def user_params
params.require(:user).permit(:lname,:email,:fname,:password,:password_confirmation,
:gender,:role,:display_private,:link_li,:country,:city,:phone,:hobbies,
:avatar,:org_name, :organization_attributes => [ :id, :name])
end
+form.html.erb
<%= u.fields_for :organization do |o| %>
<%= o.label "Organization" %>
<!-- PUT ORGA -->
<%= o.text_field :name, class:"form-control" %>
<% end %>
I would write a custom method for this:
#in User
def organization_name
(org = self.organization) && org.name
end
def organization_name=(name)
if org = Organization.find_by_name(name)
self.organization = org
else
self.organization = Organization.create(:name => name)
end
end
Now, because you've got a getter and setter method (ie two methods with the same name, apart from the = sign), you can treat organization_name like an attribute of User and put it in a form field like
f.input :organization_name
The input will get the current value from #user.organization_name and will call #user.organization_name= with the new value.
First take away the accepts_nested_attributes from the model.
Then in your controller you should do something like:
def create
#user = User.new(user_params) # :user object built from user inputform
org = Organization.where(name: user_params[:organization_attributes][:name]).first || Organization.create(name: user_params[:organization_attributes][:name])
#user.organization = org
if #user.save
# signin the user (token etc)
sign_in #user
flash[:success] = "Registration sucessfull !"
redirect_to #user
else
render 'new'
end
end
In your app/model/user.rb
def self.create(name, attribute1, ... ,organization)
user = User.new(:name => name, :atr_1 => attribute_1, ....:atr_n => attribute_n)
user.organization = organization
raise "user not created" if !user.save
user
end
In users_controller.rb
def create
org = Organization.find params['organization'] #expecting the param to be Organization#id
user = User.create(params['name'], ..., org)
render :json => {:message => "user created"}
end

Dealing with a unique error

So I have got a database called Awards.
Users are able to 'award' a recipe but they can only do this once. The award database consists of recipe_id and user_id. I have made both of these columns unique so it wont allow you to award the recipe more than once. This works fine and if you attempt to award the recipe a second time I get this error:
columns user_id, recipe_id are not unique
Is there some code I can add into th create action to check for this error and then render a flash error message such as "already awarded recipe" instead of showing the error console?
this is my create method:
def create
#award = current_user.awards.build(award_params)
if #award.save
flash[:success] = "Award Given!"
redirect_to recipe_path(params[:recipe_id])
else
render 'static_pages/home'
end
end
Thanks,
Mike
There is a whole section of rails called validations that you're hitting on. The documentation is here: link. To get you basically set up, you could:
# award.rb
class Award < ActiveRecord::Base
validates_uniqueness_of :user_id, :recipe_id
end
# awards_controller.rb
def create
#award = current_user.awards.build(award_params)
if #award.save
flash[:success] = 'Award Given!'
redirect_to recipe_path(params[:recipe_id])
else
flash[:error] = 'There was an error awarding this award!'
render 'static_pages/home'
end
end

Importing data from a form?

I am trying to import four data fields. Child_id, First_name, Last_name, Medical. In my form it is only pulling in child_id:
<%= form_for #check_in, :url => {:controller => 'check_ins', :action => 'create' } do |f| %>
<% #account.children.each do |child| %>
<%= f.check_box :child_id, {:checked => 'checked', :multiple => true}, child.id.to_s %>
<%= image_tag child.photo.url(:thumb) %>
<span class="name"><%= child.first %>
<%= child.last %></span><br/>
<% end %>
<% end %>
Model associations:
class CheckIn < ActiveRecord::Base
has_many :children
end
class Child < ActiveRecord::Base
belongs_to :account
belongs_to :check_in
end
This is my create method in my check_ins controller.
def create
#check_in = CheckIn.new(params[:check_in])
begin
params[:check_in] [:child_id].each do |child_id|
unless child_id == 0.to_s
CheckIn.new(:child_id => child_id).save!
end
end
respond_to do |format|
format.html { redirect_to(:controller => 'sessions', :action => 'new') }
format.json { render json: #check_in, status: :created, location: #check_in }
end
rescue
respond_to do |format|
format.html { render action: "new" }
format.json { render json: #check_in.errors, status: :unprocessable_entity }
end
end
end
This form is also on a show page. The checkbox is there and next to the checkbox is the information pulled from another table: child.first, child.last. But those fields I want to be selected along with the checkbox like the child_id is.
Right now I have a child saved in my table with an id of 8 it would pull in the 8 but the fields for child.first and child.last aren't pulling into the new table that the id is.
Hm, by "import data field" you mean showing the attributes of child within your form?
The form looks ok to me, now it depends on things outside of this form.
I would check on the following:
Are the fields of child indeed named first, last and photo as used in
the code snippet, and opposed to those you listed in your question?
What are the contents of #account and #account.children? You could output both on your page to check.
I only see one form tag in your form block: f.check_box :child_id. The other stuff like <%=
child.first %> are not part of the form, even though they're within the form block.
EDIT:
There are a number of problems. First, going strictly by the way the associations are set up, CheckIn should not have child_id attribute. It has_many :children while Child belongs_to :check_in. CheckIn should not have a child_id, Child should have a check_in_id. Therefore you should be updating each selected child with a check_in_id value. I'd read up on ActiveRecord associations: http://guides.rubyonrails.org/association_basics.html
Second, the way the form is rendering controls I think you're ending up with multiple checkboxes with the same name. When rails assembles the params hash it's going to ignore all but the last hash item with a particular key. So even if everything else was set up correctly you'd still only save one child to a check in. I'd watch this tutorial on nested attributes:
http://railscasts.com/episodes/196-nested-model-form-part-1?view=asciicast
Lastly, I don't understand what you mean when you say it's not saving child.first and child.last (aka first_name and last_name?). That information is stored in the child object already, correct? Why would that be saved elsewhere?
If all of this was working correctly you'd be able to do things like this:
# find an account
account = Account.find(99)
# create a check_in
check_in = CheckIn.create
# save one or more (or all) of account's children to the check_in
check_in.children << account.children
# see the first name of a child associated with a check_in
check_in.children[0].first

Editing content that is nil

I want to edit a 3 model on has_many. My question is about the controller and how can I access the information
I have the following model
Customer Book Book_Manager
id id id
first description customer_id
last book_id
email visible
password
The relationship his has follow
Customer
has_many book_managers
has_many books :through -> book_managers
Book
has_many book_managers
has_many customer :through -> book_managers
Book_managers
belongs_to :customer
belongs_to :book
When a customer wants to edit the content, i want to be the latest one to be shown if any exist. The query i have at the moment doesn't seem to handle an empty case and not sure how to do it.
#book = current_customer.books.order("created_at DESC").first
How should i declare it in a def edit of the customercontroller??
Update: I want to be able to see my data, unfoturnotly it doesn't seem to work here my
customer controller
def create
#customer = Customer.new(params[:customer])
#book = #customer.books.build(params[:book])
if #customer.save
cookies[:auth_token] = #customer.auth_token
redirect_to #customer, notice: "Thank you for signing up!"
else
render "new"
end
end
def edit
#customer = Customer.find(params[:id])
if current_customer.books.length > 0
#book = current_customer.books.order("created_at DESC").first
else
#Nor books were found, so create one that has the proper relationship to current_customer
#book = current_customer.books.build
end
end
I am rendering a partial from the book folder, should my create option be in the customer Controller or in bookControllers
Update: using customerController has my main create, when creating its say missing actions in books controller should i have more action in the book controller?
All you need to do is a simple check in the controller to see if any books exist. If one does not exists then create a new one.
#Check if the books array contains anything. There are several ways to do this
if current_customer.books.length > 0
#book = current_customer.books.order("created_at DESC").first
else
#Nor books were found, so create one that has the proper relationship to current_customer
#book = current_customer.books.build
end

Resources