I have a admin controller to create "Locations", inside of which is a form. In the form you can create a new "Location" and give it a name, description, and "exits".
<%= form_for #location do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :description %><br>
<%= f.text_area :description %>
</p>
<p>
Exits:
<br/>
<% #locations.each do |e| %>
<%= f.label :locations %>
<%= f.check_box :locations %>
<% end %>
</p>
<p>
<%= f.submit %>
</p>
<% end %>
#locations is all of the available locations to set up as an "exit". I'm trying to create an array of locations inside of the new location using checkboxes to check whether or not they should be set as "exits", but not really sure how to proceed since i'm just teaching myself and it's day one.
I'm also wondering how to set up the "exits" in the Location model. Right now i have:
class Location < ActiveRecord::Base
has_many :locations
validates :name, presence: true, length: { minimum: 1 }
validates :description, presence: true
end
created with:
class CreateLocations < ActiveRecord::Migration
def change
create_table :locations do |t|
t.string :name
t.text :description
t.references :location, index: true, foreign_key: true
t.timestamps null: false
end
end
end
But i would like to have them referenced as "exits" not "locations" (ie Location.exits instead of Location.locations).
UPDATE
I've updated the form to use the collection_check_boxes helper
<%= f.collection_check_boxes(:exit_ids, #locations, :id, :name) do |cb| %>
<%= cb.label %> <%= cb.check_box %> <br/>
<% end %>
So I have this in the create action now
def create
#location = Location.new(location_params)
if #location.save
params[:location][:exit_ids].each do |e|
if e.to_i > 0
tempLocation = Location.find(e.to_i)
#location.allowExitTo(tempLocation)
end
end
redirect_to #location
else
render 'new'
end
end
Which goes allows exits through:
def allowExitTo(other_location)
exit_locations.create(to_id: other_location.id)
end
creating an exit relationship with:
class Exit < ActiveRecord::Base
belongs_to :from, class_name: 'Location'
belongs_to :to, class_name: 'Location'
validates :from_id, presence: true
validates :to_id, presence: true
end
Hope I'm on the right track. It seems to work so I guess so.
Edit: Sorry, I must have had a brain-fart or something. You're not doing a simple has-many and belongs-to relationship, you want a has-many-and-belongs-to-many relationship. That's more complicated than I know how to explain. But this answer explained it very well: Many-to-many relationship with the same model in rails?
You can get Location#exits instead of Location#locations by changing the name of the has_many relation like this:
class Location < ActiveRecord::Base
# has_many :locations Instead of this...
has_many :exits, :class_name => 'Location' # ...Do this
validates :name, presence: true, length: { minimum: 1 }
validates :description, presence: true
end
This is called a self-join. You're associating a model with itself.
Keep in mind that this will make Location#locations unavailable.
Then in your form you would do something like:
<% #locations.each do |e| %>
<%= f.label :exit %>
<%= f.check_box :exit %>
<% end %>
The Ruby on Rails site has a nice guide about associations, includeing self-joins like what you're asking about: http://guides.rubyonrails.org/association_basics.html#self-joins
If you like RailsCasts: http://railscasts.com/episodes/163-self-referential-association
Related
I have a sample rails app with a list of users. And I wanted to experiment with polymorphic tags.
At this moment I can create tags for users through console in the following way
User.first.tags.create(name: "new tag name")
But have problems with adding them through webform
Here's what I did:
rails g model Tag name taggable:references{polymorphic}
generated the following migration
class CreateTags < ActiveRecord::Migration[5.1]
def change
create_table :tags do |t|
t.string :name
t.references :taggable, polymorphic: true
t.timestamps
end
end
end
tag model
class Tag < ApplicationRecord
belongs_to :taggable, polymorphic: true
end
user model
class User < ApplicationRecord
has_many :tags, as: :taggable
end
Tags field
<%= form_with(model: user, local: true) do |form| %>
...
<div class="field">
<%= form.label :tag_list %>
<%= form.text_field :tag_list, placeholder: "tags separated by comma" %>
</div>
...
<% end %>
I also found the following code but getting You cannot call create unless the parent is saved error.
added the following setter to user model and added :tag_list to strong params of user
def tag_list=(vals)
self.tags = vals.split(", ").each do |val|
tags.where(name: val.strip).first_or_create!
end
end
You can add tags with nested form
user model
class User < ApplicationRecord
has_many :tags, as: :taggable
accepts_nested_attributes_for :tags
end
in your form
<%= form_with(model: user, local: true) do |form| %>
...
<div class="field">
<% form.fields_for :tags do |t| %>
<%= u.text_field :tag_name %>
<% end %>
</div>
...
<% end %>
in your controller , please add other attribute as well
params.require(:user).permit( tags_attributes: [:id,:tag_name])
I have a user model and a town model. A user belongs_to a town:
# models/user.rb
class User < ApplicationRecord
belongs_to :town
accepts_nested_attributes_for :town
validates :last_name, presence: true
end
# models/town.rb
class Town < ApplicationRecord
has_many :users
validates :name, presence: true
validates :name, uniqueness: true
end
You go to create a new user record: there is a text_box in order to put in the associated town's name. When submitted, the rails application should do the following:
use find_or_create_by. go and check if there already exists a town record with the name attribute passed in.
If a town record DOES exist with that given name attribute, just associate that existing town record to this new user record
If a town record DOES NOT exist with that given name attribute, then create a new town record with that name attribute, and associate that one to this user record
If any validations fail, do not create a user record or a town record and re-render the form.
I am having real trouble with this. This question suggests putting autosave on the belongs_to :town statement, as well as defining a method called autosave_associated_records_for_town. However: I just could not get that to work.
Appreciate it!
Please, try that solution. It works for me.
User
# user.rb
class User < ActiveRecord::Base
belongs_to :town
accepts_nested_attributes_for :town
validates :last_name, presence: true
end
Town
# town.rb
class Town < ActiveRecord::Base
has_many :users
validates :name, presence: true
end
Controller
# users_controller.rb
respond_to :html
def create
# ...
#user = User.new(user_params)
#user.town = Town.find_or_initialize_by(user_params[:town_attributes])
if #user.save
respond_with(#user)
else
render 'new'
end
end
# ...
def user_params
params.require(:user).permit(:last_name, :email, :town_id, town_attributes: [:name])
end
View
# users/_form.html.erb
<%= form_for(#user) do |f| %>
<% if #user.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#user.errors.count, "error") %> prohibited this user from being saved:</h2>
<ul>
<% #user.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :email %><br>
<%= f.text_field :email %>
</div>
<div class="field">
<%= f.label :last_name %><br>
<%= f.text_field :last_name %>
</div>
<%= f.fields_for :town, #town do |bldr| %>
<div class="field">
<%= bldr.label :name, 'Town name' %><br>
<%= bldr.text_field :name %>
</div>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
UPDATE
Please consider to add validates_associated to user's validations as well.
Here is the related documentaion
class User < ActiveRecord::Base
belongs_to :town
accepts_nested_attributes_for :town
validates :last_name, presence: true
validates :town, presence: true
validates_associated :town
end
Generaly speaking, you could remove validates :town, presence: true in that case. Validations will work without it.
Could you please help with understanding why categories does not working in a right way? So I read quite a lot manuals about, and did not find how to resolve it
I created migration for category
class CreateCategories < ActiveRecord::Migration
def change
create_table :categories do |t|
t.string :name
t.text :description
t.integer :count
t.timestamps null: false
end
end
end
And added for posts new field - category
Then created model for category
class Category < ActiveRecord::Base
has_many :posts
end
Edit post model
class Post < ActiveRecord::Base
acts_as_ordered_taggable
belongs_to :category
validates :title, presence: true
validates :category, presence: true
..
end
Created template
<%= form_for #post do |f| %>
<p>
<%= f.label :title %> <br>
<%= f.text_field :title %>
</p>
<p>
<%= f.label :category %>
<%= f.select :category, Category.all.collect {|c| [c.name, c.name]} %>
</p>
<% end %>
Edited a bit post controller
def update
#post = Post.find(params[:id])
if #post.update(params[:post].permit(:title, :thumbnail, :body, :description, :tag_list, :#post.category))
redirect_to #post
else
render 'edit'
end
end
change this line in your controller:
if #post.update(params[:post].permit(:title, :thumbnail, :body, :description, :tag_list, :category_id))
I changed
:#post.category
to
:category_id
This passes a symbol to the permit method to allow the request parameter for the category
I'm trying to get my User to save to a Rate. I was able to get the Location to be saved to the Rate by removing the presence validation but after it's created it doesn't have the current user. How would I do this for my nested form?
User.rb
attr_accessible :email, :password
has_many :locations
has_many :rates
Location.rb
attr_accessible :name, :rates_attributes
belongs_to :user
has_many :rates
accepts_nested_attributes_for :rates, :reject_if => :all_blank
# Not sure if :all_blank works anyways as it -
# still saves even when theirs no user_id, lol
Rate.rb
attr_accessible :amount, :location_id
belongs_to :location
belongs_to :user
validates_presence_of :amount
# Couldn't use these validations
# validates_presence_of :user_id
# validates_presence_of :location_id
LocationsController
def new
#location = Location.new
#location.rates.build
end
def create
#location = current_user.locations.build(params[:location])
if #location.save.....
end
locations/new.html.erb
<%= nested_form_for #location do |f| %>
<%= f.label :name, "Name *" %>
<%= f.text_field :name %>
<%= f.link_to_add "Add Rate", :rates %>
<%= f.fields_for :rates do |r| %>
<%= r.text_field :amount %>
<%= r.link_to_remove "Remove" %>
<% end %>
<%= f.submit "Add Location" %>
<% end %>
There is a great railscast on this topic; episodes 196 and 197. Even better, Ryan wrote a gem https://github.com/ryanb/nested_form.
The gem is super easy to implement. If set up correctly the nested form grabs the parent object id on create automatically.
I don't notice anything in the code you have posted that looks wrong...what does your nested form look like in the view?
Here's my user model:
class User < ActiveRecord::Base
has_one :teacher, :dependent => :destroy
accepts_nested_attributes_for :teacher, :allow_destroy => true
attr_accessible :email, :password, :password_confirmation, :remember_me, :teacher_attributes
end
Here's my teacher model:
class Teacher < ActiveRecord::Base
belongs_to :user
attr_accessible :user_id, :first_name, :last_name
validates_presence_of :user_id, :first_name, :last_name
end
Here's my form:
<%= form_for(#user, :url => registration_path(:user)) do |user| %>
<%= user.text_field :email %>
<%= user.text_field :password %>
<%= user.text_field :password_confirmation %>
<%= user.fields_for resource.build_teacher do |t| %>
<%= t.text_field :first_name %>
<%= t.text_field :last_name %>
<%= t.text_field :phone %>
<% end %>
<%= user.submit 'Confirm' %>
<% end %>
Except that this thing won't "accept nested attributes"
My development log says:
WARNING: Can't mass-assign protected attributes: teacher
I don't know if it's related, but the form isn't generating fields inside a teacher_attributes array or anything - it's inside teacher. I'm guessing that's where my problem is, but I don't how to make it put the fields inside it. Please help.
Thanks!
Try these things:
At top of view:
<% #user.build_teacher if #user.teacher.nil? %>
For the fields for:
<%= user.fields_for :teacher do |t| %>
Also, personally, I like naming the block parameters in forms (the part |user| and |t|) as |form| (because when you're having a long day, and you see user down in the view and not form, it can confuse you!)