I'm trying to create an application with Ruby on Rails 4.2.1 and PostgreSQL.
I have the following models.
class Builder < ActiveModel
has_many :bills
has_many :bills_partials
end
class Bill < ActiveModel
belongs_to :builder
has_many :bills_partials
accepts_nested_attributes_for :bills_partial, :reject_if => :all_blank, :allow_destroy => true
end
class BillPartial < ActiveModel
belongs_to :builder
belongs_to :bills
end
and the following actions
def new
bill.bills_partials.build
end
def create
#bill = Bill.scoped_to(current_builder).new(bill_partial_params)
if #bill.save
redirect_to bills_path, flash: { success: t('flash.generic.female.created', model: Bill.model_name.human) }
else
render :new
end
end
private
def bill
#bill ||= Bill.scoped_to(current_builder).find_by(id: uuid_or_nil(params[:id])) || Bill.scoped_to(current_builder).new
end
helper_method :bill
def bill_params
params
.require(:bill)
.permit(:description,
:value,
:date,
bills_partials_attributes: [:id,
:due_date,
:valor,
:_destroy])
end
The output for the params is:
{
"description"=>"some desc",
"value"=>"123",
"date"=>"10/10/2010",
"bills_partials_attributes" =>
{
"0" =>
{
"due_date"=>"14/03/2003",
"value"=>"123"
},
"1" =>
{
"due_date"=>"14/03/2003",
"value"=>"123"
}
}
}
The thing is, I have to add current_builder.id to every model, Bill and BillPartial for instance. What is the best way to add the current_builder.id to the builder_id on the BillPartial model?
Well, after looking for something, I solved this by defining
before_create do
self.builder_id = bill.try(:builder_id)
end
On the BillPartial model.
Related
I add unique index, but record dont save, validation error. I need update tags in my post,existing tags adding to tags with new id, but I need existing tags not to be added
class Tag < ApplicationRecord
has_many :tags_posts
has_many :tags, through: :tags_posts
accepts_nested_attributes_for :tags_posts, :allow_destroy => true, :update_only=>true
end
class TagsPost < ApplicationRecord
belongs_to :post
belongs_to :tag
accepts_nested_attributes_for :tag, :allow_destroy => true, :update_only=>true
end
controller code:
def update
#resource=resource_class.find(params[:id])
#resource.assign_attributes(resource_params)
if #resource.save
render json: #resource.as_json(as_json_resource)
else
render json: {errors:#resource.errors}, status: :unprocessable_entity
end
end
def resource_class
Post
end
def resource_params
params.require(:post).permit(:user_id,:title,:category_id, :content, :date_of_publication, tags_posts_attributes: [tag_attributes: [:name]] )
end
Add id to tag_attributes
params.require(:post).permit(:user_id,..., tags_posts_attributes: [tag_attributes: [:id, :name]] )
That will prevent it from adding again.
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 %>
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) %>
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
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...