Searchlogic and :has_many, :through => - ruby-on-rails

I'm using Searchlogic to search on many fields in a database. One of those fields is a :has_may, :through => relationship, and I can't get it to work.
Here are the relevant parts of the models:
Source.rb:
class Source < ActiveRecord::Base
has_many :authorships
has_many :authors, :through => :authorships
end
Authorship.rb:
class Authorship < ActiveRecord::Base
belongs_to :source
belongs_to :author
end
Author.rb:
class Author < ActiveRecord::Base
has_many :authorships
has_many :sources, :through => :authorships
end
Then, in my view, I have:
<% form_for #search do |f| %>
<fieldset><legend>Search the Online Medieval Sources Bibliography</legend>
<% f.fields_for #search.conditions do |sources| %>
<%= sources.hidden_field :live_not_null %>
<p>
Text Name:
<%= sources.text_field :text_name_like, :size => 95 %> <br />
<% sources.fields_for :author do |authors| %>
Medieval Author:
<%= authors.text_field :name_like, :size => 90 %>
<% end %> <br />
Modern Editor/Translator:
<%= sources.text_field :editor_like, :size => 80 %> <br />
</p>
<% end %>
</fieldset>
<p>
<%= f.submit "Search" %>
</p>
<% end %>
The search page loads just fine, but hitting the "submit" button gives me the following error:
Searchlogic::Search::UnknownConditionError in SourcesController#index
The author is not a valid condition. You may only use conditions that map to a named scope
Here is the code from SourcesController:
class SourcesController < ApplicationController
def index
query = if params[:search] then
params[:search][:hash]
end
#search = Source.search(query)
#sources = #search.all
end
And here are the parameters:
Parameters: {"commit"=>"Search", "search"=>{"hash"=>{"text_name_like"=>"canterbury", "date_begin_greater_than_or_equal"=>"", "author"=>{"name_like"=>""}, "editor_like"=>"", "link_not_blank"=>"0", "trans_none_not_null"=>"0", "trans_other_not_null"=>"0", "trans_english_not_null"=>"0", "trans_french_not_null"=>"0", "region_like"=>"", "live_not_null"=>"", "app_facsimile_not_null"=>"0", "date_end_less_than_or_equal"=>""}, "order"=>""}}
Does anyone have any ideas about what is happening here? Do you need to see more of the error message?
Many thanks!

I too have come across this limitation on searchlogic. there is a 'hack' of a solution that i got. its simple really.
search for items you looking for on the join model. this will give you the ids of the models that have that search.
this is a bit quirky but its the closest solution i could offer you.
hope this helps.
:)

Related

Rails: Passing nested attribute parameters

