rails to_param is not working - ruby-on-rails

I am following Ryan Bates railscasts video of friendly url. I am trying to implement that on my Category model by overriding the to_parammethod.
Seems like it's not working, or I am missing something.
Below is my url before overriding:
localhost:3000/search?category_id=1
After overriding the to_param the url remained same.
Following is my code:
Category model
class Category < ActiveRecord::Base
enum status: { inactive: 0, active: 1}
acts_as_nested_set
has_many :equipments, dependent: :destroy
has_many :subs_equipments, :foreign_key => "sub_category_id", :class_name => "Equipment"
has_many :wanted_equipments, dependent: :destroy
has_many :services, dependent: :destroy
validates :name, presence: true
validates_uniqueness_of :name,message: "Category with this name already exists", scope: :parent_id
scope :active, -> { where(status: 1) }
def sub_categories
Category.where(:parent_id=>self.id)
end
def to_param
"#{id} #{name}".parameterize
end
end
Controller
def search_equipments
begin
if (params.keys & ['category_id', 'sub_category', 'manufacturer', 'country', 'state', 'keyword']).present?
if params[:category_id].present?
#category = Category.active.find params[:category_id]
else
#category = Category.active.find params[:sub_category] if params[:sub_category].present?
end
#root_categories = Category.active.roots
#sub_categories = #category.children.active if params[:category_id].present?
#sub_categories ||= {}
Equipment.active.filter(params.slice(:manufacturer, :country, :state, :category_id, :sub_category, :keyword)).order("#{sort_column} #{sort_direction}, created_at desc").page(params[:page]).per(per_page_items)
else
redirect_to root_path
end
rescue Exception => e
redirect_to root_path, :notice => "Something went wrong!"
end
end
route.rb
get "/search" => 'welcome#search_equipments', as: :search_equipments
index.html.erb
The line which is generating the url
<%= search_equipments_path(:category_id => category.id ) %>

You are generating URLs in such a way as to ignore your to_param method. You're explicitly passing a value of only the ID to be used as the :category_id segment of your URLs. If you want to use your to_param-generated ID, then you need to just pass the model to the path helper:
<%= search_equipments_path(category) %>

Related

Rails 5: Nested attributes aren't updated for model

