Destroying after save if association is blank? - ruby-on-rails

How would I destroy a Product if it doesn't belong to a Store after_save?
class Product < ActiveRecord::Base
attr_accessible :price, :name, :product_store
belongs_to :store
attr_reader :product_store
# I need this to do also "p.product_store.blank?"
after_save { |p| p.destroy if p.name.blank? && p.price.blank? }
def product_store=(id) # using Jquery TokenInput so only needed this.
self.store_id = id
end
end
I've tried a couple of different approaches like:
after_save { |p| p.destroy if p.name.blank? && p.price.blank? && p.product_store.blank? }
after_save { |p| p.destroy if p.name.blank? && p.price.blank? && p.store.id.blank? }
after_save { |p| p.destroy if p.name.blank? && p.price.blank? && p.store_id.blank? }
after_save { |p| p.destroy if p.name.blank? && p.price.blank? && p.store.blank? }
But these didn't work so I ask for your help on how it would be done?
Here is my form and controller: https://gist.github.com/1472629

how about use ActiveModel::Validations?
you don't want to save record if some attribute not filled.so you need validation.
class Product < ActiveRecord::Base
validates_presence_of :name, :price, :store_id
end
Edit:
your code looks want to create muti products use common attributes.
may be params like this:
{"products" => {:1 => {:name => "good", :desc => "sss"}}, {:2 => {:name => "good", :desc => "tt"}}, "product" => {:price => "10"} }
controller
def create_multiple
#products = params[:products].values.collect do |up|
Product.new(up.merge(params[:product]))
end
if #products.each(&:save)
redirect_to :back, :notice => "Success!"
else
render :new
end
end
Validation do not need change. but the form will a bit complicate.
form_tag "/products" do
%p
= text_field_tag "product[price]", #products.first.price
= #products.first.errors[:price]
- #products.each_with_index do |product, idx|
= text_field_tag "products[#{idx}][name]", product.name
= product.errors[:name]
= submit_tag :submit
the code is simple, you can enhance the view yourself.

This seems more like a validation issue. If it's invalid without a Store, then you shouldn't save it to the DB without a Store reference at all.
If you want to destroy the Product you should do that directly, rather than saving a nil reference to a store, trying to catch that, and then destroying it as a result.
By trying to destroy a record when its association is incorrect, you're essentially saying, "This in an invalid state, and shouldn't be in the database in the first place."

Related

Ruby-On-Rails model level class array

I'm trying to think of a best solution for following scenario. I've a model called an 'Article' with an integer field called 'status'. I want to provide class level array of statuses as shown below,
class Article < ActiveRecord::Base
STATUSES = %w(in_draft published canceled)
validates :status, presence: true
validates_inclusion_of :status, :in => STATUSES
def status_name
STATUSES[status]
end
# Status Finders
def self.all_in_draft
where(:status => "in_draft")
end
def self.all_published
where(:status => "published")
end
def self.all_canceled
where(:status => "canceled")
end
# Status Accessors
def in_draft?
status == "in_draft"
end
def published?
status == "published"
end
def canceled?
status == "canceled"
end
end
So my question is if this is the best way to achieve without having a model to store statuses? And secondly how to use these methods in ArticlesController and corresponding views? I'm struggling to understand the use of these methods. To be specific, how to do following?
article = Article.new
article.status = ????
article.save!
or
<% if article.in_draft? %>
<% end %>
I greatly appreciate any sample code example. I'm using rails 4.0.0 (not 4.1.0 which has enum support).
You could define all the methods using define_method, and use a hash instead of an array:
STATUSES = {:in_draft => 1, :published => 2, :cancelled => 3}
# Use the values of the hash, to validate inclusion
validates_inclusion_of :status, :in => STATUSES.values
STATUSES.each do |method, val|
define_method("all_#{method)") do
where(:status => method.to_s)
end
define_method("#{method}?") do
self.status == val
end
end
In that way, you can add statuses in the future without needing to create the methods manually. Then you can do something like:
article = Article.new
article.status = Article::STATUSES[:published]
...
article.published? # => true

Nested Form: "Cannot call create unless the parent is saved" error