So im working through the Odin Project's "Flight Booker" project. https://www.theodinproject.com/courses/ruby-on-rails/lessons/building-advanced-forms. Which essentially is what it sounds like and im running into a problem with passing nested attributes.
First and foremost the Relevant Models:
class Booking < ApplicationRecord
belongs_to :passenger
belongs_to :flight
accepts_nested_attributes_for :passenger
end
class Flight < ApplicationRecord
has_many :bookings, dependent: :destroy
has_many :passengers, through: :bookings
belongs_to :to_airport, class_name: 'Airport', foreign_key: 'origin_id'
belongs_to :from_airport, class_name: 'Airport', foreign_key: 'destination_id'
end
class Passenger < ApplicationRecord
has_many :bookings, dependent: :destroy
has_many :flights, through: :bookings
end
The passenger schema just contains an email and name for right now. But the problem is when I pass the information to the "booking" controller. Here is my "New" form for booking.
<%= form_for #booking do |f| %>
<%= f.hidden_field :flight_id, value: params[:booking][:flight_num] %>
<%= f.hidden_field :passengers_num, value: params[:booking][:passengers_num] %>
<% params[:booking][:passengers_num].to_i.times do |passenger| %>
<%= fields_for :passenger do |passenger| %>
<%= passenger.label :name, 'Name', class: "Label" %>
<%= passenger.text_field :name %>
<%= passenger.label :email, 'email', class: "Label" %>
<%= passenger.email_field :email %>
<% end %>
<% end %>
<%= f.submit "Book Flight" %>
<% end %>
(Ignore the hidden fields for now, they are passed from the "Flights" search page and Im getting those just fine.)
So I am getting the multiple forms (name and email fields) but when I "Submit" I am only getting parameters for the last field sets. (So if there are 3 sets of name/email fields, I only get parameters for the last one).
It's possible im not understanding the fields_for however as I can't find a ton of good examples.
Thanks!
There could be many issues with your implementation...I'll layout a few...
Move <% params[:booking][:passengers_num].to_i.times do |passenger| %> logic into the new action of your bookings controller...ie
def new
#booking = Booking.new
3.times { #booking.passengers.new } # or whatever your logic is to display x amount of passenger fields
end
Make sure that in your bookings controller you are permitting the nested attributes like this...
params.require(:booking).permit(passengers_attributes: [:name, :email])
As far as the form, you'll need to treat it like a form within a form (makes sense...nested attributes created from a nested form!) and use the block variable...like this
<ul>
<%= f.fields_for :passengers do |passenger_form| %>
<li>
<%= passenger_form.label :name
<%= passenger_form.text_field :name %>
</li>
<!-- other permitted fields -->
<% end %>
</ul>

Create 2 types of resources using the same view

I have 2 models (and resources) - Institute and Admin.
I want to have a view with 1 submit button that creates 2 types of resources. Would I need to have 2 separate forms? An example would be great!
Also, what naming convention should this view use (given that it creates 2 types resources).
There is a "has-many through" association between Institute and Admin.
What you want is a design pattern called Form Object.
https://robots.thoughtbot.com/activemodel-form-objects
With a Form Object, you can create a class that represents the form, validate the data and then persist to the resource (or resources) that you need.
There's also a gem called Virtus for that. For me, it's a overkill if what you want is simple. You could just create a ActiveModel model and do your stuff.
Would I need to have 2 separate forms?
Answer is Non. you can make one form nested.
So example : Gessing your "has many through" association like this: One institue has many admins throuth mettings
Models :
class Institute < ActiveRecord::Base
has_many :mettings
has_many :admins, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Admin < ActiveRecord::Base
has_many :mettings
has_many :institues, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Metting < ActiveRecord::Base
belongs_to :institue
belongs_to :admin
accepts_nested_attributes_for :institues
end
Controller :
def new
#institue= Institue.new
#metting= #institue.mettings.build
#admin = #metting.build_admin
end
def create
Institue.new(institue_params)
end
def institue_params
params.require(:institue).permit(:id, mettings_attributes: [:id, :metting_time, admin_attributes: [:id ] )
end
Views can be called _form.erb.rb included in edit.erb.rb:
<% form_for(#institue) do |institue_form| %>
<%= institue_form.error_messages %>
<p>
<%= institue_form.label :name, "Institue Name" %>
<%= institue_form.text_field :name %>
</p>
<% institue_form.fields_for :mettings do |metting_form| %>
<p>
<%= metting_form.label :metting_date, "Metting Date" %>
<%= metting_form.date_field :metting_date %>
</p>
<% metting_form.fields_for :admin do |admin_form| %>
<p>
<%= admin_form.label :name, "Admin Name" %>
<%= admin_form.text_field :name %>
</p>
<% end %>
<% end %>
<p>
<%= institue_form.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', institues_path %>

Refactoring view loops to use fewer queries via includes with a HABTM relationship

I have been trying to refactor this code to reduce the db calls by possibly using "includes". I would like to replace the three nested loops in the view. Tried various options but got stuck... I'm still getting familiar with active record querying.
How can I make this more efficient with less queries?
Is using includes the best option?
If so, how do I access the various fields through my HABTM relationships?
Thanks.
Models:
class Category < ActiveRecord::Base
has_many :pub_types
end
class PubType < ActiveRecord::Base
belongs_to :category
has_and_belongs_to_many :issues
end
class Issue < ActiveRecord::Base
has_and_belongs_to_many :pub_types
has_many :images, :dependent => :destroy
end
Controller:
def home
#categories = Category.all
#issues_and_pubs = Issue.joins(:pub_types).uniq
end
View:
<% #categories.each do |category| %>
<%= category.name %>
<% #issues_and_pubs.where(:pub_types => {:category_id => ["#{category.id}"]}).each do |issue| %>
<% issue.images.each do |img| %>
<% if img.featured == true %>
<%= cl_image_tag img.image, :width => 295, :height => 155, :alt => img.name, :crop => :fill %>
<%= link_to issue.name, issue %>
<% end %>
<% end %>
<%= issue.issue_date.try(:strftime, "%B %d, %Y") %>
<%= issue.pub_types.map(&:name).join(", ") %>
<% end %>
<% end %>
Try this instead.
Add this to Category:
has_many :issues, through: :pub_types
Controller:
#categories = Category.includes(:issues => :images)
View:
#categories.each do |category|
category.issues.each do |issue|
issue.images.each do |image|
issue.pub_types # might still result in N+1, haven't tested
On the note above about N+1 on pub_types, I have had occasions where I've eager loaded associations, but Rails has not taken them into account when calling from children to parents. One approach I've used in the past is to be explicit with the references:
Category.includes(:issues => [:pub_types, :images])
Without the has_many through:, this would look rightly peculiar:
Category.includes(:pub_types => [:issues => [:pub_types, :images]])

Correct way to nest form for join model?

I'm trying to nest a form for my Producttracklisting has_many through join model in my product show view. What is the correct way to do this? I'm getting various errors for my various failed attempts.
The models are as follows:
class Product < ActiveRecord::Base
has_many :producttracklistings
has_many :tracks, :through => :producttracklistings
end
class Track < ActiveRecord::Base
has_many :producttracklistings
has_many :products, :through => :producttracklistings
end
class Producttracklisting < ActiveRecord::Base
belongs_to :product
belongs_to :track
end
The form is as follows:
<%= form_for(#producttracklisting) do |f| %>
<%= f.label :track_id %>
<%= f.text_field :track_id %>
<%= f.label :product_id %>
<%= f.text_field :product_id %>
<%= f.submit %>
<% end %>
And i'm trying to bring this into product/show using:
<%= render 'producttracklistings/form' %>
With all of the above I get a "undefined method `model_name' for NilClass:Class"
Thanks in advance.
What you're looking for is accepts_nested_attributes in combination with fields_for.
See this RailsCast Part 1 and part 2 for a detailed tutorial.
PS:
I'd suggest to name your model ProductTrackListing, which results in a table named product_track_listings. This is far more readable and "the rails way"

nil object in view when building objects on two different associations

I'm relatively new to Ruby on Rails so please don't mind my newbie level!
I have following models:
class Paintingdescription < ActiveRecord::Base
belongs_to :paintings
belongs_to :languages
end
class Paintingtitle < ActiveRecord::Base
belongs_to :paintings
belongs_to :languages
end
class Painting < ActiveRecord::Base
has_many :paintingtitles, :dependent => :destroy
has_many :paintingdescriptions, :dependent => :destroy
has_many :languages, :through => :paintingdescriptions
has_many :languages, :through => :paintingtitles
end
class Language < ActiveRecord::Base
has_many :paintingtitles, :dependent => :nullify
has_many :paintingdescriptions, :dependent => :nullify
has_many :paintings, :through => :paintingtitles
has_many :paintings, :through => :paintingdescriptions
end
In my painting new/edit view, I would like to show the painting details, together with its title and description in each of the languages, so I can store the translation of those field.
In order to build the languagetitle and languagedescription records for my painting and each of the languages, I wrote following code in the new method of my Paintings_controller.rb:
#temp_languages = #languages
#languages.size.times{#painting.paintingtitles.build}
#painting.paintingtitles.each do |paintingtitle|
paintingtitle.language_id = #temp_languages[0].id
#temp_languages.slice!(0)
end
#temp_languages = #languages
#languages.size.times{#painting.paintingdescriptions.build}
#painting.paintingdescriptions.each do |paintingdescription|
paintingdescription.language_id = #temp_languages[0].id
#temp_languages.slice!(0)
end
In form partial which I call in the new/edit view, I have
<% form_for #painting, :html => { :multipart => true} do |f| %>
...
<% languages.each do |language| %>
<p>
<%= label language, language.name %>
<% paintingtitle = #painting.paintingtitles[counter] %>
<% new_or_existing = paintingtitle.new_record? ? 'new' : 'new' %>
<% prefix = "painting[#{new_or_existing}_title_attributes][]" %>
<% fields_for prefix, paintingtitle do |paintingtitle_form| %>
<%= paintingtitle_form.hidden_field :language_id%>
<%= f.label :title %><br />
<%= paintingtitle_form.text_field :title%>
<% end %>
<% paintingdescription = #painting.paintingdescriptions[counter] %>
<% new_or_existing = paintingdescription.new_record? ? 'new' : 'new' %>
<% prefix = "painting[#{new_or_existing}_title_attributes][]" %>
<% fields_for prefix, paintingdescription do |paintingdescription_form| %>
<%= paintingdescription_form.hidden_field :language_id%>
<%= f.label :description %><br />
<%= paintingdescription_form.text_field :description %>
<% end %>
</p>
<% counter += 1 %>
<% end %>
...
<% end %>
But, when running the code, ruby encounters a nil object when evaluating paintingdescription.new_record?:
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.new_record?
However, if I change the order in which I
a) build the paintingtitles and painting descriptions in the paintings_controller new method and
b) show the paintingtitles and painting descriptions in the form partial
then I get the nil on the paintingtitles.new_record? call.
I always get the nil for the objects I build in second place. The ones I build first aren't nil in my view.
Is it possible that I cannot build objects for 2 different associations at the same time? Or am I missing something else?
Thanks in advance!
Actually I found a pretty simple solution. I provide a hash with values for the language ids when building the records.
#languages = Language.all
#languages.each do |language|
#painting.paintingtitles.build( {:language_id => language.id} )
#painting.paintingdescriptions.build( {:language_id => language.id} )
end

Resources