I have a problem with Rails 4 and validation. Let's consider this model:
# has integer attribute 'order'
class Item < ActiveRecord::Base
belongs_to :parent,
validate :orders_must_be_sequence
def orders_must_be_sequence
orders = []
parent.items.each do |i|
orders << i.order
end
orders.sort!
errors.add :order, "is not in a decent order" if orders != (0..orders.length - 1).to_a
end
end
There is a parent model Parent which has_many :items. The idea is, that the items for every parent are ordered; thus for every parent, the n associated items have to have the order attributes 0 to n-1. This is checked with the orders_must_be_in_sequence-validation.
Now for the problem:
In order to reorder the items, the item have to be saved. My idea was, to do that in a transaction like:
ActiveRecord::Base.transaction do
item1.order = 2
item2.order = 3
item3.order = 1
item1.save
item2.save
item3.save
end
But then all save fails due to the failing validation. Also validate: false seems not to be the answer, since I still had to invoke save with validating at the last time in the transaction.
So I want to do a couple of saves in a transaction and I want that all saves are validated at the commit-time. How to do this?
You could shift the validation logic for being in order into the parent itself, for example:
ActiveRecord::Base.transaction do
parent.item1.order = 2
parent.item2.order = 3
parent.item3.order = 1
parent.save
end
class Item < ActiveRecord::Base
belongs_to :parent
end
class Parent < ActiveRecord::Base
has_many :item
validate :orders_must_be_sequence
def orders_must_be_sequence
orders = []
parent.items.each do |i|
orders << i.order
end
orders.sort!
errors.add :order, "is not in a decent order" if orders != (0..orders.length - 1).to_a
end
end
Other than this - I'm curious why you don't just use "sort!" or "order" (to retrieve using ORDER BY SQL), which will guarantee the order, obviating the need to order validation.
Related
I'm in the process of building an application that'll accept a specific date from a user and which will then provide a list of events that have occurred prior to that date. While my code works if I'm looking for a specific value, I'm unable to perform a search when a particular date is passed in. Essentially i'd like to view all of the children elements that occurred previous to that particular date.
My Investments models is this:
class Investment < ApplicationRecord
has_many :transactions
def count
final_quanity = 0
self.transactions.each do |transaction|
final_quanity+= transaction.quantity
end
final_quanity
end
def average_price
total_spent = 0
self.transactions.each do |transaction|
total_spent += transaction.price
end
total_spent
end
end
My transactions model is this:
class Transaction < ApplicationRecord
belongs_to :investment
end
Investment controller
class InvestmentsController < ApplicationController
def index
if params[:date]
# #investments = Investment.includes(:transactions).where(transactions: {quantity:10})
#investments = Investment.includes(:transactions).where("date > ?", params[:date])
else
#investments = Investment.all
end
render json: #investments
end
end
I'd like to only return the specific transactions that occurred before the date entered, by I'm having difficulty returning with a conditional. As you can see from the blocked out code, I'm able to successfully return entries that have a specific value. What's the appropriate way to complete the active record query with a conditional?
What's strange is that the following works:
#transactions = Transaction.where("date < ?", date)
While this doesn't:
#investments = Investment.includes(:transactions).where("date > ?", date)
Instead I get an error message that these's no such column as "date".
Essentially what's the best way to perform this action:
#investments = Investment.includes(:transactions).where( :transactions => {:price => 100})
except determining if a value is greater than or less than instead of just matching.
Using: Rails 4.1.4, PostgreSQL 9.1.13
Hi. I'm have a simple problem, but for some reason I can't get it done. The picture is this:
Models
class Article < ActiveRecord::Base
belongs_to :user
has_many :votes
end
class User < ActiveRecord::Base
has_many :articles
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :article
belongs_to :user, scope: :hotel_id
validates_inclusion_of :value, in: 0..5
validates_uniqueness_of :user_id, :article_id
end
Idea
Each User can Vote for each Article but only once (to avoid multiple voting).
Vote model has a 'value' attribute that is the range 0..10.
ArticlesController except standard CRUD methods has action #showcase which must return 5 articles with the top votes rating from the DB and sort them in the descending order (and render the respective view).
So I understand that the proper way is to write the class method in the Article Model (smth. like "by_top_votes") and use it in the ArticlesController#showcase:
def showcase
#top_five_articles = Article.by_top_votes
end
The problem is that I can't write the proper query to the DB which will: 1)find articles, 2)find all votes of the each article, 3) sum all values of the respective article's votes, 4)sort them (this step I know how to do).
Thank you for reading and for the help.
P.S. Maybe my way to solve problem is almost wrong. If this so, please tell my the right one.
Ok, I've done it by myself. If anybody will stuck with the same problem, here is solution for it.
1. In Vote model summarize the vote's values:
def self.sum_value
sum(:value)
end
2. Add new attribute (and column) to Article - user_rating:integer.
3. In the Article model define two class methods:
# assign user_rating attribute with the sum of all votes values
def set_user_rating
user_rating = self.votes.sum_value
self.update_attribute(:user_rating, user_rating)
end
# get top 5 articles by user_rating value from db
def self.top_by_user_rating
Article.order(:user_rating).reverse_order.limit(5)
end
4. In the ArticlesController define showcase action:
def showcase
#top_articles = Article.top_by_user_rating
end
5. In the VotesController define create action:
def create
#article = Article.find(params[:article_id])
#vote = #article.votes.create(vote_params)
if #vote.save
#article.set_user_rating
redirect_to #article, notice: "Thanks for your vote"
else
.
end
end
It works and tests are passing.
I have three models. Employer, User, Job.
class Employers
has_many :jobs
has_many :users, through: :jobs
end
class User
has_many :jobs
end
class Job
belongs_to :user
belongs_to :employer
end
The Job model has a boolean column named "current". An employers user count is derived by counting all the associated jobs marked 'current'.
I opted to rolled my own cache counter, rather than use active records.
Im using a before filter in the Job model to either increment or decrement a users_count in the Employer model. The increment works as expected, but no matter how I tweak the code...the decrement drops the count by a value of 2.
Im sure I can clean these methods up a bit...there might be some redundancy.
1 Why is the decrement subtracting 2 instead of 1?
2 Can the active record cache counter handle logic like this?
class Job
before_destroy :change_employer_users_counter_cache_after_destroy
before_create :change_employer_users_counter_cache_after_create
before_update :change_employer_users_counter_cache_after_update
def change_employer_users_counter_cache_after_create
Operator.increment_counter(:users_count, self.operator_id) if self.current == true
end
def change_employer_users_counter_cache_after_update
if self.current_changed?
if self.current == true
Operator.increment_counter(:users_count, self.operator_id)
else
Operator.decrement_counter(:users_count, self.operator_id)
end
end
end
def change_employer_users_counter_cache_after_destroy
Operator.decrement_counter(:users_count, self.operator_id)
end
end
the gem "counter_culture" handled this very nicely...and cleaned up my code.
iam just asking myself, whats the best solution for my problem.
Here are my models:
class Product < ActiveRecord::Base
has_many :prices, :class_name => "ProductPrice"
accepts_nested_attributes_for :prices
end
class ProductPrice < ActiveRecord::Base
belongs_to :product
end
The controller
def create
#product = Product.new(params[:product])
#product.save
...
end
What i want to do is to prevent all ProductPrices from being saved when product_price.value == nil or product_price.value == 0.0
before_save hook in ProductPrice. return false will rollback the whole transaction, thats not what i want to do. i just want to "kick" all prices with value == 0 or value == nil
first kick all price_params from params[...] and than call Product.new(params[:product]) seems not to be the rails way eighter...
after Product.new(params[:product]) iterate over all prices and delete them from the array. but the logic should be in my models right? i just dont want to repeat myself on every controller that creates new prices...
can someone tell me the best solution for that? whats the rails way?
thanks!
What you want it called a validation hook, something like this:
class ProductPrice < ActiveRecord::Base
belongs_to :product
validates :value, :numericality => {:greater_than => 0.0 }
end
See http://guides.rubyonrails.org/active_record_validations_callbacks.html for other ways you may want to do this with finer control.
To avoid adding these invalid prices in the first place, you can remove them from the nested attributes hash like this:
class Product < ActiveRecord::Base
def self.clean_attributes!(product_params)
product_prices = product_params['prices'] || []
product_prices.reject!{|price| price['value'].to_f == 0 rescue true }
end
end
Product.clean_attributes!(params[:product])
Product.new(params[:product])
I've setup a nested form in my rails 3.2.3 app, it's working fine, my models are:
class Recipe < ActiveRecord::Base
attr_accessible :title, :description, :excerpt, :date, :ingredient_lines_attributes
has_and_belongs_to_many :ingredient_lines
accepts_nested_attributes_for :ingredient_lines
end
and:
class IngredientLine < ActiveRecord::Base
attr_accessible :ingredient_id, :measurement_unit_id, :quantity
has_and_belongs_to_many :recipes
belongs_to :measurement_unit
belongs_to :ingredient
end
As above, a Recipe can have multiple IngredientLines and vice versa.
What I'm trying to avoid is record duplication on IngredienLine table.
For example imagine that for recipe_1 an IngredientLine with {"measurement_unit_id" => 1, "ingredient_id" => 1, "quantity" => 3.5} is associated, if for recipe_5 the IngredientLine child form is compiled by the user with the same values, I don't want a new record on IngredientLine table, but only a new association record in the join table ingredient_lines_recipes.
Note that currently I dont't have any IngredientLine controller as saving and updating IngredientLines is handled by nested form routines. Even my Recipe controller is plain and standard:
class RecipesController < ApplicationController
respond_to :html
def new
#recipe = Recipe.new
end
def create
#recipe = Recipe.new(params[:recipe])
flash[:notice] = 'Recipe saved.' if #recipe.save
respond_with(#recipe)
end
def destroy
#recipe = Recipe.find(params[:id])
#recipe.destroy
respond_with(:recipes)
end
def edit
respond_with(#recipe = Recipe.find(params[:id]))
end
def update
#recipe = Recipe.find(params[:id])
flash[:notice] = 'Recipe updated.' if #recipe.update_attributes(params[:recipe])
respond_with(#recipe)
end
end
My guess is that should be enough to override the standard create behavior for IngredientLine with find_or_create, but I don't know how to achieve it.
But there's another important point to take care, imagine the edit of a child form where some IngredientLines are present, if I add another IngredientLine, which is already stored in IngredientLine table, rails of course should not write anything on IngredientLine table, but should also distinguish between child records already associated to the parent, and the new child record for which needs to create the relation, writing a new record on the join table.
Thanks!
in Recipe model redefine method
def ingredient_lines_attributes=(attributes)
self.ingredient_lines << IngredientLine.where(attributes).first_or_initialize
end
Old question but I had the same problem. Forgot to add :id to white list with rails 4 strong_parameters.
For example:
widgets_controller.rb
def widget_params
params.require(:widget).permit(:name, :foos_attributes => [:id, :name, :_destroy],)
end
widget.rb
class Widget < ActiveRecord::Base
has_many :foos, dependent: :destroy
accepts_nested_attributes_for :foos, allow_destroy: true
end
foo.rb
class Foo < ActiveRecord::Base
belongs_to :widget
end
I have run into a similar situation and found inspiration in this answer. In short, I don't worry about the duplication of nested models until save time.
Translated to your example, I added autosave_associated_records_for_ingredient_lines to Recipe. It iterates through ingredient_lines and performs a find_or_create as your intuition said. If ingredient_lines are complex, Yuri's first_or_initialize approach may be cleaner.
I believe this has the behavior you're looking for: nested models are never duplicated, but editing one causes a new record rather than updating a shared one. There is the strong possibility of orphaned ingredient_lines but if that's a serious concern you could choose to update if that model has only one recipe with an id that matches the current one.