I have a nested form where each post has and belongs to many locations. Adding locations to a post with the nested form works just fine. However when I click 'edit' and change anything except a location I get passed the cannot call create unless the parent is saved error.
I'm guessing this is something to do with the bit of code in my model which runs through the location_attributes being submitted and checks them for uniqueness however I have no idea how to fix this. I have tried just #post.save! before I do find_or_initialize_by_name in the controller but get the same error.
Code is below. I know this is pretty unique to me but any suggestions would be great!
posts_controller.rb
...
def update
location_set = params[:post].delete(:locations_attributes) unless params[:post][:locations_attributes].blank?
#post = Post.find(params[:id])
#post.locations = Location.find_or_initialize_location_set(location_set) unless location_set.nil?
if #post.update_attributes(params[:post])
redirect_to #post, notice: 'Blog post was successfully updated.'
else
render action: "edit"
end
end
...
location.rb (model)
include PublicActivity::Model
tracked
tracked owner: Proc.new{ |controller, model| controller.current_user }
include FriendlyId
friendly_id :name
after_save { |location| location.destroy if location.name.blank? }
after_save { |venue| venue.destroy if venue.name.blank? }
has_many :location_post, :order => "position"
has_many :posts, :through => :location_post, :order => 'location_posts.position'
attr_accessible :latitude, :longitude, :name, :post_id, :notes, :asset, :assets_attributes, :venues_attributes
attr_accessor :_destroy, :position, :location_post_id
def self.find_or_initialize_location_set(location_set)
#create a locations array
locations = []
locations = locations.delete_if { |elem| elem.flatten.empty? }
location_set.each do |key, location|
if location.delete(:_destroy) == "1"
locations.delete_if {|elem| elem[:name] == location[:name]}
else
locations << find_or_initialize_by_name(location)
#REMINDER In rails 4 this must be written as where(...).first_or_create
end
end
locations
end
*EDIT - The Error *
app/models/location.rb:10:in `block in <class:Location>'
app/controllers/blogit/posts_controller.rb:97:in `update'
This was caused by a stupid mistake.
The line after_save { |venue| venue.destroy if venue.name.blank? } is no longer relevant.
I'm an idiot and didn't read the error properly. Thanks for all those who helped.

How should I refactor create_unique methods in Rails 3?

I have following ugly create_unique method in few models ex:
def self.create_unique(p)
s = Subscription.find :first, :conditions => ['user_id = ? AND app_id = ?', p[:user_id], p[:app_id]]
Subscription.create(p) if !s
end
And then in controllers #create actions I have
s = Subscription.create_unique({:user_id => current_user.id, :app_id => app.id})
if s
raise Exceptions::NotAuthorized unless current_user == s.user
#app = s.app
s.destroy
flash[:notice] = 'You have been unsubscribed from '+#app.name+'.'
redirect_to '/'
end
did you try dynamic finders ?
find_or_initialize_by_user_id_and_app_id
find_or_create_by_user_id_and_app_id
first_or_initialize...
first_or_create....
check manual http://guides.rubyonrails.org/active_record_querying.html#dynamic-finders
also option is to create validation rule for check unique value
class Subscription < ActiveRecord::Base
validates_uniqueness_of :user_id, :scope => :app_id
end
then
sub = Subscription.new({:user_id => current_user.id, :app_id => app.id})
sub.valid? #false
You can use validates_uniquness_of :app_id,:scope=>:user_id so app id is uniq for respected user_id

Accessing the child instance in a RABL template

