I want to display belongs_to relationship columns in ransackable attributes list. So that I can display them in the dropdown, and perform an advanced search on the (joined) table.
How can I do that?
Below my model, where each manifest has one consignee. I've adjusted the attribute list, but when I select the consignee name it looks for 'manifest'.'name' and not in 'consignee'.'name' via a JOIN.
When I use the simple search form, it works correctly.
manifest.rb
class Manifest < ApplicationRecord
belongs_to :shipper
belongs_to :consignee
...
def self.ransackable_attributes(auth_object = nil)
super - ['id', 'created_at', 'updated_at', 'consignee_id']
super + ['consignee_name']
end
end
consignee.rb
class Consignee < ApplicationRecord
has_many :manifest, dependent: :destroy
...
end
manifest_controller.rb
...
def index
#search = ransack_params
#search.build_grouping unless #search.groupings.any?
#manifests = #search.result(distinct: true)
#search.build_condition
...
private
def ransack_params
Manifest.includes(:vessel, :pod, :pol, :por, :del, :consignee).ransack(params[:q])
end
end
index.html.erb
<%= search_form_for #search do |f| %>
<%= f.condition_fields do |c| %>
<div class="field">
<%= c.attribute_fields do |a| %>
<%= a.attribute_select %>
<% end %>
<%= c.predicate_select :only => [:cont, :not_cont, :matches]%>
<%= c.value_fields do |v| %>
<%= v.text_field :value %>
<% end %>
</div>
<% end %>
I expect to see Consignee Name in the dropdown list, but only see 'Name' at the bottom. When I select this and press search it returns with an error:
undefined method `type' for nil:NilClass
on line: Manifest.includes(:vessel, :pod, :pol, :por, :del, :consignee).ransack(params[:q])
manifest.rb (define ransackable_attributes only for this model)
class Manifest < ApplicationRecord
belongs_to :shipper
belongs_to :consignee
...
private
def self.ransackable_attributes(auth_object = nil)
super - ['id', 'created_at', 'updated_at', 'consignee_id']
end
end
consignee.rb (define ransackable_attributes for this model)
class Consignee < ApplicationRecord
has_many :manifest, dependent: :destroy
...
private
def self.ransackable_attributes(auth_object = nil)
if auth_object == :manifest_search
['name']
else
super
end
end
end
manifest_controller.rb
...
def index
#search = ransack_params
#search.build_grouping unless #search.groupings.any?
#manifests = #search.result(distinct: true)
#search.build_condition
...
private
def ransack_params
Manifest.ransack(params[:q], auth_object: :manifest_search)
end
index.html.erb (add the associations parameter to attribute_select)
<%= search_form_for #search do |f| %>
<%= f.condition_fields do |c| %>
<div class="field">
<%= c.attribute_fields do |a| %>
<%= a.attribute_select :associations => ["consignee"] %>
<% end %>
<%= c.predicate_select :only => [:cont, :not_cont, :matches]%>
<%= c.value_fields do |v| %>
<%= v.text_field :value %>
<% end %>
</div>
<% end %>
note that the auth_object conditional is optional, but it allows you to have different search pages with different attributes. For example, the consignee page could have its own search form showing all of consignee's attributes, while the manifest search shows only the consignee name as a searchable attribute.
I removed the includes to simplify the code; it's not required to make the search work. If you need those associations pre-loaded, you can put it back in.
Related
sorry it may be simple but it's making me bash my head against a wall!
I created my own searches model and controller and having trouble searching a belongs_to association. I am able to search for has_and_belongs_to_many associations fine but I get zero results for belong_to.
# models/user.rb
class User < ApplicationRecord
belongs_to :city
has_and_belongs_to_many :sports
has_and_belongs_to_many :goals
end
# models/search.rb
class Search < ApplicationRecord
def search_users
users = User.all
# These two work
users = users.joins(:sports).where("sports.name ILIKE ?", "%#{sports}%") if sports.present?
users = users.joins(:goals).where("goals.name ILIKE ?", "%#{goals}%") if goals.present?
# This doesn't
users = users.joins(:city).where("cities.name ILIKE ?", "%#{cities}") if cities.present?
return users
end
end
# searches_controller.rb
class SearchesController < ApplicationController
def new
#search = Search.new
#sports = Sport.uniq.pluck(:name)
#goals = Goal.uniq.pluck(:name)
#cities = City.uniq.pluck(:name)
end
def create
#search = Search.create(search_params)
redirect_to #search
end
def show
#search = Search.find(params[:id])
end
private
def search_params
params.require(:search).permit(:sports, :goals, :gyms, :cities)
end
end
and my views:
#searches/new.html.erb
<%= simple_form_for #search do |search| %>
<%= search.label :sports %>
<%= search.select :sports, options_for_select(#sports), include_blank: :true %>
<%= search.label :goals %>
<%= search.select :goals, options_for_select(#goals), include_blank: :true %>
<%= search.input :cities, collection: City.all.order(name: :asc), as: :select, label: "City" %>
<%= submit_tag "Search" %>
<% end %>
searches/show.html.erb
<% if #search.search_users.empty? %>
Nothing
<% else %>
<% #search.search_users.each do |user| %>
<%= user.first_name + ' ' + user.last_name %>
<% end %>
<% end %>
Basically when selecting a City from the dropdown list it doesn't register and gives me the empty search condition and I can't figure out why it won't select it. However selecting the sports and/or goals and it gives me the user matching those.
Thanks in advance!
EDIT:
Okay I've managed to get it working by simply changing my dropdown select in my view from:
<%= search.input :cities, collection: City.all.order(name: :asc), as: :select, label: "City" %>
to this:
<%= search.select :cities, options_for_select(#cities) %>
HOWEVER, I'd still like to know why the other way doesn't work?
<%= search.input :cities, collection: City.all.order(name: :asc), as: :select, label: "City" %>
One of the reasons the above code didn't work is,
City.all.order(name: :asc) return a ActiveRecord::Relation object, but a collection searches for an array or range.
Collections can be arrays or ranges. from the collection documentation
Another point from that documentaion is,
when a :collection is given the :select input will be rendered by default, so we don't need to pass the as: :select
So, change the input to
<%= search.input :cities, collection: City.uniq.pluck(:name).sort,label: "City" %>
I am trying to use fields_for and create a nested form, however only one text field shows up, blank. I have 3 crewmember records.
crewmember model:
class Crewmember < ActiveRecord::Base
belongs_to :production
belongs_to :callsheet
validates :firstname, presence: true
validates :email, presence: true
def name
"#{firstname} #{lastname}"
end
end
callsheet model
class Callsheet < ActiveRecord::Base
attr_accessible :crewmembers_params
has_many :castmembers
has_many :crewmembers
accepts_nested_attributes_for :crewmembers
end
callsheets controller
class CallsheetsController < ApplicationController
def index
#callsheets = Callsheet.all
#departments = Department.where(production_id: current_user.default_working_production_id)
end
def show
#callsheet = Callsheet.find(params[:id])
end
def new
#callsheet = Callsheet.new
#departments = Department.where(production_id: current_user.default_working_production_id)
end
def edit
#callsheet = Callsheet.find(params[:id])
end
def create
#callsheet = Callsheet.new(callsheets_params)
#Callsheet.production_id = current_user.default_working_production_id
if #callsheets.save
redirect_to callsheet_path
else
render 'new'
end
end
def update
#callsheet = Callsheet.find(params[:id])
if #callsheet.update(callsheets_params)
redirect_to callsheet_path, :notice => "callsheets successfully updated."
else
render 'edit', :notice => "callsheets not updated."
end
end
def destroy
#callsheet = Callsheet.find(params[:id])
#callsheet.destroy
redirect_to callsheets_path
end
private
def callsheets_params
params.require(:callsheet).permit(:crewmembers_params [:id, :firstname])
end
end
form for new callsheet:
<%= form_for #callsheet do |f| %>
<% if #callsheet.errors.any? %>
<div id="error_explanation" class="alert alert-danger">
<strong>
<%= pluralize(#callsheet.errors.count, "error") %> prohibited
this call sheet from being saved:
</strong>
<ul>
<% #callsheet.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.fields_for :crewmember do |crewmember| %>
<fieldset>
<%= crewmember.label :firstname, "First Name" %><br />
<%= crewmember.text_field :firstname %>
</fieldset>
<% end %>
<% end %>
You don't need attr_accessible (that's only for Rails 3).
You should also rename all your models to snake_case, referencing with CamelCase:
#app/models/call_sheet.rb
class CallSheet < ActiveRecord::Base
has_many :cast_members
has_many :crew_members
accepts_nested_attributes_for :crew_members
end
As is the custom with fields_for, you also need to build the associated objects (if you're creating a new record) (you don't need to do this if editing):
#app/controllers/call_sheets_controller.rb
class CallSheetsController < ApplicationController
before_action :set_departments
def new
#callsheet = Callsheet.new
#callsheet.crew_members.build
end
def edit
#callsheet = Callsheet.find params[:id]
end
def update
#callsheet = Callsheet.find params[:id]
#callsheet.update callsheet_params
end
private
def set_departments
#departments = Department.where(production_id: current_user.default_working_production_id)
end
def callsheet_params
params.require(:callsheet).permit(crew_members_attributes: [:id, :firstname])
end
end
This will allow you to use:
<%= form_for #callsheet do |f| %>
<%= f.fields_for :crew_members do |crewmember| %>
<%= crewmember.label :firstname, "First Name" %><br />
<%= crewmember.text_field :firstname %>
<% end %>
<%= f.submit %>
<% end %>
--
When passing nested attributes through fields_for, you need several components:
The correct association in your parent model
An instantiated version of the associated model (#parent.build_child)
Correct fields_for definition
Passing correct parameters through your controller
I've outlined how to achieve the above, all of which you had incorrect.
You can also declare multiple validations in the same call:
#app/models/crew_member.rb
class CrewMember < ActiveRecord::Base
validates :firstname, :email, presence: true
end
Try changing
<%= f.fields_for :crewmember do |crewmember| %>
into
<%= f.fields_for :crewmember, #callsheet.crewmember || #callsheet.build_crewmember do |crewmember| %>
I have a polymorphic association form and I'd like to build a nested form, but the fields are not showing up:
views/reviews/_form.html.erb:
<%= form_for [#reviewable, #review] do |f| %>
<%= f.fields_for :review_images do |i| %>
<%= i.file_field :image %>
<% end %>
<% end %>
review.rb:
class Review < ActiveRecord::Base
attr_accessible :review_styles_attributes
belongs_to :reviewable, polymorphic: true
has_many :review_styles
accepts_nested_attributes_for :review_images, allow_destroy: true
end
review_image.rb:
class ReviewStyle < ActiveRecord::Base
attr_accessible :review_id, :image
belongs_to :reviewable, polymorphic: true
belongs_to :review
end
reviews_controller.rb:
class ReviewsController < ApplicationController
before_filter :get_reviewable
def new
#review = #reviewable.reviews.new
#review_style = #review.build_review_style
3.times {#review.review_styles.new}
end
def edit
# not sure what goes here if I need to edit as well
end
private
def get_reviewable
#reviewable = params[:reviewable].classify.constantize.find(reviewable_id)
end
def reviewable_id
params[(params[:reviewable].singularize + "_id").to_sym]
end
end
I think your problem is here:
<%= f.fields_for :review_images do |i| %>
<%= i.file_field :image %>
<% end %>
From looking at your code, it should be:
#app/views/reviews/new.html.erb
<%= f.fields_for :review_styles do |i| %>
<%= i.file_field :image %>
<% end %>
#app/controllers/reviews_controller.rb
def new
#review = #reviewable.reviews.new
#review.review_styles.build
end
You should note when you're building associative values, you should use .build for plural / multiple associations, and build_ for singular
I have two resources: Recommendations and Ratings.
Recommendations Model:
class Recommendation < ActiveRecord::Base
has_many :ratings
accepts_nested_attributes_for :ratings
end
Ratings Model:
class Rating < ActiveRecord::Base
belongs_to :recommendation
end
Recommendations Controller:
class RecommendationsController < ApplicationController
def index
#product = Product.find(params["product_id"])
#recommendations = Recommendation.find(:all, :joins => :products, :conditions => ["product_id = ?", (params["product_id"])])
#recommendation = Recommendation.new
#rating = Rating.new
end
def create
#need to find_or_create new rating based on recommendation_id
end
end
Recommendations Index:
<div id ="prods_container">
<%= form_for #recommendation do |f| %>
<%= f.fields_for #rating do |r| %>
<% #recommendations.each do |rec| %>
<%= rec.title %>
<div id="rec_note_text"><%= r.text_field :notes %></div>
<% end %>
<%= f.submit %>
<% end %>
<% end %>
</div>
I am trying to add a notes field for each of the "recommendation" objects which I need to update the ratings table in my create action/recommendations controller. How do I pass the correct ID's to my controller to achieve this?
We have a user model which :has_one detail. In a form_for a user, I want a drop-down to select the user's details' time_zone.
I've tried
<% form_for #user do |f| %>
... user stuff ...
<%= f.select :"detail.time_zone", ...other args... %>
<% end %>
but I get a NoMethodError for detail.time_zone. What's the correct syntax for doing this - or if it's not possible to do it this way, how should I be doing it?
Don't forget to use accepts_nested_attributes_for in your user model:
class User < ActiveRecord::Base
has_one :detail
accepts_nested_attributes_for :detail
end
Detail model:
class Detail < ActiveRecord::Base
belongs_to :user
end
Users controller:
class UsersController < ActionController::Base
def new
#user = User.new
#user.build_detail
end
end
User new/edit view:
<% form_for #user do |u| %>
<% u.fields_for :detail do |d| %>
<%= d.select :country, Country.all.map { |c| [c.name, c.id] }
<%= d.time_zone_select :time_zone %>
<% end %>
<% end %>
Why is there a colon after f.select? Also it should be <%=... and not <%..
Assuming you have a 'time_zone' column in your table,
<% form_for #user do |f| %>
... user stuff ...
# args: user object, time_zone method, prioritizing zones(separating list), default
<%= f.time_zone_select :time_zone, nil, :default => "Pacific Time (US & Canada)", :include_blank => false %>
<% end %>
Additional resource :
http://railscasts.com/episodes/106-time-zones-in-rails-2-1
http://railsontherun.com/2007/12/21/timezone-selection/