Rails auto complete User object - ruby-on-rails

In my application users belongs to a group and this is done in my Usergroup Model.
class Usergroup < ApplicationRecord
belongs_to :user
belongs_to :group
end
class User < ApplicationRecord
has_many :usergroups
has_many :groups, through: :usergroups
end
class Group < ApplicationRecord
has_many :usergroups
has_many :users, through: :usergroups
end
When I want to add a user to the group though I need to know the ID of the group and the ID of the user which is less than ideal. I created a autocomplete field using jQuery to take care of this for me.
<%= form_with(model: usergroup, local: true) do |form| %>
<div class="field">
<%= form.label :user_id %>
<%= form.text_field :user_id, id: 'user', data: { autocomplete_source: User.order(:email).map(&:email) } %>
</div>
<%= form.submit %>
<% end %>
When I attempt to create the new usergroup with the email I selected from this dropdown menu it's submitting because it's requiring there to be a user. How do I pass the complete user object to this field instead of just the email of the user I want to create? Am I approaching this correctly?
This is the route that is being used to get the users.
user GET /users/:id(.:format) users#index

When I have to find a value from a large list I like to use the gem "select2-rails" gem
you can make an Ajax code to get your values like this
class UserController < ApplicationController
respond_to :html, :json
def index
if params[:email].present?
#users = User.where(email: params[:email])
else
#users = User.all
end
respond_with #users
end
end
in your HTML all you need
<div class="field">
<%= form.label :user_id %>
<%= form.hidden_field :user_id, id: 'user_id', class: 'select2' %>
</div>
and in your JS
$(document).ready(function() {
$('.select2').select2({
ajax: {
url: "<%= user_path(format: :json) %>",
dataType: "json",
results: function(data, page) {
return {
results: $.map( data, function(user, i) {
return { id: user.id, text: user.email }
} )
}
}
}
});
});
I hope that this works for you or points you in the right direction