I have a RABL template as shown below
object #user
attributes :name
child :contacts do
# does not work
if contact.is_foo?
attributes :a1, :a2
else
attributes :a3, :a4
end
end
How do I access the Contact object in the child block of the template? I need to perform some conditional logic on the child instance.
You can access the current object by declaring the block parameter.
object #user
attributes :name
child :contacts do |contact|
if contact.is_foo?
attributes :a1, :a2
else
attributes :a3, :a4
end
end
Old answer
I ended up using the root_object method, which returns the data object in a given context.
object #user
attributes :name
child :contacts do
if root_object.is_foo?
attributes :a1, :a2
else
attributes :a3, :a4
end
end
Another approach to keep things DRY:
contacts/show.json.rabl
object #contact
node do |contact|
if contact.is_foo?
{:a1 => contact.a1, :a2 => contact.a2}
else
{:a3 => contact.a3, :a4 => contact.a4}
end
end
users/show.json.rabl
object #user
attributes :name
child :contacts do
extends 'contacts/show'
end
Here's one way:
child :contacts do
node(:a1, :if => lambda { |c| c.is_foo? }
node(:a2, :if => lambda { |c| c.is_foo? }
node(:a3, :unless => lambda { |c| c.is_foo? }
node(:a4, :unless => lambda { |c| c.is_foo? }
end
Not exactly the same but one possibility, another is:
node :contacts do |u|
u.contacts.map do |c|
if contact.is_foo?
partial("contacta", :object => c)
# or { :a1 => "foo", :a2 => "bar" }
else
partial("contactb", :object => c)
# or { :a3 => "foo", :a4 => "bar" }
end
end
end
I know it's a late reply but came across the similar problem so thought of answering.
It's more like a hack but works.
When two variables are used as block argument contact and a random variable x, contact refers to an object of the collection
When one variable is used in block argument, it renders collection object
object #user
attributes :name
child :contacts do |contact, x|
if contact.is_foo?
attributes :a1, :a2
else
attributes :a3, :a4
end
end

How to update a model's attribute with a virtual attribute?

I have a model named UserPrice which has the attribute :purchase_date(a date_select) in its table. With my form I can create multiple user_prices at once but for user convenience I made a virtual attribute inside of my UserPrice model called :all_dates that's also a date_select field and its job is to be the replacement of the :purchase_dates so users only have to select the :all_dates field for the date.
Problem & Question
The :all_dates field is not updating the :purchase_date fields of my user_prices that are being created. What do I need to do in order to get my :all_dates field to update the :purchase_date fields of my new UserPrices?
Does anyone have any tips on how to do this?
Parameters
Parameters:
"user_price"=> {
"all_dates(2i)"=>"10",
"all_dates(3i)"=>"27",
"all_dates(1i)"=>"2011"
},
"user_prices"=>
{
"0"=>{"product_name"=>"Item1", "store"=>"Apple Store","price"=>"6"},
"1"=>{"product_name"=>"Item2", "store"=>"Apple Store", "price"=>"7"}
},
"commit"=>"Submit"}
Code
class CreateUserPrices < ActiveRecord::Migration
def self.up
create_table :user_prices do |t|
t.decimal :price
t.integer :product_id
t.date :purchase_date
t.timestamps
end
end
end
I took out the :purchase_date field so it isn't inside of the user_price loop.
<%= form_tag create_multiple_user_prices_path, :method => :post do %>
<%= date_select("user_price", "all_dates" ) %>
<% #user_prices.each_with_index do |user_price, index| %>
<%= fields_for "user_prices[#{index}]", user_price do |up| %>
<%= render "user_price_fields", :f => up %>
<% end %>
<% end %>
<% end %>
class UserPrice < ActiveRecord::Base
attr_accessible :price, :product_name, :purchase_date, :all_dates, :store
attr_accessor :all_dates
after_save :save_all_dates_to_user_prices
composed_of :all_dates, :class_name => "DateTime",
:mapping => %w(Time to_s),
:constructor => Proc.new { |item| item },
:converter => Proc.new { |item| item }
def user_prices
#user_prices = Array.new() { UserPrice.new }
end
protected
def save_all_dates_to_user_prices
if !self.all_dates.nil?
self.user_prices.each {|up| up.purchase_date = self.all_dates if up.new_record?}
end
end
class UserPricesController < ApplicationController
def new
#user_prices = Array.new(5) { UserPrice.new }
end
def create_multiple
#user_prices = params[:user_prices].values.collect { |up| UserPrice.new(up) }
if #user_prices.all?(&:valid?)
#user_prices.each(&:save!)
redirect_to :back, :notice => "Successfully added prices."
else
redirect_to :back, :notice => "Error, please try again."
end
end
This is a case of trying to do in a model what is better left to the controller. All you're trying to do here is to auto-assign a certain attribute on creation from a parameter not directly tied to your model. But you're not even passing that extra parameter to the model anywhere - you're creating your model instances from the user_prices parts of the parameter hash, but the user_price sub-hash is not used anywhere. In any case, this is behavior that is more closely related to the view and action taken than the model, so keep it in the controller.
Try this:
Throw out the virtual attribute, and get rid of the whole after_save callback stuff
Throw away the user_prices method in your model
Change the all_dates attribute name back to purchase_date in the form
Then your parameter hash should look like this:
{"user_price"=> {
"purchase_date(2i)"=>"10",
"purchase_date(3i)"=>"27",
"purchase_date(1i)"=>"2011"
},
"user_prices"=>
{
"0"=>{"product_name"=>"Item1", "store"=>"Apple Store","price"=>"6"},
"1"=>{"product_name"=>"Item2", "store"=>"Apple Store", "price"=>"7"}
}}
All that's left to do is to merge the single user_price attributeS into each user_prices sub-hash in your create_multiple action. Replace the first line in that action with this:
#user_prices = params[:user_prices].values.collect do |attributes|
UserPrice.new(attributes.merge(params[:user_price]))
end
I'm not sure why you are even using that virtual attribute is there more to this implementation? If you are just trying to save an associated model, you might simply want a accepts_nested_attributes_for :user_prices in your User model
This works great and many developers use this method, so it's nice to know for working on other projects as well as for the people who might end up maintaining yours.
http://railscasts.com/episodes/196-nested-model-form-part-1
http://railscasts.com/episodes/197-nested-model-form-part-2

Resources