Related
I'm trying to setup multiple tagging in my app. I have used a single tag and tagging model which works fine for tagging a single model.
However, after setting up the association, i got this error when i wanted to create a new question undefined method map for nil:NilClass which actually points to <%= select_tag(:kategory_id, options_for_select(#kategories), :prompt => "Select Category", class: "form-control") %>. I don't know why it's pointing to this since it has nothing to do with the tagging. I might have made a mistake somewhere which i could not figure out.
Here is how i do the setup.
I added taggable_id and taggable_type to taggings table.
class AddAttributesToTaggings < ActiveRecord::Migration[5.1]
def change
add_column :taggings, :taggable_id, :integer
add_column :taggings, :taggable_type, :string
add_index :taggings, [:taggable_type, :taggable_id]
end
end
Tag.rb
class Tag < ApplicationRecord
has_many :taggings
has_many :posts, through: :taggings, source: :taggable, source_type: Post
has_many :questions, through: :taggings, source: :taggable, source_type: Question
has_many :user_tags, dependent: :destroy
has_many :users, through: :user_tags
extend FriendlyId
friendly_id :name, use: :slugged
def should_generate_new_friendly_id?
name_changed?
end
end
tagging.rb
class Tagging < ApplicationRecord
belongs_to :tag
belongs_to :taggable, :polymorphic => true
end
Question.rb
class Question < ApplicationRecord
belongs_to :user
belongs_to :kategory
validates :title, :content, presence: true
is_impressionable counter_cache: true, unique: :all
Question.order('impressions_count DESC')
scope :most_recent, -> { order(created_at: :desc) }
extend FriendlyId
friendly_id :title, use: :slugged
def should_generate_new_friendly_id?
title_changed?
end
include Taggable
def related_questions
Question.joins(:tags).where(tags: { id: self.tags.pluck(:id) }).where.not(id: self.id)
end
end
models/concerns/taggable.rb
module Taggable
extend ActiveSupport::Concern
included do
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
def tag_list
self.tags.collect do |tag|
tag.name
end.join(", ")
end
def tag_list=(tags_string)
tag_names = tags_string.split(",").collect{|s| s.strip.downcase}.uniq
new_or_found_tags = tag_names.collect { |name| Tag.friendly.find_or_create_by(name: name) }
self.tags = new_or_found_tags
end
end
tags and taggings table in schema.rb
create_table "taggings", force: :cascade do |t|
t.bigint "tag_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "taggable_id"
t.string "taggable_type"
t.index ["tag_id"], name: "index_taggings_on_tag_id"
t.index ["taggable_type", "taggable_id"], name: "index_taggings_on_taggable_type_and_taggable_id"
end
create_table "tags", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "slug"
t.index ["slug"], name: "index_tags_on_slug", unique: true
end
questions/_form
<%= form_with(model: question, local: true) do |form| %>
<% if question.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(question.errors.count, "error") %> prohibited this question from being saved:</h2>
<ul>
<% question.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<b><%= form.label "Category" %></b>
<%= select_tag(:kategory_id, options_for_select(#kategories), :prompt => "Select Category", class: "form-control") %>
</div>
<br>
<div class="field">
<b><%= form.label :title %></b>
<%= form.text_field :title, id: :question_title, class: "form-control" %>
</div>
<br>
<div class="field">
<%= form.label :content %>
<%= form.text_area :content, id: :question_content, class: "form-control" %>
</div>
<br>
<div>
<%= form.label :tag_list %>
<%= form.text_field :tag_list %>
</div>
<br>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
questions_controller.rb
class QuestionsController < ApplicationController
before_action :set_question, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!
before_action :find_contacts
before_action :find_kategory
impressionist :actions=>[:show,:index]
# GET /questions
# GET /questions.json
def index
if params[:kategory].blank?
#questions = Question.most_recent
else
#kategory_id = Kategory.find_by(name: params[:kategory]).id
#questions = Question.where(:kategory_id => #kategory_id).most_recent
end
end
# GET /questions/1
# GET /questions/1.json
def show
impressionist(#question)
#most_viewed = Question.order('impressions_count DESC').take(20)
end
# GET /questions/new
def new
#question = current_user.questions.build
#kategories = Kategory.all.map{ |k| [k.name, k.id] }
end
# GET /questions/1/edit
def edit
#kategories = Kategory.all.map{ |k| [k.name, k.id] }
end
# POST /questions
# POST /questions.json
def create
#question = current_user.questions.build(question_params)
#question.kategory_id = params[:kategory_id]
respond_to do |format|
if #question.save
flash[:success] = 'Your question was successfully posted.'
format.html { redirect_to #question }
format.json { render :show, status: :created, location: #question }
else
format.html { render :new }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /questions/1
# PATCH/PUT /questions/1.json
def update
respond_to do |format|
if #question.update(question_params)
flash[:success] = 'Your question was successfully updated.'
format.html { redirect_to #question }
format.json { render :show, status: :ok, location: #question }
else
format.html { render :edit }
format.json { render json: #question.errors, status: :unprocessable_entity }
end
end
end
# DELETE /questions/1
# DELETE /questions/1.json
def destroy
#question.destroy
respond_to do |format|
flash[:success] = 'Your question has been deleted.'
format.html { redirect_to questions_url }
format.json { head :no_content }
end
end
def most_viewed
#questions = Question.order('impressions_count DESC').take(20)
end
private
# Use callbacks to share common setup or constraints between actions.
def set_question
#question = Question.friendly.includes(:tags).find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def question_params
params.require(:question).permit(:title, :content, :kategory_id, :tag_list)
end
def find_contacts
#contacts = user_signed_in? ? current_user.all_active_contacts : ''
end
def find_kategory
#questions = Kategory.where(:kategory_id => #kategory_id)
end
end
UPDATE:**I'm now able to save a question without adding any tag but with tags added, I get a form error that tagging is invalid. **Tags are properly loaded when I edit a question.
Error Log:
Started POST "/questions" for 127.0.0.1 at 2018-07-13 12:01:01 +0100
Processing by QuestionsController#create as HTML
Parameters: {"utf8"=>"√", "authenticity_token"=>"hgLn8bzd6fVSzCIPtRd3P0Jp/LEso
/h5cKNBY0a80x3i4RF6tpLlRyqmls3xRFryqyP1/s/rLDVIt9C1/uK+qw==", "kategory_id"=>"1"
, "question"=>{"title"=>"Help!!! My Dell Inspiron 6400 is hanging", "content"=>"
Lorem ipsum dolor amet seitan offal ethical, beard viral lo-fi put a bird on it
salvia actually yr. Ethical ennui pitchfork fanny pack, gentrify seitan sartoria
l bespoke. Viral 90's church-key swag, you probably haven't heard of them banh m
i intelligentsia brunch DIY iceland wolf pitchfork. Everyday carry photo booth n
ormcore XOXO tumblr portland.\r\n\r\nBrooklyn heirloom kombucha, edison bulb leg
gings hell of DIY chartreuse austin tacos bitters. Blog hexagon copper mug blue
bottle cray. Post-ironic direct trade kale chips mumblecore. Craft beer squid cr
onut vape, hoodie bitters succulents ramps snackwave vegan small batch brunch ch
ia food truck umami. Chillwave blue bottle viral raclette authentic health goth
vape coloring book cardigan 3 wolf moon taxidermy. Gluten-free tote bag hella, l
omo pinterest direct trade gastropub tattooed. Iceland hammock post-ironic, bush
wick cornhole tumeric dreamcatcher blog palo santo chartreuse 90's food truck sy
nth chicharrones.", "tag_list"=>"hardware"}, "commit"=>"Create Question"}
User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDE
R BY "users"."id" ASC LIMIT $2 [["id", 3], ["LIMIT", 1]]
Private::Conversation Load (2.9ms) SELECT "private_conversations".* FROM "pri
vate_conversations" WHERE ("private_conversations"."recipient_id" = $1 OR "priva
te_conversations"."sender_id" = $2) [["recipient_id", 3], ["sender_id", 3]]
Private::Message Load (2.0ms) SELECT "private_messages".* FROM "private_messa
ges" WHERE "private_messages"."conversation_id" IN (2, 3, 4, 6)
Group::Conversation Load (3.9ms) SELECT "group_conversations".* FROM "group_c
onversations" INNER JOIN "group_conversations_users" ON "group_conversations"."i
d" = "group_conversations_users"."conversation_id" WHERE "group_conversations_us
ers"."user_id" = $1 [["user_id", 3]]
Group::Message Load (2.0ms) SELECT "group_messages".* FROM "group_messages" W
HERE "group_messages"."conversation_id" = 1
User Load (2.0ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (2, 3,
5)
(40.0ms) SELECT "group_conversations"."id" FROM "group_conversations" INNER
JOIN "group_conversations_users" ON "group_conversations"."id" = "group_conversa
tions_users"."conversation_id" WHERE "group_conversations_users"."user_id" = $1
[["user_id", 3]]
CACHE (0.0ms) SELECT "group_conversations"."id" FROM "group_conversations" I
NNER JOIN "group_conversations_users" ON "group_conversations"."id" = "group_con
versations_users"."conversation_id" WHERE "group_conversations_users"."user_id"
= $1 [["user_id", 3]]
User Load (2.9ms) SELECT "users".* FROM "users" INNER JOIN "contacts" ON "use
rs"."id" = "contacts"."contact_id" WHERE "contacts"."user_id" = $1 AND "contacts
"."accepted" = $2 [["user_id", 3], ["accepted", "t"]]
User Load (4.9ms) SELECT "users".* FROM "users" INNER JOIN "contacts" ON "use
rs"."id" = "contacts"."user_id" WHERE "contacts"."contact_id" = $1 AND "contacts
"."accepted" = $2 ORDER BY "users"."created_at" ASC [["contact_id", 3], ["accep
ted", "t"]]
Tag Load (2.0ms) SELECT "tags".* FROM "tags" WHERE "tags"."name" = $1 LIMIT
$2 [["name", "hardware"], ["LIMIT", 1]]
Kategory Load (10.7ms) SELECT "kategories".* FROM "kategories"
(1.0ms) BEGIN
Question Exists (4.9ms) SELECT 1 AS one FROM "questions" WHERE ("questions".
"id" IS NOT NULL) AND "questions"."slug" = $1 LIMIT $2 [["slug", "help-my-dell-
inspiron-6400-is-hanging"], ["LIMIT", 1]]
Kategory Load (5.9ms) SELECT "kategories".* FROM "kategories" WHERE "kategor
ies"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.0ms) ROLLBACK
Rendering questions/new.html.erb within layouts/application
Rendered questions/_form.html.erb (17.6ms)
Rendered questions/new.html.erb within layouts/application (141.6ms)
Rendered layouts/navigation/header/_toggle_button.html.erb (1.0ms)
Rendered layouts/navigation/header/_home_button.html.erb (30.3ms)
User Load (28.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIM
IT $2 [["id", 5], ["LIMIT", 1]]
Rendered layouts/navigation/header/dropdowns/conversations/_private.html.erb (
73.2ms)
User Load (2.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMI
T $2 [["id", 3], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $
1 LIMIT $2 [["id", 5], ["LIMIT", 1]]
Rendered layouts/navigation/header/dropdowns/conversations/_private.html.erb (
109.4ms)
Rendered layouts/navigation/header/dropdowns/conversations/_group.html.erb (3.
9ms)
User Load (30.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIM
IT $2 [["id", 1], ["LIMIT", 1]]
Rendered layouts/navigation/header/dropdowns/conversations/_private.html.erb (
40.0ms)
CACHE User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $
1 LIMIT $2 [["id", 3], ["LIMIT", 1]]
User Load (2.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMI
T $2 [["id", 2], ["LIMIT", 1]]
Rendered layouts/navigation/header/dropdowns/conversations/_private.html.erb (
100.6ms)
Rendered layouts/navigation/header/dropdowns/_conversations.html.erb (983.4ms)
User Load (2.0ms) SELECT "users".* FROM "users" INNER JOIN "contacts" ON "use
rs"."id" = "contacts"."user_id" WHERE "contacts"."contact_id" = $1 AND "contacts
"."accepted" = $2 [["contact_id", 3], ["accepted", "f"]]
Rendered layouts/navigation/header/dropdowns/contact_requests/_no_requests.htm
l.erb (1.0ms)
Rendered layouts/navigation/header/dropdowns/_contact_requests.html.erb (374.0
ms)
Rendered layouts/navigation/header/_dropdowns.html.erb (1713.9ms)
Rendered layouts/navigation/_header.html.erb (2485.4ms)
Kategory Load (2.9ms) SELECT "kategories".* FROM "kategories"
Rendered layouts/navigation/collapsible_elements/_constant_links.html.erb (2.0
ms)
(2.0ms) SELECT COUNT(*) FROM "questions" INNER JOIN "save_questions" ON "que
stions"."id" = "save_questions"."question_id" WHERE "save_questions"."user_id" =
$1 [["user_id", 3]]
Rendered layouts/navigation/collapsible_elements/_signed_in_links.html.erb (21
.5ms)
Rendered layouts/navigation/_collapsible_elements.html.erb (543.0ms)
Rendered layouts/_navigation.html.erb (3323.2ms)
Rendered layouts/application/_private_conversations_windows.html.erb (1.0ms)
Rendered layouts/application/_group_conversations_windows.html.erb (2.0ms)
Completed 200 OK in 22203ms (Views: 21796.6ms | ActiveRecord: 158.2ms)
You need to define #kategories = Kategory.all.map{ |k| [k.name, k.id] } in create and update actions. This actions rerender new/edit actions if they can't save the question, and they need #kategories for rerendering
If you provide the full server log for create action I can try to help you to figure out - why #question can not be saved
Looks to me like your #kategories, or something within there (perhaps a name), is nil, hence the error :) (Hopefully this is a good thing, as all may be well with your tagging.)
Ensure Kategory.all.map { |k| [k.name, k.id] } in your new action is returning records and this error should go away.
N.B. you can also use options_from_collection_for_select here.
# in the controller
def new
#question = current_user.questions.build
#kategories = Kategory.all
end
# and the view
<%= select_tag(:kategory_id, options_from_collection_for_select(#kategories, :name, :id), prompt: "Select Category", class: "form-control") %>
This way, if you don't have any categories, you won't get an error - instead, you just won't have any options available.
I have an issue with bringing a view into my app, which calculates a running balance for a transactions table using SQL. I have the following models:
account.rb
class Account < ApplicationRecord
belongs_to :user
has_many :transactions, dependent: :destroy
validates :name, presence: true, length: { maximum: 50, minimum: 2 }
validates :starting_balance, presence: true
#validates_associated :transactions
after_create :create_initial_transaction
def create_initial_transaction
self.update_attributes(current_balance: 0.00)
Transaction.create(trx_type: 'credit', trx_date: DateTime.now, account_id: self.id, description: "Starting Balance", amount: self.starting_balance)
#self.update_attributes(current_balance: #initbalance)
end
end
transaction.rb
class Transaction < ApplicationRecord
belongs_to :account
has_one :transaction_balance
delegate :running_balance, to: :transaction_balance
attr_accessor :trx_type
#default_scope { order('trx_date, id DESC') }
validates_presence_of :trx_type, :message => "Please select debit or credit"
validates :trx_date, presence: true
validates :description, presence: true, length: { maximum: 150 }
validates :amount, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :memo, length: { maximum: 500 }
before_save :convert_amount
after_create :update_account_balance_new
after_update :update_account_balance_edit
after_destroy :update_account_balance_destroy
scope :with_balance, -> { joins(:transaction_balance) }
scope :desc, -> { order('trx_date, id DESC') }
# Determine the transaction_type for existing records based on amount
def transaction_type
if !new_record?
if self.amount >= 0
return ['Credit', 'credit']
else
return ['Debit', 'debit']
end
else
return ['Debit', 'debit']
end
end
private
def convert_amount
if self.trx_type == "debit"
self.amount = -self.amount.abs
end
end
def update_account_balance_new
#account = Account.find(account_id)
#account.update_attributes(current_balance: #account.current_balance + amount)
end
def update_account_balance_edit
#account = Account.find(account_id)
if saved_change_to_amount?
#account.update_attributes(current_balance: #account.current_balance - amount_was + amount)
end
end
def update_account_balance_destroy
#account = Account.find(account_id)
#account.update_attributes(current_balance: #account.current_balance - amount_was)
end
end
Basically my app allows users to create bank accounts, then add transactions to them to keep track of finances. I wanted to add a running balance at the transaction level, so I created a view which joins back to the transactions table 1:1 ....
Migration for view
class CreateTransactionBalancesView < ActiveRecord::Migration[5.1]
def up
execute <<-SQL
CREATE VIEW transaction_balances AS (
SELECT id AS transaction_id,
SUM(amount) OVER(PARTITION BY account_id ORDER BY trx_date, id) AS running_balance
FROM transactions
)
SQL
end
def down
execute("DROP VIEW transaction_balances")
end
end
Now, when I created a model for this view, I had issues referencing "belongs_to :transaction" because it was complaining that "transaction" was a reserved word, so I had to find a workaround, as seen in my model below:
transaction_balance.rb
class TransactionBalance < ApplicationRecord
self.primary_key = "transaction_id"
#belongs_to :transaction
belongs_to :user_transaction, foreign_key: "transaction_id", class_name: "Transaction"
end
transactions_controller.rb
class TransactionsController < ApplicationController
before_action :find_account
before_action :find_transaction, only: [:edit, :update, :show, :destroy]
# Index action to render all transactions
def index
#transactions = #account.transactions.paginate(page: params[:page], per_page: 25)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #transactions }
end
end
# New action for creating transaction
def new
#transaction = #account.transactions.build
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #transaction }
end
end
# Create action saves the trasaction into database
def create
#transaction = #account.transactions.build(transaction_params)
respond_to do |format|
if #transaction.save
format.html { redirect_to([#transaction.account, #transaction], :notice => 'Transaction was successfully created.') }
format.xml { render :xml => #transaction, :status => :created, :location => [#transaction.account, #transaction] }
else
format.html { render :action => "new" }
format.xml { render :xml => #transaction.errors, :status => :unprocessable_entity }
end
end
end
# Edit action retrieves the transaction and renders the edit page
def edit
end
# Update action updates the transaction with the new information
def update
respond_to do |format|
if #transaction.update_attributes(transaction_params)
format.html { redirect_to([#transaction.account, #transaction], :notice => 'Transaction was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #transaction.errors, :status => :unprocessable_entity }
end
end
end
# The show action renders the individual transaction after retrieving the the id
def show
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #transaction }
end
end
# The destroy action removes the transaction permanently from the database
def destroy
#transaction.destroy
respond_to do |format|
format.html { redirect_to(account_transactions_url) }
format.xml { head :ok }
end
end
private
def transaction_params
params.require(:transaction).permit(:trx_date, :description, :amount, :trx_type, :memo)
end
def find_account
#account = current_user.accounts.find(params[:account_id])
end
def find_transaction
#transaction = #account.transactions.find(params[:id])
end
end
And finally, my transactions index view, where I reference the running_balance field
<% #transactions.with_balance.desc.each do |transaction| %>
<tr class="row m-0">
<td class="col-sm-1 text-center"><%= link_to transaction.id, [transaction.account, transaction] %></td>
<td class="col-sm-1 text-center"><%= transaction.trx_date.strftime('%m/%d/%Y') %></td>
<td class="col-sm-4"><%= transaction.description %></td>
<td class="col-sm-2 text-right"><%= if transaction.amount >= 0 then number_to_currency(transaction.amount) end %></td>
<td class="col-sm-2 text-right"><%= if transaction.amount < 0 then "(" + number_to_currency(transaction.amount.abs) + ")" end %></td>
<td class="col-sm-2 text-right"><%= number_to_currency(transaction.running_balance) %></td>
</tr>
<% end %>
Now, my problem is when I access the transactions index page in browser, my server console shows the following:
Started GET "/accounts/1/transactions" for 127.0.0.1 at 2018-03-28 16:32:08 -0400
Processing by TransactionsController#index as HTML
Parameters: {"account_id"=>"1"}
User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
Account Load (1.1ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."user_id" = $1 AND "accounts"."id" = $2 LIMIT $3 [["user_id", 1], ["id", 1], ["LIMIT", 1]]
Rendering transactions/index.html.erb within layouts/application
Transaction Load (0.8ms) SELECT "transactions".* FROM "transactions" INNER JOIN "transaction_balances" ON "transaction_balances"."transaction_id" = "transactions"."id" WHERE "transactions"."account_id" = $1 ORDER BY trx_date, id DESC LIMIT $2 OFFSET $3 [["account_id", 1], ["LIMIT", 25], ["OFFSET", 0]]
TransactionBalance Load (0.3ms) SELECT "transaction_balances".* FROM "transaction_balances" WHERE "transaction_balances"."transaction_id" = $1 LIMIT $2 [["transaction_id", 8], ["LIMIT", 1]]
TransactionBalance Load (1.5ms) SELECT "transaction_balances".* FROM "transaction_balances" WHERE "transaction_balances"."transaction_id" = $1 LIMIT $2 [["transaction_id", 7], ["LIMIT", 1]]
TransactionBalance Load (0.3ms) SELECT "transaction_balances".* FROM "transaction_balances" WHERE "transaction_balances"."transaction_id" = $1 LIMIT $2 [["transaction_id", 6], ["LIMIT", 1]]
TransactionBalance Load (0.2ms) SELECT "transaction_balances".* FROM "transaction_balances" WHERE "transaction_balances"."transaction_id" = $1 LIMIT $2 [["transaction_id", 5], ["LIMIT", 1]]
TransactionBalance Load (0.2ms) SELECT "transaction_balances".* FROM "transaction_balances" WHERE "transaction_balances"."transaction_id" = $1 LIMIT $2 [["transaction_id", 4], ["LIMIT", 1]]
TransactionBalance Load (0.3ms) SELECT "transaction_balances".* FROM "transaction_balances" WHERE "transaction_balances"."transaction_id" = $1 LIMIT $2 [["transaction_id", 3], ["LIMIT", 1]]
TransactionBalance Load (1.0ms) SELECT "transaction_balances".* FROM "transaction_balances" WHERE "transaction_balances"."transaction_id" = $1 LIMIT $2 [["transaction_id", 1], ["LIMIT", 1]]
(1.8ms) SELECT COUNT(*) FROM "transactions" WHERE "transactions"."account_id" = $1 [["account_id", 1]]
Rendered transactions/index.html.erb within layouts/application (59.8ms)
Account Load (0.5ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."user_id" = $1 [["user_id", 1]]
Rendered layouts/_navbar.html.erb (3.9ms)
Completed 200 OK in 255ms (Views: 169.1ms | ActiveRecord: 19.5ms)
As seen above, the view is being selected from for each individual record. What I expect it to do is to join the transactions table with the transaction_balances view in one single select statement. Any help on this would be greatly appreciated! Thanks!
In your TransactionsController#index action, change this line:
#transactions = #account.transactions.paginate(page: params[:page], per_page: 25)
to this (credit to #engineersmnky):
#transactions = #account.transactions.includes(:transaction_balance).references(:transaction_balance).paginate(page: params[:page], per_page: 25)
This will generate a single query that allows your view to access the transaction_balance for each transaction without going back to the database.
This is happening because you're not loading the TransactionBalance records when you load the transactions in your controller. Here's what you're doing:
#account.transactions
And here's what will fix the problem:
#account.transactions.with_balance
This will use the with_balance scope in your model, which does joins(:balance), which will load both the transacations and all their balances in the one query.
I'm not sure where you're getting your #account from in your TransactionsController, but you may want to do a join or an includes.
For instance, #account = Account.includes(:transactions).find(params[:account_id]) before you query the transactions will eager load the transactions and pull them in one query rather than "n+1-ing" them.
The API dock entry can tell you more about the includes method
and
This article can tell you more about how to get rid of the n+1 querying problem.
Good luck!
I migrated my Rails from 3.2 to Rails 4.2.6. I am having 2 tables where report :has_many => icons. I added strong parameters for report and icon_attributes. The create functionality is working fine and when coming to update functionality, I am able to update reports but couldn't update icons, instead new icon is created every time it hits update action.
This is my code:
report.rb:
class Report < ActiveRecord::Base
has_many :icons, -> { order 'position_id ASC'}
accepts_nested_attributes_for :icons, :reject_if => lambda { |a| a[:icon].blank? }, :allow_destroy => true
end
icon.rb:
class Icon < ActiveRecord::Base
belongs_to :report
end
reports_controller:
def update
respond_to do |format|
if #report.update_attributes(report_params)
#report.save
format.html { redirect_to(user_reports_url, :notice => 'Report was successfully updated.') }
format.json { render :json => { :success => true, :report_id => #report.id, :report_title => #report.title, :icon_array => #report.icons, :redirect => report_url(#report.id) } }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #report.errors, :status => :unprocessable_entity }
end
end
end
private
def report_params
params.require(:report).permit(:title, :comments, :remarks,{:icons_attributes => [:id, :icon, :rotation, :top, :_destroy]})
end
I have seen the log by putting puts in the controller, the icons are inserting at #report.update_attributes(report_params) step and this is the log:
Processing by ReportsController#update as JSON Parameters:
{"utf8"=>"✓", "report"=>{"title"=>"title1", "comments"=>"This is a comment",
"icons_attributes"=>{"0"=>{"id"=>"", "icon"=>"market_indicator",
"rotation"=>"0", "top"=>"", "_destroy"=>"false"}, "id"=>"87"}
Report Load (0.3ms) SELECT "reports".* FROM "reports" WHERE
"reports"."deleted_at" IS NULL AND "reports"."id" = ? LIMIT 1 [["id",
87]]
SQL (1.6ms) INSERT INTO "icons" ("icon", "rotation", "top")
VALUES (?, ?, ?) [["icon", "market"], ["rotation", "0"], ["top", ""],
["left", ""]] (12.0ms) commit transaction
ActiveRecord::Associations::CollectionProxy
I have put log as:
def update
puts #report.icons.inspect
respond_to do |format|
.....
end
it resulted as:
Icon Load (0.9ms) SELECT "icons".* FROM "icons" WHERE "icons"."report_id" = ? ORDER BY position_id ASC [["report_id", 91]]
<ActiveRecord::Associations::CollectionProxy [#<Icon id: 204, report_id: 91, icon: "asking_price", rotation: "", top: "150", left: "165">]>
Your "icon_attributes" is not passing the id of the icon along.
"icons_attributes"=>{"0"=>{"id"=>"", "icon"=>"market_indicator", "rotation"=>"0", "top"=>"", "_destroy"=>"false"}, "id"=>"87"}
You'll notice the id is blank. Since the id is blank rails thinks it is a new record and thus creates a new icon. The error lies in how you have made your form.
I added the file_validators gem to my app and called the validation in my vehicle_image.rb model that you can see below.
After attempting to upload a new image, I receive a unpermitted parameters message in the Rails console. I suspect the error has something to do with strong parameters? I attempted to assign the image prior to the if #vehicle.save but was unsuccessful.
edit:vehicle_image.rb
class VehicleImage < ActiveRecord::Base
belongs_to :vehicle
validates :image, file_size: { less_than_or_equal_to: 500.kilobytes, message: "Image must be less that 500kbs" }
mount_uploader :image, ImageUploader
def set_to_primary_and_save
VehicleImage.where(vehicle: vehicle).update_all(primary: false)
self.primary = true
save
end
end
stack trace
Started PATCH "/vehicles/65" for 127.0.0.1 at 2015-11-05 14:03:06 -0500
Processing by VehiclesController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"o6W0JsKzxGe9D1z6VA2WeXW3b4JBVsfvYDvM4ANf4Eo5wVFBn1e31y+oKdLIsWFy41WXeW1BUenCzKTE6tni1Q==", "vehicle"=>{"make"=>"Pontiac", "model"=>"GTO", "year"=>"1967", "production_date"=>"January 5, 1968", "engine"=>"454 ", "transmission"=>"4 Speed Muncie", "trim"=>"Red", "color"=>"Black", "options"=>"Tinted Glass, Hurst Shifter", "location"=>"Milton, Ontario", "description"=>"sdfsdfdsf", "vehicle_images"=>{"image"=>[#<ActionDispatch::Http::UploadedFile:0x007f4adfa1c738 #tempfile=#<Tempfile:/tmp/RackMultipart20151105-7060-d0j694.jpg>, #original_filename="switzerland-3840x2160-alps-mountauns-stars-night-5713.jpg", #content_type="image/jpeg", #headers="Content-Disposition: form-data; name=\"vehicle[vehicle_images][image][]\"; filename=\"switzerland-3840x2160-alps-mountauns-stars-night-5713.jpg\"\r\nContent-Type: image/jpeg\r\n">], "image_cache"=>""}}, "commit"=>"Save", "id"=>"65"}
Vehicle Load (0.1ms) SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."id" = ? ORDER BY created_at DESC LIMIT 1 [["id", 65]]
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 6]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 6]]
Unpermitted parameter: vehicle_images
(0.0ms) begin transaction
(0.0ms) commit transaction
(0.1ms) begin transaction
SQL (0.3ms) INSERT INTO "vehicle_images" ("image", "vehicle_id", "primary", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?) [["image", "switzerland-3840x2160-alps-mountauns-stars-night-5713.jpg"], ["vehicle_id", 65], ["primary", "t"], ["created_at", "2015-11-05 19:03:06.685379"], ["updated_at", "2015-11-05 19:03:06.685379"]]
(18.5ms) commit transaction
Redirected to http://localhost:3000/vehicles/65
Completed 302 Found in 2474ms (ActiveRecord: 19.4ms)
vehicles_controller.rb
class VehiclesController < ApplicationController
def index
scope = Vehicle.approved
scope = scope.filter_by_make(params[:makes]) if params[:makes].present?
scope = scope.filter_by_year(params[:years]) if params[:years].present?
#vehicles = scope
authorize #vehicles
end
def show
#vehicle = Vehicle.find(params[:id])
#primary_image, #images = #vehicle.primary_and_all_vehicle_images
end
def new
#vehicle = Vehicle.new
authorize #vehicle
end
def create
#vehicle = Vehicle.new(vehicle_params)
#vehicle.user = current_user
authorize #vehicle
if #vehicle.save
add_vehicle_images if params[:vehicle][:vehicle_images][:image]
create_registry_request(#vehicle)
flash[:notice] = "The Vehicle was sent to the Administrator for Approval. You will be notified in your Dashboard if your vehicle was approved or denied."
redirect_to current_user
else
flash[:error] = "There was an error saving the Vehicle to the Registry. Please try again."
render :new
end
end
def edit
#vehicle = Vehicle.find(params[:id])
authorize #vehicle
#primary_image, #images = #vehicle.primary_and_all_vehicle_images
end
def update
#vehicle = Vehicle.find(params[:id])
authorize #vehicle
if #vehicle.update_attributes(vehicle_params)
add_vehicle_images if params[:vehicle][:vehicle_images][:image]
flash[:notice] = "The Vehicle entry was updated."
redirect_to #vehicle
else
flash[:error] = "There was an error updating the Vehicle. Please try again."
#primary_image, #images = #vehicle.primary_and_all_vehicle_images
render :edit
end
end
def re_edit
#vehicle = Vehicle.find(params[:id])
authorize #vehicle
#primary_image, #images = #vehicle.primary_and_all_vehicle_images
end
def resubmit
#update and new request
#vehicle = Vehicle.find(params[:id])
authorize #vehicle
if #vehicle.update_attributes(vehicle_params)
add_vehicle_images if params[:vehicle][:vehicle_images][:image]
Vehicle.transaction do
#vehicle.active_registry_request.archive
create_registry_request(#vehicle)
end
flash[:notice] = "The Vehicle entry was updated and sent to the Administrator. Please wait for Approval."
redirect_to #vehicle
else
flash[:error] = "There was an error updating the Vehicle. Please try again."
#primary_image, #images = #vehicle.primary_and_all_vehicle_images
render :re_edit
end
end
private
def vehicle_params
params.require(:vehicle).permit(:make, :model, :year, :production_date, :engine, :transmission, :trim, :color, :options, :location, :description, vehicle_images_attributes: [:image])
end
def add_vehicle_images
params[:vehicle][:vehicle_images][:image].each_with_index do |img, i|
image = #vehicle.vehicle_images.build(image: img)
image.primary = true if i == 0
image.save!
end
end
def create_registry_request(vehicle)
RegistryRequest.create!(vehicle: vehicle)
end
end
Parameters: {
# ... snip ...
"vehicle" => { # ... snip ...
"vehicle_images"=>{ # ... snip ... }
}
}
But the parameter whitelist is specified as:
params.require(:vehicle).permit(..., vehicle_images_attributes: [:image])
"vehicle_images" is not equal to "vehicle_images_attributes", thus the message:
Unpermitted parameter: :vehicle_images
Either the form or the whitelist needs to change so that the key in the params hash matches the argument in permit.
Normally the _attributes suffix is added to the form when we use accepts_nested_attributes_for, but you don't appear to be doing that.
I am trying to get token fields to work based on the RailsCasts episode #258 and I can submit the form just fine. When I go back to my main page, my tag does not appear.
Looking at the server logs on my local machine, I can see...
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = 'zviHK_WgeHLYUAtbKdzyfQ' LIMIT 1
Activity Load (0.1ms) SELECT "activities".* FROM "activities" WHERE "activities"."id" = ? LIMIT 1 [["id", "6"]]
(0.0ms) begin transaction
Tag Load (3.1ms) SELECT "tags".* FROM "tags" WHERE "tags"."id" = ? LIMIT 1 [["id", 1]]
Tag Load (0.1ms) SELECT "tags".* FROM "tags" INNER JOIN "activities_tags" ON "tags"."id" = "activities_tags"."tag_id" WHERE "activities_tags"."activity_id" = 6
(0.3ms) INSERT INTO "activities_tags" ("activity_id", "tag_id") VALUES (6, 1)
(0.2ms) DELETE FROM "activities_tags" WHERE "activities_tags"."activity_id" = 6 AND "activities_tags"."tag_id" IN (1)
(1.6ms) commit transaction
The 'INSERT INTO "activities_tags"...' has the correct values but, for some reason, it is deleting the record immediately and I can't figure out where that would be coming into play.
My Activity controller:
class ActivitiesController < ApplicationController
...
def update
#activity = Activity.find(params[:id])
params[:activity][:tag_ids] ||= []
respond_to do |format|
if #activity.update_attributes(params[:activity])
flash[:success] = "Activity was successfully updated!"
format.html { redirect_to master_resumes_url }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #activity.errors, status: :unprocessable_entity }
end
end
end
...
end
My Activity.rb:
class Activity < ActiveRecord::Base
attr_accessible :end_date, :organization, :position, :start_date, :user_id,
:tag_tokens, :tag_ids
attr_reader :tag_tokens
has_and_belongs_to_many :tags
belongs_to :user
def tag_tokens=(ids)
temp = ids.split(",")
self.tag_ids = temp[0]
end
end
My activity form:
<%= form_for(#activity) do |f| %>
<h4>Choose Tags</h4>
<%= f.label :tag_tokens, "Tags" %>
<%= f.text_field :tag_tokens, "data-pre" => #activity.tags.map(&:attributes).to_json %>
<p><%= link_to "Create A New Tag", new_tag_path %></p>
<% end %>
Finally, my activities.js:
$(function() {
$("#activity_tag_tokens").tokenInput("/tags.json", {
crossDomain: false,
prePopulate: $("#activity_tag_tokens").data("pre"),
theme: "facebook",
allowCustomEntry: true
});
});
Any thoughts?
1) Do you have a before_filter named anywhere?
2) Do you have :activities_tags named in attr_accessible?