Typically when you have an autocomplete, you want to use a select box for this. One of the advantages of a select box is that you can display one value and the select will actually submit a different one. This is perfect for your situation where you want to have the user select an email but you actually want to receive a user_id server side.
<%= form.collection_select :user_id, User.order(:email), :id, :email %>
You can wrap a select like this with libraries like Selectize or Select2 to provide an autocomplete style dropdown.
I believe the jQuery Autocomplete library would work for this too, but I'm not terribly familiar with it. This looks like what you'd want where each item is an object with a value and a label. https://jqueryui.com/autocomplete/#custom-data
It might look like this with your existing JS that loads the autocomplete_source attribute, but no guarantees this will work.
<%= form.collection_select :user_id, User.order(:email), :id, :email, data: { autocomplete_source: User.order(:email).map{ |u| { value: u.id, label: u.email } } %>

Related

Ruby on rails Validation does not work for Form objects that contain Action text, etc

Error Overview
There is a Github URL of a simple app that reproduces the problem in the bottom paragraph.
I'm creating an application that allows article submission. To make it easier to submit articles, I introduced Rails Action text. I also decided to add a tagging feature, so I have a tags table, an article table, and a user table in the database. The article form looks like the following image.
The article form
I was able to submit an article, but I wanted to check the validation of the condition that the article could not be submitted with a blank field, so I submitted it with a blank field, but I got the following error.
NoMethodError
I don't know how to solve this problem and I need your help.
The relevant source code
This is the view file of the corresponding section.↓
<%= render "shared/header" %>
<%= form_with model: #article, url: articles_path, class:'form-wrap', local: true do |f| %>
<%= render 'shared/error_messages', model: f.object %>
<div class='article-form'>
<div class="title-field">
<%= f.label :title, "題名" %>
<%= f.text_area :title, class:"input-title" %>
</div>
<div class="tag-field", id='tag-field'>
<%= f.label :name, "タグ" %>
<%= f.text_field :name, class:"input-tag" %>
</div>
<div class="content-field">
<%= f.label :content, "記事本文" %>
<%= f.rich_text_area :content %>
</div>
<div id="search-result">
</div>
</div>
<div class="submit-post">
<%= f.submit "Send", class: "submit-btn" %>
</div>
<% end %>
This is the controller code.↓
class ArticlesController < ApplicationController
before_action :authenticate_user!, only: [:new, :create]
def index
#article = Article.all.order(created_at: :desc)
end
def new
#article = ArticlesTag.new
end
def create
#article = ArticlesTag.new(article_params)
if #article.valid?
#article.save
return redirect_to root_path
else
render :new
end
end
def search
return nil if params[:keyword] == ""
tag = Tag.where(['name LIKE ?', "%#{params[:keyword]}%"] )
render json:{ keyword: tag }
end
private
def article_params
params.require(:articles_tag).permit(:title, :content, :name).merge(user_id: current_user.id)
end
end
This is the code of the Article model.↓
class Article < ApplicationRecord
has_rich_text :content
belongs_to :user
has_one_attached :image
has_many :article_tags
has_many :tags, through: :article_tag_relations
end
This is the code of The Tag model. ↓
class Tag < ApplicationRecord
has_many :article_tag_relations
has_many :articles, through: :article_tag_relations
validates :name, uniqueness: true
end
This is the intermediate model between The Tag model and The Article model.↓
class ArticleTagRelation < ApplicationRecord
belongs_to :article
belongs_to :tag
end
This is the Form object class that collects the tags and articles tables.
class ArticlesTag
include ActiveModel::Model
attr_accessor :title, :name, :content, :user_id
with_options presence: true do
validates :title
validates :name
validates :content
validates :user_id
end
def save
article = Article.create(title: title, content: content, user_id: user_id)
tag = Tag.where(name: name).first_or_initialize
tag.save
ArticleTagRelation.create(article_id: article.id, tag_id: tag.id)
end
end
Database Status
Action text table
Article Tag Relation table
Article table
Tag table
Please help me.
A simple application that reproduces the error.
github URL
A simple app that reproduce the error
This is the error you are getting
undefined method `body' for "":String
That says that it is trying to call the method body on an empty string.
Why is it trying to call that method? Because you wrote this:
<%= f.rich_text_area :content %>
So the form helper is expecting that content contains an instance of a RichText (https://api.rubyonrails.org/classes/ActionText/RichText.html)
However, content actually just contains an instance of String, because your ArticlesTag model does not declare that it has_rich_text (like your Articles Model does)
I note that your ArticlesTag model is not persisted. I am not sure how to use rich text with a model that is not persisted - I suspect it might not be possible.

Store value from another table form in rails

I have a users table and games table. Games table has user_id. The help I want is how can I change/enter the value of city of birth from the game's form which is a field in the user table. I am using the try() method to display the value of city of birth from the user table in the game's form.
user.rb
has_many :games, dependent: :destroy
game.rb
belongs_to :user
_form.html.erb(game)
<div class="custom-hidden field">
<%= form.label :user_id %>
<%= form.number_field :user_id %>
</div>
<div class="field">
<%= form.label :city_of_birth, "User City of Birth" %>
<%= form.text_field :city_of_birth, :value => #user.try(:city_of_birth) %>
</div>
<div class="field">
<%= form.label :game_name %>
<%= form.text_field :game_name %>
</div>
I'm going to answer this question assuming that this form is meant to create a Game, which belongs_to :user, since there is a user_id in the form, and that user.city_of_birth is a string.
The traditional way to do this would be to use theaccepts_nested_attributes_for feature of Rails.
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
However, I would strongly suggest that you consider writing a form object to handle this, so that the responsibility for validating this mixed-model form is held cleanly in one place. I suggest using a gem like Reform to make this process easier.
# app/forms/games/new_game_form.rb
class Games::NewGameForm < Reform::Form
property :user_id, validates: { presence: true }
property :city_of_birth, virtual: true, validates: { presence: true }
property :game_name, validates: { presence: true }
def save
Game.transaction do
user.update!(city_of_birth: city_of_birth)
Game.create(user: user, game_name: game_name)
end
def user
#user ||= User.find(user_id)
end
end
This form object can then be used in place of the Game instance.
# GamesController
def new
#form = Games::NewGameForm.new(Game.new)
end
<!-- new.html.erb -->
<%= form_with #form, url: games_path, method: :post do |f| %>
<!-- your form fields -->
<% end %>
Please note that it appears very odd that you're accepting a user_id in the form, but also reading the city_of_birth from #user. If the #user for whom the game is being created is already known (perhaps the signed in user), then it's useless (and a security risk) to accept the ID in the form - you can simply use the same method that was used to set #user.

Rails nested form with unknown columns

I'm creating an admin interface where the admin (of a company) can add custom fields to their employees.
Example:
Models:
Employee: Basic info like name, contact info, etc (has_many employee_field_values)
EmployeeFields: These are the dynamic ones the admin can add (every company has different needs, it could be anything), lets say favorite_food
EmployeeFieldValues: The actual values based on the fields above, say pizza (belongs_to both models above)
What's a smart way of adding the EmployeeFieldValues fields while editing an employee?
I'm trying something simple like this, but not sure if I like it
# Controller
#custom_fields = EmployeeFields.all
# View
<%= form_for(#employee) do |f| %>
<%= f.text_field :first_name %>
<% #custom_fields.each do |custom_field| %>
<%= custom_field.name %>
<%= text_field_tag "employee_field_values[#{custom_field.name}]" %>
<% end %>
<%= f.submit :save %>
<% end %>
And then when updating, params[:employee_field_values] gives this:
<ActionController::Parameters {"favorite_food"=>"pizza"}>
So, not sure if this is a good direction, also I'm not sure how to handle future edits to an employee's custom_fields if they change.
I think it will be better to use EmployeeField as nested model and EmployeeFieldValue for select field.
For example:
Models
class Employee < ActiveRecord::Base
validates :name, presence: true
has_many :employee_field_values
accepts_nested_attributes_for :employee_field_values, reject_if: ->(x) { x[:value].blank? }
end
class EmployeeFieldValue < ActiveRecord::Base
belongs_to :employee
belongs_to :employee_field
end
class EmployeeField < ActiveRecord::Base
has_many :employee_field_values, inverse_of: :employee_field, dependent: :destroy
validates :title, presence: true, uniqueness: true
end
Controller
class EmployeesController < ApplicationController
def new
#employee = Employee.new
#employee.employee_field_values.build
end
end
View
= simple_form_for #employee, url: '/' do |f|
= f.input :name
= f.simple_fields_for :employee_field_values do |ff|
= ff.input :value
= ff.input :employee_field_id, collection: EmployeeField.all.map{|x| [x.title, x.id]}
Also you need to make buttons for adding/removing :employee_field_value, and you can do it with gem cocoon for example
OR you can build all objects in controller(for each EmployeeField) and do without select box

Rails4 Dynamic Select Dropdown

I am trying to set up some dynamic Dropdown Select Menus in a Search Form using form_tag. What I would like is similar functionality to the example found at Railcasts #88
Models:
class Count < ActiveRecord::Base
belongs_to :host
end
class Host < ActiveRecord::Base
belongs_to :site
has_many :counts
end
class Site < ActiveRecord::Base
belongs_to :state
has_many :hosts
end
class State < ActiveRecord::Base
has_many :sites
end
View:
<%= form_tag(counts_path, :method => "get", id: "search-form") do %>
<%= select_tag "state_id", options_from_collection_for_select(State.all.order(:name), :id, :name) %>
<%= select_tag "site_id", options_from_collection_for_select(Site.all.order(:name), :id, :name) %>
<% end %>
A State has_many Sites which has_many Hosts which has many Counts. Or conversely, Counts belong_to Host whichs belongs_to Site which belongs to State
So I would like to select a state from the States dropdown that would then "group" the Sites based on the state they associate through the Host.
I have struggled with this nested association and can't seem to figure out how build the grouped_collection_select.
I know I'm overlooking something obvious! Could sure use some pointers...
You can fire jquery-ajax request. Change event in first select box will call action on controller and called method will change the value of second dropdown through ajax call. Simple example:
In your view file:
<%= select_tag 'state_id', options_for_select(State.all.order(:name), :id, :name) %>
<%= select_tag "site_id", options_for_select(Site.all.order(:name), :id, :name) %>
In JS file of that controller:
$(document).on('ready page:load', function () {
$('#state_id').change(function(event){
$("#site_id").attr('disabled', 'disabled')
$.ajax({
type:'post',
url:'/NameOfController/NameOfMethod',
data:{ state_id: $(this).val() },
dataType:"script"
});
event.stopImmediatePropagation();
});
});
In NameOfController.rb
def NameOfMethod
##no need to write anything
end
In NameOfMethod.js.erb
<% if params[:state_id].present? %>
$("#site_id").html("<%= escape_javascript(render(partial: 'site_dropdown'))%>")
<% end %>
in _site_dropdown.html.erb file:
<% if params[:state_id].present? %>
<%= select_tag 'site_id', options_for_select(Site.where("state_id = ?", params[:state_id])) %>
<% else %>
<%= select_tag "site_id", options_for_select(Site.all.order(:name), :id, :name) %>
So it will change site dropdown based on selected state dropdown. You can go upto n number of level for searching. Good luck.

Creation form for models with polymorphism

I have a model 'item', two models 'bar' and 'restaurant' and a model 'user'.
The relations between those models are:
User has_many :bars and has_many :restaurants
Item belongs_to :activity, polymorphic: true
Bar has_many :items, as: :activity
Restaurant has_many :items, as: :activity
How my _form view to create a new item should be like?A user can create an item and assign it to a model that can be bar or restaurant, so i would like that user can choose in which activity the item should belongs to.
In my form i have something like <%= f.select :activity, #my_activities.collect { |a| [a.name, a.id] } %> but doesn't work.
For polymorphic associations you use a fields_for block:
<%= form_for(#bar) do |f| %>
Bar<br />
<%= select :id, options_for_select(#bars.map {|i| [i.name, i.id]}, include_blank: true) %><br />
New Item<br />
<%= f.fields_for :items do |a| %>
Kind: <%= a.select :kind, options_for_select(Item.kinds.keys.map.with_index {|k,i| [k, i]}) %><br /> <!-- # If you have an enum of kind for item -->
Vendor: <%= a.select :vendor_id, options_for_select(current_user.vendors.map {|i| [i.name, i.id]}) %><br /> <!-- # If you have specific vendors per user -->
Name: <%= a.text_field :name %><br />
<% end %>
<%= f.submit %>
<% end %>
This will go inside your form_for tag. Use fields_for block to nest any polymorphic relation.
You would only use select for an attribute that exists when creating something new. The example you've partly written out looks like you're simply selecting from existing items. That would be a different answer if you're looking for that. You've specifically asked about creating an item. So you won't be using select for creating something by name, you will need a text_field to enter the new name.
You can ignore the two select fields for your solution. They are there to demonstrate select. I don't believe your answer needs a select field.
https://gorails.com/episodes/forum-nested-attributes-and-fields-for
On another note your naming scheme for Item may be confusing. The standard naming would be more like.
class Item < ActiveRecord::Base
belongs_to :itemable, polymorphic: true
end
class Bar < ActiveRecord::Base
has_many :items, as: :itemable, dependent: :destroy
accepts_nested_attributes_for :items, reject_if: proc { |att| att['name'].blank? }
end
class Restaurant < ActiveRecord::Base
has_many :items, as: :itemable, dependent: :destroy
accepts_nested_attributes_for :items, reject_if: proc { |att| att['name'].blank? }
end
In this case you would use a fields_for on :items. The polymorphic relationship name activity or itemable is not referred to in the fields_for field. Rather it is the plural for Item so :items.
To answer:
i have a page to add an item, where the user fill informations like
title, description, etc, and then chooses in which 'bar' or
'restaurant' he wants to publish it.
<%= form_for(#item) do |f| %>
<%= f.select :itemable_type, options_for_select([Bar.name, Restaurant.name]) %>
<%= f.select :itemable_id, [1,2,3] %># COMPLICATION, NEED AJAX/JS TO GET AVAILABLE IDs
<%= f.text_field :name %>
<% end %>
Well this is basically what you want to do. But you would need to have an Ajax/JavaScript call to change the available options for :itemable_id to the list of either Bars or Restaurants mapped with [:name, :id]. You could just use a text field to input the number of the ID of the Bar/Restaurant but this is not a user friendly experience.
If I was proficient in JavaScript I could give you a way to do this. One way would be to have duplicate :itemable_id fields and have javascript disable/remove whichever the select field it isn't using.
Alternative solution 1:
You could make a page for each type new_bar_item.html.erb and new_restaurant_item.html.erb and each of those you would simply put a hidden_field for itemable_type to be either Bar.name or Restaurant.name respectively. And then you would already know which collection to give to your select field. Map the collections for the select field to your :name, :id. This removes all the complication for doing this.
A workable solution 2:
A good way I can recommend to do it without JavaScript is to have both Bars and Restaurants listed.
<%= form_tag(#item) do |f| %>
Choose either Bar or Restaurant.<br />
Bar: <%= select_tag 'item[bar_id]', options_for_select(#bars.map {|i| [i.name, i.id]}, include_blank: true) %><br />
Restaurant: <%= select_tag 'item[restaurant_id]', options_for_select(#restaurants.map {|i| [i.name, i.id]}, include_blank: true) %><br />
Item name: <%= text_field_tag 'item[name]' %>
<% end %>
Then in your ItemController you will need to write a method to check which field isn't blank and set the polymorphic type with that.
before_action :set_poly_by_params, only: [:create, :update]
private
def set_poly_by_params
if !params["item"]["bar_id"].empty? ^ !params["item"]["restaurant_id"].empty?
if !params["item"]["bar_id"].empty?
params["item"]["itemable_id"] = params["item"].delete("bar_id")
params["item"]["itemable_type"] = Bar.name
else
params["item"]["itemable_id"] = params["item"].delete("restaurant_id")
params["item"]["itemable_type"] = Restaurant.name
end
else
raise "some error about incorrect selection"
end
end
# NOTE: The above code will fail if the form doesn't submit both a bar_id field and a restaurant_id. It expects both, empty or not.
Solution 3 (revised #2)
<%= form_tag(#item) do |f| %>
Location: <%= select_tag 'item[venue]', options_for_select(
#bars.map {|i| [i.name, "b"+i.id.to_s]} +
#restaurants.map {|i| i.name, "r"+i.id.to_s]}
) %><br />
Item name: <%= text_field_tag 'item[name]' %>
<% end %>
We've added a b before the ID for bar or r before the ID for restaurant. Then we simply need to parse the params for it.
before_action :set_poly_by_params, only: [:create, :update]
private
def set_poly_by_params
if params["item"]["venue"]["b"]
params["item"]["itemable_type"] = Bar.name
else
params["item"]["itemable_type"] = Restaurant.name
end
params["item"]["itemable_id"] = params["item"].delete("venue")[1..-1]
end
This meets your requirement of one select field with both Bars and Restaurants in it.
I used part of the solutions posted by 6ft Dan.
in items_controller i created a
before_action :set_activity, only: [:create, :update]
def set_activity
#itemable = params["item"]["itemable"]
if params["item"]["itemable"]["bar"]
#activity = Bar.find(#itemable[3..-1])
elsif params["item"]["itemable"]["res"]
#activity = Restaurant.find(#itemable[3..-1])
end
end
and then in create action i added
#item.update_attribute(:itemable, #activity)
after the
#item = Item.new(item_params)
and in my form i have
<%= f.select :itemable, options_for_select(#bars.map {|i| [i.name, "bar"+i.id.to_s]} + #restaurants.map {|i| [i.name, "res"+i.id.to_s]}) %>
Now item creates and has an itemable attribute linking to the activity which it belongs!

Resources