Rails 4 Nested Form - ruby-on-rails

I have three models:
class Item < ActiveRecord::Base
has_many :item_points
has_many :groups, through: :item_points
accepts_nested_attributes_for :item_points
end
class ItemPoint < ActiveRecord::Base
belongs_to :group
belongs_to :item
accepts_nested_attributes_for :group
end
class Group < ActiveRecord::Base
has_many :item_points
has_many :items, through: :item_points
end
The schema for items
create_table "items", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The schema for item_points
create_table "item_points", force: :cascade do |t|
t.decimal "points", null: false
t.integer "item_id", null: false
t.integer "group_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The schema for groups
create_table "groups", force: :cascade do |t|
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
In my groups table, I've created a number of rows, e.g. group 1 and group 2.
In the form for creating items I'd really like to see a field each for group 1 and group 2, so that I might be able to enter the points for that item. e.g. In item X, group 1 is worth 10 points, and group 2 is worth 5 points.
EDIT Added the form
The form:
<%= form_for(#item) do |item_form| %>
<div class="form-group">
<%= item_form.label :name %>
<%= item_form.text_field :name, :class => 'form-control' %>
</div>
<%= item_form.fields_for(:groups) do |groups_form| %>
<% group = groups_form.object_id.to_s%>
<%= groups_form.hidden_field :id %>
<%= groups_form.fields_for(:item_point) do |entity_form| %>
<%= entity_form.text_field :points %>
<% end %>
<% end %>
This provides me with a form, which contains one extra entry box, called item[groups][item_point][points], and has no label.
What I'd like to know is how do I get all of the rows I've added into groups as fields into a Rails Form? And when I do, how do I save the associated item_points data using strong parameters?
I've spent quite some time looking for an answer, and I can't seem to find anything other than a series of StackOverflow questions, which don't quite seem to have the same problem as me.
All help is wonderfully appreciated.

There's a helpful post with some examples at: https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through. It specifically talks about adding inverse_of on your associations.
In your view you'll need to use fields_for. You could (for example) put that in a table or a list and have each entry be a row.
If you share what you've tried in your view so far you may be able to get a more detailed response if you need it.
As for permitted_params in your controller, you can nest them something like:
def permitted_params
params.permit(item: [
:name,
item_points_attributes: [
:id,
:points,
:group_id,
]
)
end
Update:
Rather than fields_for(:groups) I think you want your controller to build the models for all the item_points (#item_points = Group.all.collect {|group| ItemPoint.new({group_id: group.id, item_id: #item.id}).
Then you can use a fields_for(:item_points, #item_points).
You can add a label for the field so it's not just an unlabeled field using the HTML label tag.

Related

Multiple Checkbox in Ruby

I'm trying to create an "ingredient" checkbox list derived from my "recipes", I'd like for the values to be saved in the database so that when it's checked and I refresh the page, it still shows as checked.
The error says "uninitialized constant #Class:0x00007f8f2d360830::Parties"
Here's an example of what i am trying to do
Controller:
# parties_controller.rb
def ingredients
#party = Party.find(params[:party_id])
#party_recipe = #party.recipes
#party_recipe.each do |recipe|
#ingredients = recipe.ingredients
end
The models:
Party model
#party.rb
class Party < ApplicationRecord
has_many :party_recipes
has_many :recipes, through: :party_recipes
end
Recipe model
#recipe_ingredient.rb
class RecipeIngredient < ApplicationRecord
belongs_to :recipe
belongs_to :ingredient
end
Ingredient model
#ingredient.rb
class Ingredient < ApplicationRecord
has_many :recipe_ingredients
has_many :recipes, through: :recipe_ingredients
end
Form:
#ingredients.html.erb
<% form_for "/parties/#{#party.id}/ingredients" do |f| %>
<% Parties::Recipes::Ingredients.each do |ingredient| %>
<%= check_box_tag(ingredient) %>
<%= ingredient %>
<% end %>
<% end %>
Schema:
create_table "ingredients", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "parties", force: :cascade do |t|
t.string "title"
t.string "address"
t.bigint "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "theme"
t.date "date"
t.integer "attendancy"
t.integer "appetizers"
t.integer "mains"
t.integer "desserts"
t.string "status", default: "pending"
t.index ["user_id"], name: "index_parties_on_user_id"
end
create_table "party_recipes", force: :cascade do |t|
t.bigint "recipe_id", null: false
t.bigint "party_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["party_id"], name: "index_party_recipes_on_party_id"
t.index ["recipe_id"], name: "index_party_recipes_on_recipe_id"
end
create_table "recipe_ingredients", force: :cascade do |t|
t.bigint "recipe_id", null: false
t.bigint "ingredient_id", null: false
t.string "amount"
t.boolean "included", default: false
t.index ["ingredient_id"], name: "index_recipe_ingredients_on_ingredient_id"
t.index ["recipe_id"], name: "index_recipe_ingredients_on_recipe_id"
end
create_table "recipes", force: :cascade do |t|
t.string "title"
t.text "description"
end
add_foreign_key "party_recipes", "parties"
add_foreign_key "party_recipes", "recipes"
add_foreign_key "recipe_ingredients", "ingredients"
add_foreign_key "recipe_ingredients", "recipes"
I'm not entirely sure where exactly needs to be corrected, any help appreciated, thank you so much!
Well the error message is correct, you don't have any model called Parties, in fact in Rails, models are always singular, camel-case. So that explains the error message.
However that won't fix your problem! The iterator in the view should be
<% #ingredients.each do |ingredient| %>
<%= check_box_tag(ingredient) %>
<%= ingredient %>
<% end %>
Because I think you are trying to populate an #ingredients variable in your controller. However it still won't work, b/c the value of the #ingredients variable is not being correctly assigned...
Personally I much prefer the "fat model skinny controller" design style for Rails. So I would have a PartiesController#ingredients method that looks like this:
# parties_controller.rb
def ingredients
#party = Party.find(params[:party_id])
#ingredients = #party.ingredients
end
then in your Party model:
# app/models/party.rb
def ingredients
recipes.map(&:ingredients).flatten
end
Why do it this way? Well you're just getting started with Rails, but eventually (soon hopefully) you'll be writing tests, and it's much much easier to write tests on models than controllers.
Now, there could well be some other issues in your code, but try my suggestions and see where that gets you.
#Les Nightingill's answer should work well for organizing your controller and model! Regarding when you click refresh and the value of the boxes are saved either;
Set up some listeners in javascript and send a request to your update controller method every time there is a value change for one of your check boxes.
Or add a save button at the bottom of your form that points to your update controller method to save the values of the checkboxes. Something like:
<%= submit_tag "Save", data: { disable_with: "Saving..." } %>
https://api.rubyonrails.org/v5.2.3/classes/ActionView/Helpers/FormTagHelper.html#method-i-submit_tag

Associating two columns in the same Model class to the same column in another Model - Rails

Quick question - I have two model classes - Transactions and Accounts.
The Account Model looks as follows:
create_table "accounts", force: :cascade do |t|
t.string "account_name"
t.integer "account_number"
t.boolean "current_asset"
t.boolean "non_current_asset"
t.boolean "current_liability"
t.boolean "non_current_liability"
t.boolean "equity"
t.boolean "cost_of_sales"
t.boolean "operating_expense"
t.boolean "sales"
t.boolean "other_income"
t.boolean "bank"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
The Transaction Model looks as follows:
create_table "transactions", force: :cascade do |t|
t.date "date"
t.string "description"
t.string "reference"
t.integer "amount"
t.integer "account_id"
t.boolean "payment"
t.boolean "receipt"
t.integer "bank_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
t.boolean "vat"
t.integer "vat_amount"
t.integer "transaction_form_id"
t.integer "contact_id"
end
A user adds a new transaction which, through collection_select
they to choose an Account which displays all the accounts except for where bank == true (where bank == true it represents a bank account).
When a user selects a Bank, they can only choose a bank account - where bank==true (again through collection select).
I need to run a method that allows me to call all the Transaction amounts through the Account model based on the bank_id and not the account_id. So how would i associate two different columns of my Transaction model to the same column in the Accounts class through either account_id or bank_id.
The method will look something like:
<% Account.each do |account| %>
<% if account.bank == true && account.transaction(:bank_id) == account.id %>
<%= account.number %>
<%= account.number %>
<%= account.transactions.sum(:amount) %>
<% end %>
Ps: I know this is in my views, not controller, but that's for another discussion!
An image of Models example
I suggest you back up and re-think your approach.
A bank account and a GL account are very different things (even though they both use the word 'account'). Using a single model to represent both of these will lead to confusion and a lot of empty fields. Consider using a BankAccount and a GLAccount (you can use custom inflection on GLAccount, if you like, so you can do gl_account).
If you use BankAccount and GLAccount, then you can simply do a polymorphic association on your Transaction model, something like:
class Transaction < ActiveRecord::Base
belongs_to :account, polymorphic: true
end
You would need to change your transactions table to include account_type and account_id for that to work.
Then, your BankAccount and GLAccount models would look something like:
class BankAccount < ActiveRecord::Base
has_many :transactions, as: :account
end
and
class GLAccount < ActiveRecord::Base
has_many :transactions, as: :account
end
In which case, to iterate the BankAccounts, you would do something like:
<% BankAccount.all.each do |bank_account| %>
<%= bank_account.number %>
<%= bank_account.transactions.sum(:amount) %>
<% end %>
That'll end up with an N+1 query problem, but you can sort that out separately.
BTW, on your GL Accounts, you should use enums for the account type. As you currently have it set up, you will always have 8 empty boolean fields (not good). That might look something like:
class GLAccount < ActiveRecord::Base
has_many :transactions, as: :account
enum account_type: {
current_asset: 0,
non_current_asset: 1,
current_liability: 2,
non_current_liability: 3,
equity: 4,
cost_of_sales: 5,
operating_expense: 6,
sales: 7,
other_income: 8
}
end
Your gl_accounts table would need to include account_type as an integer for that to work.
A couple of other random notes:
if account.bank == true should just be if account.bank
You repeat <%= account.number %>
Given, however, what you have done, you could do something like:
class Account < ActiveRecord::Base
has_many bank_transactions, class_name: 'Transaction', foreign_key: :bank_id
end
In which case, you should be able to do:
<% Account.where(bank: true).each do |bank_account| %>
<%= bank_account.number %>
<%= bank_account.bank_transactions.sum(:amount) %>
<% end %>

Counter Cache customizing for zeros and large numbers

I'm still learning rails so any help you can provide would be super helpful. I've set a count for my likes on my book app. Thus, every time a user likes a book - the number increases by one or decreases if the unlike it. However, if no one has liked a book yet - a 0 appears. I'd like that to be blank so that only when a user has liked it will the number appear. I've listed all my relevant code below. Thank you so much.
Schema.rb
create_table "books", force: :cascade do |t|
t.string "title"
t.integer "user_id"
t.integer "book_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
t.integer "likes_count", default: 0, null: false
end
create_table "likes", force: :cascade do |t|
t.integer "user_id"
t.integer "book_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Book.rb
class Book < ApplicationRecord
has_many :likes, :counter_cache => true
has_many :users, through: :likes
belongs_to :user
end
Likes.rb
class Like < ApplicationRecord
belongs_to :book, :counter_cache => true
belongs_to :user
end
Likes Count Migration
class AddLikecountsToBook < ActiveRecord::Migration[5.0]
def change
add_column :books, :likes_count, :integer, :null => false, :default => 0
end
end
With associations in rails you get several interogation methods such as .any? and .none? which can be used to create conditional expressions.
<% if book.likes.any? %>
<%= number_to_human(book.likes.size) %>
<% end %>
# or
<%= number_to_human(book.likes.size) unless book.likes.none? %>
This uses the counter cache as well to avoid n+1 queries.
If you do not want your view to display 0 you could add a if statement in your view.
<% if #votes == 0 %>
be the first to rate this book
<% else %>
<%= #votes %>
<% end %>
Or when returning the variable to the view from the controller
def
if #votes == 0
#votes = ''
end
end

Update Second Drop Down Menu Based on Selection in the First Drop Down

I am building a cinema application in Ruby on Rails and am currently working on the booking system. What I want to be able to do is select a film from a drop down menu, and then the show times for that film are displayed in a drop down menu, the user can select a show time and then the seats available are displayed in a drop down menu.
I have watched this video: https://www.youtube.com/watch?v=GYg6s-b1XGo and have looked at many other sites but what I want to do is a bit more complicated.
Ok, so my models/bookings.rb:
class Booking < ActiveRecord::Base
belongs_to :user
belongs_to :showing
belongs_to :seat
end
models/showing.rb:
class Showing < ActiveRecord::Base
belongs_to :film
has_many :bookings
belongs_to :screen
def showing_times
"#{show_date.strftime("%e %b %Y")} # #{show_time.strftime("%H:%M")}"
end
end
models/seats.rb:
class Seat < ActiveRecord::Base
belongs_to :screen
has_many :bookings
def seats_available
"#{row_letter}#{row_number}"
end
end
This is where it gets complicated, my views/bookings/_form.html.erb:
<%= form_for #booking do |f| %>
<%= f.hidden_field :user_id %>
<%= image_tag "thor_hammer.jpg",:size => "900x250" %>
<td width="300px">
<br><%= f.label :film_id, 'Film:' %>
<br><%= f.collection_select :film_id, Film.all, :id,:title_info %>
<br><%= f.label :showing_id, 'Showing:' %>
<br><%= f.collection_select :showing_id, Showing.all, :id,:showing_times %>
<br><%= f.label :seat_id, 'Seat ID:' %>
<br><%= f.collection_select :seat_id, Seat.all, :id,:seats_available %><br>
</td>
<td width="300px">
<div class="actions">
<%= f.submit %>
</div>
<br>
<%= render "/error_messages", :message_header => "Cannot save: ", :target => #booking %>
</td>
<% end %>
This is where it gets complicated because the line <%= f.collection_select :film_id, Film.all, :id,:title_info %> causes the error:
NoMethodError in Bookings#new
undefined method `film_id' for #<Booking:0x584e6b0>
Because the bookings data table does not store the film, it stores the showing time and this has the film associated with it, this is the same for the seats: the user needs to be able to select a showing to select the seat but the showings table is associated with the screens table, which is associated with the seats.
To make this clearer, here is the schema:
create_table "bookings", force: :cascade do |t|
t.integer "user_id"
t.integer "showing_id"
t.integer "seat_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "films", force: :cascade do |t|
t.string "title"
t.string "synopsis"
t.string "director"
t.string "cast1"
t.string "cast2"
t.string "cast3"
t.date "release_date"
t.string "warnings"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_url"
t.string "certificate_id"
t.integer "category_id"
t.integer "hours"
t.integer "minutes"
t.string "video_url"
end
create_table "screens", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "seats", force: :cascade do |t|
t.string "row_letter"
t.integer "row_number"
t.integer "screen_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "showings", force: :cascade do |t|
t.date "show_date"
t.time "show_time"
t.integer "film_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "screen_id"
end
Has anyone got any suggestions?
You are trying to set the film_id of an instance of the Booking class. You are correct to notice that you do not have a film_id in the bookings table.
The easiest way to accomplish what you desire is simply to add a film_id to the bookings table and to create a has_one and belongs_to association between Booking and Film.
Another way to accomplish this, that will require you to dive deeper into rails is to use accepts_nested_attributes_for. Here's a good tutorial and here's a newer tutorial if you run into any issues with the first one. This will allow you to not have to create redundant associations.
UPDATE 1
After looking closer at what you are trying to do I realized that you are not trying to create a new resource (film) so please disregard my previous comment about nested attributes.
The solution to what you are trying to do involves a few moving parts that are a little too long to give in an answer like this. However, here is a summary that may guide you while you try to figure it out:
You need a separate form to select the film first
Based on the film selection you can use ajax to fetch all the showings that belong to that film (For this you need to read up on ajax; you also need to read up on how to expose your data via a json endpoint in rails) You will end up passing an id of the film the user has selected and you will return the result of this query: Showing.where(film_id: <id>)
After you managed to get that data you need to populate it in a second form that ends up displaying only the showings associated with that film.
This seems a bit complicated for what you are trying to do. What I would recommend is that you either:
spend some extra time on figuring out how to structure your models a little differently
or that you don't allow the users to select the film and then the showings on the same page (dynamically). Make them select the film first. After they selected the film they are redirected to a page where they can select the showings. This would be a worse experience for the user but it would enable you to understand rails a little better (ajax and javascript wouldn't be required in this scenario).
I assume you're doing this to try to get a better grasp of rails, so I hope this information helps you. Good luck!

Rails Unidentified Method Issue: undefined method `outlet_id'

In my database I am trying to get a one to many relationship between outlets and articles.
I am getting the following error when that relationship is used:
undefined method `outlet_id' for #<Article:0x007fc353887e58>
Here are the models:
class Article < ActiveRecord::Base
belongs_to :analyst
belongs_to :outlet
has_and_belongs_to_many :loe
attr_accessible :article_body, :author, :distribution, :loe, :most_important, :pubdate, :publication, :state, :submitted, :summary, :title, :url, :analyst_id, :loe_ids, :outlet_id
end
class Outlet < ActiveRecord::Base
has_many :articles, foreign_key: :title
attr_accessible :distribution, :name, :state, :article_ids
end
Here are the schema:
create_table "articles_loes", :id => false, :force => true do |t|
t.integer "article_id"
t.integer "loe_id"
end
create_table "loes", :force => true do |t|
t.string "name"
t.string "customer"
t.integer "article_id"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "loes", ["article_id"], :name => "index_loes_on_article_id"
create_table "outlets", :force => true do |t|
t.string "name"
t.integer "articles_id"
t.integer "distribution"
t.string "state"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "outlets", ["articles_id"], :name => "index_outlets_on_articles_id"
And here is the chunk of the view that calls on :outlet :
<div class="span4">
<%= f.association :loe %>
<%= f.association :outlet %>
</div>
If anyone has any ideas I'd really appreciate them. I think I might need an index of Outlets in Article? I'm not really sure how to implement that if that is the case. Thanks in advance.
Right now there is no way for your Outlet model to associate with the articles that it has. Once you say belongs_to, you need to have an outlet_id column. So you need to add an outlet_id (integer) column to your Article model and populate it with the id of the outlet they belong to. If an Article can belong to many outlets in that case you need to create a many-to-many relationship through a joint table.

Resources