I can't get rails to update my nested attributes, though regular attributes work fine. This is my structure:
unit.rb:
class Unit < ApplicationRecord
has_many :unit_skill_lists
has_many :skill_lists, through: :unit_skill_lists, inverse_of: :units, autosave: true
accepts_nested_attributes_for :skill_lists, reject_if: :all_blank, allow_destroy: true
end
unit_skill_list.rb:
class UnitSkillList < ApplicationRecord
belongs_to :unit
belongs_to :skill_list
end
skill_list.rb:
class SkillList < ApplicationRecord
has_many :unit_skill_lists
has_many :units, through: :unit_skill_lists, inverse_of: :skill_lists
end
And this is (part of) the controller:
class UnitsController < ApplicationController
def update
#unit = Unit.find(params[:id])
if #unit.update(unit_params)
redirect_to edit_unit_path(#unit), notice: "Unit updated"
else
redirect_to edit_unit_path(#unit), alert: "Unit update failed"
end
end
private
def unit_params
unit_params = params.require(:unit).permit(
...
skill_list_attributes: [:id, :name, :_destroy]
)
unit_params
end
end
The relevant rows in the form (using formtastic and cocoon):
<%= label_tag :skill_lists %>
<%= f.input :skill_lists, :as => :check_boxes, collection: SkillList.where(skill_list_type: :base), class: "inline" %>
Any idea where I'm going wrong? I have tried following all guides I could find but updating does nothing for the nested attributes.
Edit after help from Vasilisa:
This is the error when I try to update a Unit:
ActiveRecord::RecordInvalid (Validation failed: Database must exist):
This is the full unit_skill_list.rb:
class UnitSkillList < ApplicationRecord
belongs_to :unit
belongs_to :skill_list
belongs_to :database
end
There is no input field for "database". It is supposed to be set from a session variable when the unit is updated.
If you look at the server log you'll see something like skill_list_ids: [] in params hash. You don't need accepts_nested_attributes_for :skill_lists, since you don't create new SkillList on Unit create/update. Change permitted params to:
def unit_params
params.require(:unit).permit(
...
skill_list_ids: []
)
end
UPDATE
I think the best options here is to set optional parameter - belongs_to :database, optional: true. And update it in the controller manually.
def update
#unit = Unit.find(params[:id])
if #unit.update(unit_params)
#unit.skill_lists.update_all(database: session[:database])
redirect_to edit_unit_path(#unit), notice: "Unit updated"
else
redirect_to edit_unit_path(#unit), alert: "Unit update failed"
end
end

Rails use an instance variable in controller or generate within a helper?

So I'm trying to build out on an Invoice page the past_due_amount where I'm trying to find only the invoices for the current account, that are not paid off, and should be in the past.
So roughly I have:
past_due_amount = Invoice.where(account: invoice.account, status: :unpaid).where('date < ? ', invoice.date).map(&:due).sum
For additional context here are the models involved:
Invoice:
class Invoice < ApplicationRecord
belongs_to :account
has_many :line_items, dependent: :destroy
has_many :payment_destinations, dependent: :destroy
has_many :prorated_fees, dependent: :nullify
enum status: [:unpaid, :paid]
validates :date, presence: true
validates :period_start, :period_end,
uniqueness: { scope: :account, allow_blank: true }, on: :create
validate :start_is_before_end
DAYS_DUE_AFTER_DATE = 14.days
scope :descending, -> { order(date: :desc) }
scope :ascending, -> { order(date: :asc) }
scope :due, -> { unpaid.where(arel_table[:date].lteq(Time.zone.today - DAYS_DUE_AFTER_DATE)) }
def total
if persisted?
line_items.sum(:amount)
else
line_items.map(&:amount).sum
end
end
end
Account:
class Account < ApplicationRecord
belongs_to :customer
belongs_to :property_address,
class_name: Address.to_s,
dependent: :destroy,
required: false
[:products, :account_changes, :equipments,
:payments, :invoices].each do |assoc|
has_many assoc, dependent: :destroy
end
accepts_nested_attributes_for :property_address
delegate :street, :city, :state, :zip,
to: :property_address, allow_nil: true
delegate :email, :full_name, to: :customer
enum status: [:staged, :active, :inactive]
scope :active_or_staged, -> { where(status: [:staged, :active]) }
scope :past_due, lambda {
joins(:invoices)
.where(
Invoice.arel_table[:status].eq(:unpaid)
.and(Invoice.arel_table[:date].lt(Time.zone.today - 14.days))
).distinct
}
scope :search, lambda { |term|
joins(:customer)
.where(
arel_table[:account_num].matches("%#{term}%")
.or(Customer.arel_search(term))
)
}
end
With the rough code in place I decided to build out a instance variable on the InvoicesController within the show method as below:
def show
#invoice = Invoice.find_by!(id: params[:id], account: current_customer.account_ids)
#account = #invoice.account
#past_due_amount = Invoice.where(account: #account, status: :unpaid).where('date < ?', #invoice.date).map(&:due).sum
end
No errors appear but that's not saying much since the examples I have are poor, at best. But my question is...should I actually be putting this in a helper instead of the show method on an InvoicesController or even in the model?
EDIT:
I've also tried putting in my Invoice model:
def self.past_due_amount
Invoice.where(account: #account, status: :unpaid).where('date < ?', #invoice.date).map(&:due).sum
end
Then in my InvoicesController:
def show
#invoice = Invoice.find_by!(id: params[:id], account: current_customer.account_ids)
#account = #invoice.account
#past_due_amount = Invoice.past_due_amount
end
End up getting undefined method `date' for #invoice.date.
The best way is to create a method past_due_amount in the InvoicesHelper
module InvoicesHelper
def past_due_amount
Invoice.where(account: #account, status: :unpaid).where('date <?', #invoice.date).map(&:due).sum
end
end
In you controller just initialize all the instance variables
def show
#invoice = Invoice.find_by!(id: params[:id], account: current_customer.account_ids)
#account = #invoice.account
end
In the view you should use: <%= past_due_amount > to show your data
Create an instance method in Account model
def past_due_amount
invoices.map(&:due).sum
end
and then from view you can all it #account.past_due_amount. no need to create extra instance variable in controller action
So I sort of used Patrick's answer but it was actually failing so I switched to passing invoice as params.
Helper
module InvoicesHelper
def past_due_amount(invoice)
Invoice.where(account: invoice.account, status: :unpaid).where('date < ?', invoice.date).map(&:due).sum
end
end
Then in my view:
<% if past_due_amount(invoice).positive? %>
<p><%= number_to_currency past_due_amount(invoice) %></p>
<% end %>

Rails render object in json with model associations

I'm trying to render the user model along with the posts model, but I'm having trouble figuring out what the syntax for it would be
class Post < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
has_many :posts, dependent: :destroy
end
Controller
def map_locations
#posts = Post.where.not(location: [nil, ""])
render :json => #posts.as_json(only: [:topic, :location, :latitude, :longitude],)
end
Output:
[{"topic":"Garret ATX","location":"Hornitos, CA, USA","latitude":37.5021592,"longitude":-120.238241}]
Desired Output:
[{"user_name":"Randy","topic":"Garret ATX","location":"Hornitos, CA, USA","latitude":37.5021592,"longitude":-120.238241}
The user model has #user.user_name which is the one I need for each post.
How do I render the user associated with each post?
I hope this works
def map_locations
#posts = Post.where.not(location: [nil, ""])
render :json => #posts.as_json(:only => [:topic, :location, :latitude, :longitude], :include => {:user => {:only => :user_name}})
end
Please check the apidock for more details.

How to create nested models from API request?

I've a Rails API and I've two models:
class Event < ActiveRecord::Base
belongs_to :category
has_many :event_categories
has_many :events, through: :event_categories
attr_accessible :title, :description, :event_categories_attributes
accepts_nested_attributes_for :event_categories
end
and
class EventCategory < ActiveRecord::Base
belongs_to :event
belongs_to :category
attr_accessible :category_id, :event_id, :principal
validates :event, :presence => true
validates :category, :presence => true
validates_uniqueness_of :event_id, :scope => :category_id
end
In a first moment, EventCategory didn't exist so I created Event resources sending params like event[title]='event1', event[description] = 'blablbla' thought POST REST request.
My API EventsController was like this (I haven't a new method because I don't need views):
def create
#event = Event.create(params[:event])
if #event
respond_with #event
else
respond_with nil, location: nil, status: 404
end
end
This way worked correctly for me. Now, with the new EventCategory model I don't know how I could create EventCategories models at the same time.
I've trying this... but it doesn't work:
def create
#event = Event.new(params[:event])
#event.event_categories.build
if #event.save
respond_with #event
else
respond_with nil, location: nil, status: 404
end
end
Rails told me:
{
"event_categories.event": [
"can't be blank"
],
"event_categories.category": [
"can't be blank"
]
}
I send the category_id like this:
event[event_categories_attributes][0][category_id] = 2
Any ideas?
In your create action, instead of this:
#event.event_categories.build
Try this:
#event.event_categories = EventCategory.new do |ec|
ec.event = #event
ec.category = the_cattegory_you_want_to_specify
# You need both of these as you are validating the presence of event AND category
end

How to test a nested form with RSpec and FactoryGirl?

In my Rails project I have a User that can have many Projects which in turn can have many Invoices. Each Invoice can have many nested Items.
class Invoice < ActiveRecord::Base
attr_accessible :number, :date, :recipient, :project_id, :items_attributes
belongs_to :project
belongs_to :user
has_many :items, :dependent => :destroy
accepts_nested_attributes_for :items, :reject_if => :all_blank, :allow_destroy => true
validates :project_id, :presence => true
def build_item(user)
items.build(:price => default_item_price(user), :tax_rate => user.preference.tax_rate)
end
def set_number(user)
self.number ||= (user.invoices.maximum(:number) || 0).succ
end
end
class InvoicesController < ApplicationController
def new
#invoice = current_user.invoices.build(:project_id => params[:project_id])
#invoice.build_item(current_user)
#invoice.set_number(current_user)
#title = "New invoice"
end
def create
#invoice = current_user.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice created"
redirect_to invoices_path
else
render :new
end
end
end
Now, I am trying to test the creation of invoices with RSpec and FactoryGirl and all tests pass except for the ones related to the POST create action, such as:
it "saves the new invoice in the database" do
expect {
post :create, invoice: attributes_for(:invoice, project_id: #project, items_attributes: [ attributes_for(:item) ])
}.to change(Invoice, :count).by(1)
end
It keeps giving me this error:
Failure/Error: expect {
count should have been changed by 1, but was changed by 0
Can anybody tell me why this happens?
This is my factories.rb which I use to fabricate objects:
FactoryGirl.define do
factory :invoice do
number { Random.new.rand(0..1000000) }
recipient { Faker::Name.name }
date { Time.now.to_date }
association :user
association :project
end
factory :item do
date { Time.now.to_date }
description { Faker::Lorem.sentences(1) }
price 50
quantity 2
tax_rate 10
end
end
Can anybody help?
Thanks...

Resources