Using awesome_nested_set in a nested form - ruby-on-rails-3

I'm using Rails 3.0.7 with awesome_nested_set and I'm trying to create a nested form which will allow me to enter a category and sub categories all in one create form.
Here is my category model, controller & form
category.rb
class Category < ActiveRecord::Base
acts_as_nested_set
end
categories_controller.rb
class CategoriesController < InheritedResources::Base
def new
#category = Category.new
3.times { #category.children.build(:name => "test") }
end
end
form
= form_for #category do |f|
-if #category.errors.any?
#error_explanation
%h2= "#{pluralize(#category.errors.count, "error")} prohibited this category from being saved:"
%ul
- #category.errors.full_messages.each do |msg|
%li= msg
.field
= f.label :name
= f.text_field :name
%p Sub categories
= f.fields_for :children do |child|
= child.text_field :name
.actions
= f.submit 'Save'
The problem here is that I only end up with one sub category in my form and it doesn't have name set to 'test' so I don't believe that it's actually the child of the category showing here.
What am I missing here?
Can this be done?
Is there an easier way?
Update
If I change my form to the following then it displays three sub categories each with name set to 'test'. This will not save correctly though.
%p Sub categories
- #category.children.each do |sub|
= f.fields_for sub do |child|
= child.label :name
= child.text_field :name
%br

Found my answer and wrote a wiki page about it here:
https://github.com/collectiveidea/awesome_nested_set/wiki/nested-form-for-nested-set

Related

Rails form - search engine

I try to create simple search engine but I meet some problmes. I have several search_field in my form and if either is empty should returns all objects. Otherwise when it has any content it should be selected by that content. Below is my sample form:
<%= form_for :product, url: products_path, method: :get do |form| %>
<%= form.search_field :brand %>
<%= form.search_field :model %>
<%= form.search_field :price_from %>
<%= form.search_field :price_to %>
<%= form.submit 'Submit' %>
<% end %>
my model method:
def self.search(search)
where(brand: search[:brand]).where(model: search[:model]).where("price >= ?", search[:price_from]).where("price <= ?", search[:price_to])
end
But the above piece of code is wrong because if I leave some field empty it is treated directly as empty string instead of ignore this field and final result is not correct.
Summary this form should work similarly to filter on online store
You'd could do something like this
def self.search(search)
results = all
results = results.where(brand: search[:brand]) if search[:brand]
results = results.where(model: search[:model]) if search[:model]
results = results.where("price >= ?", search[:price_from]) if search[:price_from]
results = results.where("price <= ?", search[:price_to]) if search[:price_to]
return results
end
Good luck.

comma separated string with Advance Search

I've added an Advance Search to my Book App using this tutorial. Everything works fine, but now I am trying to find a Book by its Tags.
I got the advance search to work if the user enters one Tag into the :keywords text_field.
Is there a way to search various tags by splitting the keyword string with commas?
(ex: fun, kid stories, action)
Would allow me to search books with fun OR kids stories OR actions.
How can I search multiple tags via a comma separated string?
Note: I created a search method that I think could help, but I am not sure how to combine it with the single keyword search.
MODEL
class Book < ActiveRecord::Base
has_many :book_mappings
has_many :tags, through: :book_mappings
end
class BookMapping < ActiveRecord::Base
belongs_to :book
belongs_to :tag
end
class Tag < ActiveRecord::Base
has_many :book_mappings
has_many :books, through: :book_mappings
end
class Search < ActiveRecord::Base
def books
#books ||= find_books
end
def find_books
books = Book.order(:name)
###This works for a single word but NOT if I have multiple tags separated by commas
books = books.joins(:tags).where("tags.name like ?", "%#{keywords}%") if keywords.present?
books
end
def search(keywords)
return [] if keywords.blank?
cond_text = keywords.split(', ').map{|w| "name LIKE ? "}.join(" OR ")
cond_values = keywords.split(', ').map{|w| "%#{w}%"}
all(:conditions => (keywords ? [cond_text, *cond_values] : []))
end
end
VIEWS
<%= form_for #search do |f| %>
<div class="field">
<%= f.label :keywords %><br />
<%= f.text_field :keywords %>
</div>
<% end %>
Here is a simple solution. Just add a like statement for each keyword.
To filter books with all the tags
if keywords.present?
books = books.joins(:tags)
keywords.tr(' ','').split(',').each do |keyword|
books = books.where("tags.name like ?", "%#{keyword}%")
end
end
To filter books with any of the tags
if keywords.present?
books = books.joins(:tags)
keyword_names = keywords.split(', ')
cond_text = keyword_names.map{|w| "tags.name like ?"}.join(" OR ")
cond_values = keyword_names.map{|w| "%#{w}%"}
books = books.where(cond_text, *cond_values)
end

Rails sorting associations with Ransack

first time poster. I am trying to sort a table of users using the Ransack gem and Kaminari for pagination. When I use name, id, etc. sorting works but when I try an association with posts_count, sorting breaks and won't work. Note: in the view, 'u.posts.count' work correctly. I have tried custom scopes in the users model, and creating custom objects for the search params but nothing seems to work. I think I am having trouble either in the default scope or the #search object not having the data. Need help!
Here are some relevant snippets:
models/user.rb
has_many :posts, :dependent => :destroy
models/post.rb
belongs_to :user
default_scope :order => 'post.created_at DESC'
controllers/users_controller.rb
def index
#title = "User Index"
#search = User.search(params[:q]) # Ransack
#total_users = User.all.count
# .per(10) is the per page for pagination (Kaminari).
#users = #search.result.order("updated_at DESC").page(params[:page]).per(10) #This displays the users that are in the search criteria, paginated.
end
views/users/index.html.erb
..
<%= sort_link #search, :posts_count, "No. of Posts" %> #Sort links at column headers
..
<% #users.each do |u| %> #Display everything in the table
<%= u.posts.count %>
<% end %>
You can add a scope to your User model:
def self.with_posts
joins(:posts).group('posts.id').select('users.*, count(posts.id) as posts_count')
end
and use it like this:
#search = User.with_posts.search(params[:q]) # Ransack
then, you can treat posts_count like any other attribute.
I found a solution:
Controller:
def index
sql = "users.*, (select count(posts.id) from posts\
where posts.user_id = users.id) as count"
#search = User.select(sql).search(params[:q])
if params[:q] && params[:q][:s].include?('count')
#users = #search.result.order(params[:q][:s])
else
#users = #search.result
end
.......
end
View:
<th><%= sort_link #search, :count, "posts count" %></th>

form_for non-AR model - fields_for Array attribute doesn't iterate

I'm having trouble getting fields_for to work on an Array attribute of a non-ActiveRecord model.
Distilled down, I have to following:
models/parent.rb
class Parent
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
extend ActiveModel::Translation
attr_accessor :bars
end
controllers/parent_controller.rb
def new_parent
#parent = Parent.new
#parent.bars = ["hello", "world"]
render 'new_parent'
end
views/new_parent.html.haml
= form_for #parent, :url => new_parent_path do |f|
= f.fields_for :bars, #parent.bars do |r|
= r.object.inspect
With the code as above, my page contains ["hello", "world"] - that is, the result of inspect called on the Array assigned to bars. (With #parent.bars omitted from the fields_for line, I just get nil displayed).
How can I make fields_for behave as for an AR association - that is, perform the code in the block once for each member of my bars array?
I think the correct technique is:
= form_for #parent, :url => new_parent_path do |f|
- #parent.bars.each do |bar|
= f.fields_for "bars[]", bar do |r|
= r.object.inspect
Quite why it can't be made to Just Work I'm not sure, but this seems to do the trick.
I think that it can be done without the need of each:
= form_for #parent, :url => new_parent_path do |f|
= f.fields_for :bars do |r|
= r.object.inspect
You need to set some methods that are expected in the parent class to identify the collection.
class Parent
def bars_attributes= attributes
end
end
And you also will need to make sure that the objects in the array respond to persisted (so you cannot use strings) :(
I ditched the fields_for and added multiple: true
= form_for #parent, :url => new_parent_path do |f|
- #parent.bars.each_with_index do |bar, i|
= f.text_field :bars, value: bar, multiple: true, id: "bar#{i}"

Rails HABTM fields_for – check if record with same name already exists

I have a HABTM-relation between the models "Snippets" and "Tags". Currently, when i save a snippet with a few tags, every tag is saved as a new record.
Now i want to check if a tag with the same name already exists and if that´s the case, i don´t want a new record, only an entry in snippets_tags to the existing record.
How can i do this?
snippet.rb:
class Snippet < ActiveRecord::Base
accepts_nested_attributes_for :tags, :allow_destroy => true, :reject_if => lambda { |a| a.values.all?(&:blank?) }
...
end
_snippet.html.erb:
<% f.fields_for :tags do |tag_form| %>
<span class="fields">
<%= tag_form.text_field :name, :class => 'tag' %>
<%= tag_form.hidden_field :_destroy %>
</span>
<% end %>
Ok, i´m impatient… after a while i found a solution that works for me. I don´t know if this is the best way, but i want to show it though.
I had to modify the solution of Ryan Bates Railscast "Auto-Complete Association", which handles a belongs_to-association to get it working with HABTM.
In my snippet-form is a new text field named tag_names, which expects a comma-separated list of tags.
Like Ryan, i use a virtual attribute to get and set the tags. I think the rest is self-explanatory, so here´s the code.
View "_snippet.html.erb"
<div class="float tags">
<%= f.label :tag_names, "Tags" %>
<%= f.text_field :tag_names %>
</div>
Model "snippet.rb":
def tag_names
# Get all related Tags as comma-separated list
tag_list = []
tags.each do |tag|
tag_list << tag.name
end
tag_list.join(', ')
end
def tag_names=(names)
# Delete tag-relations
self.tags.delete_all
# Split comma-separated list
names = names.split(', ')
# Run through each tag
names.each do |name|
tag = Tag.find_by_name(name)
if tag
# If the tag already exists, create only join-model
self.tags << tag
else
# New tag, save it and create join-model
tag = self.tags.new(:name => name)
if tag.save
self.tags << tag
end
end
end
end
This is just the basic code, not very well tested and in need of improvement, but it seemingly works and i´m happy to have a solution!