I'm attempting to make an invoice application. Here are my models which are related to my question:
UPDATE: Model information has changed due to recent suggestions
Invoice
> id
> created_at
> sales_person_id
LineItem
> id
> invoice_id
> item_id
> qty_commit (inventory only)
> qty_sold
> price (because prices change)
> ...etc
Item
> barcode
> name
> price
> ...etc
Invoice has_many items, :through => :line_items. Ditto for Item. What I want to do is that when I create a new invoice, I'd like the form to be populated with all available Items. The only time I don't want all items to be populated is when I'm viewing the invoice (so only items which exist in the LineItems table should be retrieved). Currently - and obviously - a new Invoice has no items. How do I get them listed when there is nothing currently in the collection, and how do I populate the form? Also I'd like all products to be available when creation fails (along with what the user selected through the form).
UPDATE: I can create items through the controller via the following:
#invoice = Invoice.new
# Populate the invoice with all products so that they can be selected
Item.where("stock > ?", 0).each do |i|
#invoice.items.new(i.attributes)
end
This is of course my crude attempt at doing what I want. Visually it works out great, but as predicted my form id's and such are not playing well when I actually attempt to save the model.
LineItem(#37338684) expected, got Array(#2250012)
An example of the form:
# f is form_for
<% #invoice.items.group_by{|p| p.category}.each do |category, products| %>
<%= category.name %>
<%= f.fields_for :line_items do |line_item| %>
<% for p in products %>
<%= line_item.hidden_field :tax_included, :value => p.tax_included %>
<%= p.name %>
$<%= p.price %>
<% end %>
<% end %>
<% end %>
First of all, if you explicitly want to have a join model with additional attributes in it, you should use has_many :through instead of has_and_belongs_to_many. See the RoR Guide to the differences of the two.
Second, there is no single solution for what you want to reach. I see there two typical usages, depending on the mass of possible instances, one is better than the other:
Use radio buttons to select (and deselect) where a relation should be created or deleted. See the railscast #165 how to do part of that.
You could use select menus with a button to add a relation. See railscast #88. The added relation could be shown in a list, with a delete button nearby.
Use token fields (see railscast #258) to autocomplete multiple entries in one single text entry field.
In all the situations, you normally have to check at the end, if
a relation should be deleted
kept
or created
I hope some of the ideas may show you the right solution for your problem.
Related
I'm working on a Rails app and have made some initial migrations and associations. Here's the schema I currently have:
Current Schema
Right now, I'm not sure if this schema will actually work. I'm trying to include different data in my posts depending on the category (e.g. If the post is in the "Music" category, it will show the title of the record, along with the artists who created it). The way I have it now, some of the tables will have a hardcoded category_id (e.g. products, episodes, records).
If you're trying to include some data of a music record in your post that means that you need to create a relationships between the two tables. for example:
rails g migration add_record_references_to_posts record:references
then rake db:migrate
models/post.rb:
belongs_to :record
validates :band_type, presence: true, if: 'category.id == 2'
models/record.rb:
has_many :posts
This way you can choose from what record your post belongs to using a select (don't forget to add that in your form).
You will then be able to retrieve your record title from your post for example in your post show page you can do:
<% if #post.category.name == "Music" %>
<%= #post.record.title %>
<%= #post.record.name %>
<% end %>
In your post form you can use jquery to show or hide the inputs specific to the post category.
I however don't think that you need the category table at all. You'll be able to know what kind of post it is depending on the presence of the post relationship with other tables (records, episodes or products). For example if the post (the object not the model) belongs to a product, you will know that it's a product's post, so you can do for example:
<% if #post.record.present? %>
<%= #post.record.title %>
<%= #post.record.name %>
<% end %>
I have a model course which has_many subcategories. I want to build a page that shows courses grouped by their subcategory. So far, I have
#courses = Course.personal_enrichment.order('subcategory_id').page params[:page]
#courses_facet = #courses.group_by(&:subcategory_id)
which works fine, but I need to show the actual subcategory name in the view, not the number. I've seen some other answers about this type of thing, but most of them assume the attribute you're grouping by is already human readable. Maybe I'm missing something?
When rendering the view you can just access the referenced models' attributes. Since group_by returns a hash, you could do something like this:
<% #courses_facet.each do |subcategory_id, courses| %>
<% subcategory_name = courses.first.subcategory.name rescue nil %>
<label><%= subcategory_name %></label>
<% end%>
Unless relevant subcategory models are cached this will generate N+1 queries to fetch the subcategory names. One way to avoid that is to include subcategory records to the initial resultset.
#courses.includes(:subcategories).group_by(&:subcategory_id)
The title may be too general, but I hope someone can help me.
The scenario is the following:
- I have a model Types, TypesActivity and Activity (and the relation is many to many)
- After create the type (that only consists of a name), I then have to assign activities to that type and in order to do that I have in another view (form) 2 sections:
1) With a select_tag of the types (that shows the name)
2) A list of checkboxes that display all of the activities
I'm showing the checkboxes using this on the form view
_form.html.erb
<% #activities.each do |a|%>
<li><%= check_box_tag "act[]", a.id, false%> | <%= a.name%></li>
<%end%>
and in my controller
def create
params[:act].each do |a|
TypeActivity.create({:type_id => params[:resource][:type_id], :activity_id=>a})
end
redirect_to "somewhere"
end
And everything works fine, but I have a question about... how to update it?
Is this correct? (or is there another better way?)
def update
_v = TypeActivity.find(params[:id]).type_id
params[:act] do |a|
TypeActivity.update_attributes(:type_id=>_v,:activity_id=>a)
end
end
Also if I want to edit one TypeActivity (type_activities/edit/3) I wanted to show the checkboxes that were already selected, and I've done this
def edit
#t = TypeActivity.find(params[:id]).type_id
#activities = Activity.all
end
and in my view
_form.html.erb
<% #activities.each do |a|%>
<li><%= check_box_tag "act[]", a.id, !TypeActivity.where(:type_id=>#t,:activity_id=>a.id).empty?%> | <%= a.name%></li>
<%end%>
Is that a good way?
Thanks in advance to everyone that read all of it =)
JavierQQ
If the type_activity table only contains type_id and activity_id then you are better off not creating a TypeActivty model at all.
Treat type_activity as a link table and configure a has_and_belongs_to_many association in the Type and Activity models. Then use the TypesController to manage the assignment of Activities to a Type.
Reading Materials
Rails Guide on associations:
http://guides.rubyonrails.org/association_basics.html#the-has_and_belongs_to_many-association
Railscast #17 - HABTM Checkboxes:
http://railscasts.com/episodes/17-habtm-checkboxes
There are rfq, quote and test_item in our rails 3.1.0 app. RFQ has many quotes and test items. Quote belongs to a rfq and has many test items. Test item has many rfqs and quotes.
When creating a quote, the test items in the rfq are passed into the quote new form directly. Here is the code in new in quotes controller:
#quote = #rfq.quotes.new()
#quote.test_items << #rfq.test_items
Here is the view code in quote new form for displaying the test items passed from the rfq:
<%= simple_form_for([#rfq, #quote]) do |f| %>
....
<% #quote.test_items.each do |t| %>
<p><%= f.association :test_items, :label => false %><%= link_to_remove_fields "remove", f %></p>
<% end %>
....
<% end %>
The view code above can display the test item passed from the rfq and maintain the association (quote has many test items) which is what the app needs. However it also displays a selection box for test item which is not needed here(can't change the test item in quote other than delete. Also t in the loop hasn't been useful). What we need is only to display the name of the test items, maintain the association (quote has many test items) and allow to delete test item (done by link_to_remove_fields).
Is there a clean way to accomplish this? Thanks so much.
Take a look at the nested attributes mechanism in Rails. It requires both a model declaration, accepts_nested_attributes_for and is supported by the fields_for form helpers that are also compatible with simple_form's simple_fields_for.
I think your error is that you loop over the test_items explicitly and call f.assocation which tells the form that you'll want to choose a different test_item. I think what you're trying to do is to let the user remove test_items with a check box. You'll need to use fields_for to implicitly loop over the list of test_items and use nested_attributes to permit deletion.
I'm attempting to make an invoice application. Here are my models which are related to my question:
UPDATE: Model information has changed due to recent suggestions
Invoice
> id
> created_at
> sales_person_id
LineItem
> id
> invoice_id
> item_id
> qty_commit (inventory only)
> qty_sold
> price (because prices change)
> ...etc
Item
> barcode
> name
> price
> ...etc
Invoice has_many items, :through => :line_items. Ditto for Item. What I want to do is that when I create a new invoice, I'd like the form to be populated with all available Items. The only time I don't want all items to be populated is when I'm viewing the invoice (so only items which exist in the LineItems table should be retrieved). Currently - and obviously - a new Invoice has no items. How do I get them listed when there is nothing currently in the collection, and how do I populate the form? Also I'd like all products to be available when creation fails (along with what the user selected through the form).
UPDATE: I can create items through the controller via the following:
#invoice = Invoice.new
# Populate the invoice with all products so that they can be selected
Item.where("stock > ?", 0).each do |i|
#invoice.items.new(i.attributes)
end
This is of course my crude attempt at doing what I want. Visually it works out great, but as predicted my form id's and such are not playing well when I actually attempt to save the model.
LineItem(#37338684) expected, got Array(#2250012)
An example of the form:
# f is form_for
<% #invoice.items.group_by{|p| p.category}.each do |category, products| %>
<%= category.name %>
<%= f.fields_for :line_items do |line_item| %>
<% for p in products %>
<%= line_item.hidden_field :tax_included, :value => p.tax_included %>
<%= p.name %>
$<%= p.price %>
<% end %>
<% end %>
<% end %>
First of all, if you explicitly want to have a join model with additional attributes in it, you should use has_many :through instead of has_and_belongs_to_many. See the RoR Guide to the differences of the two.
Second, there is no single solution for what you want to reach. I see there two typical usages, depending on the mass of possible instances, one is better than the other:
Use radio buttons to select (and deselect) where a relation should be created or deleted. See the railscast #165 how to do part of that.
You could use select menus with a button to add a relation. See railscast #88. The added relation could be shown in a list, with a delete button nearby.
Use token fields (see railscast #258) to autocomplete multiple entries in one single text entry field.
In all the situations, you normally have to check at the end, if
a relation should be deleted
kept
or created
I hope some of the ideas may show you the right solution for your problem.