Validation by neighbor association attribute in rails - ruby-on-rails

I have two models: Parent and Child, and i have a nested form for Parent, something like this:
= simple_form_for #parent do |form|
= form.input :name
= form.simple_fields_for :children do |children_form|
= children_form.input :money
= form.button :submit
What i need is a way to validate if sum of all children money equals to 100.
I've tried (in Parent):
validate do
total = children.inject(0) { |sum, child| child.money }
unless money == 100
children.each do |child|
child.errors.add(:money, :invalid)
end
end
end
but its not working for some reason. Thanks
Update:
Sorry, false alarm, i've used inject incorrectly.
total = children.inject(0) { |sum, child| sum + child.money }

Related

Rails - filter by date range with 2 models

I have two models: space and booking. Space has_many bookings and Booking has two date attributes: check_in and check_out.
Given a valid date range, I want to show all spaces available during this range
This is the view:
<%= form_tag spaces_path, method: :get do %>
<%= date_field_tag :query1,
params[:query1],
class: "form-control" %>
<%= date_field_tag :query2,
params[:query2],
class: "form-control" %>
<%= submit_tag "Search", class: "btn" %>
<% end %>
This is the SpaceController:
(...)
def index
if params[:query1].present? && params[:query2].present?
query1 = DateTime.parse(params[:query1])
query2 = DateTime.parse(params[:query2])
search = query1..query2
bookings = Booking.all
# returns the bookings that overlaps with the search
overlapping_bookings = bookings.select do |booking|
check_in = booking[:check_in]
check_out = booking[:check_out]
period = check_in..check_out
search.overlaps?(booking.period)
end
# returns the spaces_id of the bookings that overlaps
overlapping_space_ids = overlapping_bookings.select do |overlapping_booking|
overlapping_booking[:space_id]
end
# remove the duplicates
overlapping_space_ids.uniq!
# remove the spaces with bookings that overlap with the search
#spaces = Space.all.reject do |space|
overlapping_space_ids.include? space[:id]
end
else
#spaces = Space.all
end
end
(...)
I assume the root cause of my problem is that I'm treating the Active Record Query Object as an array of hashes, not sure if it's correct. I made some researches on this but I haven't found any exhaustive answer.
Using an SQL subquery (in PostgreSQL for example) you would do this:
sql = <<SQL
SELECT *
FROM spaces
WHERE id in (
SELECT space_id
FROM bookings
WHERE
(check_in, check_out) OVERLAPS (:from, :to)
)
SQL;
Booking.find_by_sql([sql, {from: query1, to: query2})
Hope that helps :)
I would add a scope to the Booking model first:
# in app/models/booking.rb
scope :overlapping, ->(from, to) {
where(
"(check_in, check_out) OVERLAPS (?, ?)", from, to
)
}
and would then change the whole controller method to:
def index
#spaces = Space.all
if params[:query1].present? && params[:query2].present?
from = DateTime.parse(params[:query1])
to = DateTime.parse(params[:query2])
#space = #space.where.not(
id: Booking.select(:space_id).overlapping(from, to)
)
end
end

Show categories/sub-categories in tree hierarchy inside a dropdown

I have a category table with fields id,name and parent_id. The root categories have parent_id 0. Now i want to show the list of categories in a drop down and a structure like this:
root_category
first_sub_category
sub_sub_category
another_sub_sub_category
second_sub_category
another_root_category
first_sub_category
second_sub_category
Here's my Controller:
def new
#category = Category.new
end
And here's the view:
<%= f.label :parent_category %>
<% categories = Category.all.map{|x| [x.name] + [x.id]} %>
<%= f.select(:parent_id, options_for_select(categories), {}, class: 'form-control') %>
Please Help.
Assuming you can get the children of a given category similar to:
has_many :children, :class_name => 'Category', :foreign_key => 'parent_id'
Create a method for categories to get all children and indent each by the level:
def all_children2(level=0)
children_array = []
level +=1
#must use "all" otherwise ActiveRecord returns a relationship, not the array itself
self.children.all.each do |child|
children_array << " " * level + category.name
children_array << child.all_children2(level)
end
#must flatten otherwise we get an array of arrays. Note last action is returned by default
children_array = children_array.flatten
end
Then in your view:
<select>
<option></option>
<% root_categories.each do |category| %>
<option><%=category.name%></option>
<% category.all_children2.each do |child| %>
<option><%=child.html_safe%></option>
<% end %>
<% end %>
</select>
I haven't 100% tested this but the bits I did suggest it should work...
Solved the problem by adding these functions in application_helper.rb
def subcat_prefix(depth)
(" " * 4 * depth).html_safe
end
def category_options_array(current_id = 0,categories=[], parent_id=0, depth=0)
Category.where('parent_id = ? AND id != ?', parent_id, current_id ).order(:id).each do |category|
categories << [subcat_prefix(depth) + category.name, category.id]
category_options_array(current_id,categories, category.id, depth+1)
end
categories
end
and using them in my view like this
<%= f.select(:parent_id, options_for_select(category_options_array), {}, class: 'form-control') %>

Spree ProductFilters do not work anymore

I got a problem defining new dynamic ProductFilters for Spree 2.0.4
My customer has got Categories (e.g. Taxons). A Product belongs to one category and has maximum 8 Properties BUT the Properties also depend on the Category each Product is in AND the Position of the Property is also important.
My Solution was to extend the Database in an unnormalized way:
module Spree
Taxon.class_eval do
belongs_to :prop1, class_name: "Spree::Property",
foreign_key: "p1_id"
belongs_to :prop2, class_name: "Spree::Property",
foreign_key: "p2_id"
belongs_to :prop3, class_name: "Spree::Property",
foreign_key: "p3_id"
belongs_to :prop4, class_name: "Spree::Property",
foreign_key: "p4_id"
belongs_to :prop5, class_name: "Spree::Property",
foreign_key: "p5_id"
belongs_to :prop6, class_name: "Spree::Property",
foreign_key: "p6_id"
belongs_to :prop7, class_name: "Spree::Property",
foreign_key: "p7_id"
belongs_to :prop8, class_name: "Spree::Property",
foreign_key: "p8_id"
attr_accessible :prop1, :prop2, :prop3, :prop4, :prop5, :prop6, :prop7, :prop8
def properties
prop = []
prop << prop1
prop << prop2
prop << prop3
prop << prop4
prop << prop5
prop << prop6
prop << prop7
prop << prop8
return prop
end
def applicable_filters
fs = []
fs << Spree::Core::ProductFilters.price_filter if Spree::Core::ProductFilters.respond_to?(:price_filter)
#fs << Spree::Core::ProductFilters.brand_filter if Spree::Core::ProductFilters.respond_to?(:brand_filter)
fs
end
end
end
So that I'm able to get the possible properties in a taxon for the corresponding product. I now made 8 Filters for each taxon property (prop1 .. prop8) because some values are numerical and should be handled different from text values, so even that's not completely DRY I came to the following solution:
module Spree
module Core
module ProductFilters
if Spree::Property.table_exists?
Spree::Product.add_search_scope :selective_prop1_any do |*opts|
conds = opts.map {|o| ProductFilters.selective_prop1_filter[:conds][o]}.reject {|c| c.nil?}
scope = conds.shift
conds.each do |new_scope|
scope = scope.or(new_scope)
end
Spree::Product.where(scope)
end
def ProductFilters.selective_prop1_filter(taxon = nil, locale = 'en')
return if taxon.nil? #||= Spree::Taxon.find_by_permalink!("categories")
property = taxon.prop1
scope = Spree::ProductProperty.where(:property_id => property, :locale => locale).
joins(:product => :taxons).
where("#{Spree::Taxon.table_name}.id" => [taxon] + taxon.descendants).
scoped
brands = scope.pluck(:value).uniq
{
:name => property.presentation,
:scope => :selective_prop1_any,
:labels => brands.sort.map { |k| [k,k] }
}
end
end
if Spree::Property.table_exists?
Spree::Product.add_search_scope :selective_prop2_any do |*opts|
conds = opts.map {|o| ProductFilters.selective_prop2_filter[:conds][o]}.reject {|c| c.nil?}
Rails.logger.debug conds.inspect
scope = conds.shift
Rails.logger.debug scope.inspect
conds.each do |new_scope|
scope = scope.or(new_scope)
end
Rails.logger.debug scope.inspect
Spree::Product.where(scope)
end
# ... other filters cut out for brevity
if Spree::Property.table_exists?
Spree::Product.add_search_scope :selective_prop8_any do |*opts|
[..]
end
def ProductFilters.selective_prop8_filter(taxon = nil, locale = 'en')
[..]
end
end
Spree::Product.add_search_scope :price_range_any do |*opts|
conds = opts.map {|o| Spree::Core::ProductFilters.price_filter[:conds][o]}.reject {|c| c.nil?}
scope = conds.shift
conds.each do |new_scope|
scope = scope.or(new_scope)
end
Spree::Product.joins(:master => :default_price).where(scope)
end
def ProductFilters.format_price(amount)
Spree::Money.new(amount)
end
def ProductFilters.price_filter
v = Spree::Price.arel_table
conds = [ [ Spree.t(:under_price, :price => format_price(10)) , v[:amount].lteq(10)],
[ "#{format_price(10)} - #{format_price(15)}" , v[:amount].in(10..15)],
[ "#{format_price(15)} - #{format_price(18)}" , v[:amount].in(15..18)],
[ "#{format_price(18)} - #{format_price(20)}" , v[:amount].in(18..20)],
[ Spree.t(:or_over_price, :price => format_price(20)) , v[:amount].gteq(20)]]
{ :name => Spree.t(:price_range),
:scope => :price_range_any,
:conds => Hash[*conds.flatten],
:labels => conds.map {|k,v| [k,k]}
}
end
end
end
end
Due to the fact, that even the values of the Properties should be localized, a made a column locale in the ProductProperties table. My selective filters pass the locale Variable to retrieve the correct ProductProperty.
Because of the MVC restrictions not being able to pass the locale from session[:locale] and the current taxon to the model I overwrote the original display logic which is using the applicable_filters method for the taxon in the CONTROLLER(!) like so:
TaxonsController.class_eval do
def show
#taxon = Taxon.find_by_permalink(params[:id])
return unless #taxon
if #taxon
#filters = []
#filters << Spree::Core::ProductFilters.selective_prop1_filter(#taxon, locale) unless #taxon.prop1.nil?
[...]
#filters << Spree::Core::ProductFilters.selective_prop8_filter(#taxon, locale) unless #taxon.prop8.nil?
#filters.concat(#taxon.applicable_filters)
else
#filters = Spree::Core::ProductFilters.all_taxons
end
p = params.merge(:taxon => #taxon.id)
#searcher = Spree::Config.searcher_class.new(params)
#searcher.current_user = try_spree_current_user
#searcher.current_currency = current_currency
#products = #searcher.retrieve_products
end
end
And here's the mainly left original view code:
<% unless #filters.empty? %>
<%= form_tag '', :method => :get, :id => 'sidebar_products_search' do %>
<% params[:search] ||= {} %>
<%= hidden_field_tag 'per_page', params[:per_page] %>
<% #filters.each do |filter| %>
<% labels = filter[:labels] || filter[:conds].map {|m,c| [m,m]} %>
<% next if labels.empty? %>
<div class="" data-hook="navigation">
<h5 class="filter-title"> <%= filter[:name] %> </h5>
<% labels.each do |nm,val| %>
<% label = "#{filter[:name]}_#{nm}".gsub(/\s+/,'_') %>
<label for="<%= label %>" class="checkbox" style="display:block;"><%= nm %><input type="checkbox"
id="<%= label %>"
name="search[<%= filter[:scope].to_s %>][]"
value="<%= val %>"
<%= params[:search][filter[:scope]] && params[:search][filter[:scope]].include?(val.to_s) ? "checked" : "" %> />
</label>
<% end %>
</div>
<% end %>
<%= submit_tag Spree.t(:search), :name => nil, :class => 'button' %>
<% end %>
<% end %>
The display works like it should: Depending on the taxon the user is in, the values from ProductProperties in the session[:locale] are fetched and displayed correctly, but now there comes the Problem:
The Search doesn't work work anymore. Even the not modified :price_range_any filter does not work. All Products belonging to the current taxon are always displayed. The search hash in params hash is built correct by the form search => {"price_range_any":["10.00 € EUR - 15.00 € EUR"]}
I've no idea. If I switch back to the original files by removing the overwrites everything is working.
How can I get my filters to run correctly?

Rails radio button options hash

My view:
<%= radio_button_tag "recommendation_ratings[#{rec.recommendation.id}][rating]", '3'%>
In my controller, I parse out these values:
input = params[:recommendation_ratings].values
input.each do |mini_params|
rating = mini_params[:rating]
recommendation_id = mini_params[:recommendation_id]
Using the rating variable I create a new records in the same controller action:
rate = Rating.new(
:rating => rating,
:label => rating_label)
How can I assign a rating to a rating_label when a new record is created. Example:
if rating == 1 rating_label = "Fair"
Should I do this in my controller, or can I add the rating_label to my radio_button_tag as another attribute?
UPDATE:
I tried a case statement in my controller but get an NoMethodError "Undefined Method Rating"
rating case
when rating == 1
rating_label = "Bad"
when rating == 2
rating_label = "Fair"
when rating == 3
rating_label = "Good"
else rating == 0
rating_label = "N/A"
end
Refactor your view code from using radio_button_tag to a fields_for block:
<%= fields_for :recommendation_ratings do |f| %>
<% f.hidden_field :id, rec.recommendation.id %>
<% f.radio_button :rating, 3 %>
<% end %>
Change your controller code to this:
selected_rating = params[:recommendation_ratings]
rating = selected_rating[:rating]
recommendation_id = selected_rating[:id]
And add a helper method somewhere as:
def rating_label(rating)
%w{N/A Bad Fair Good}[rating.to_i]
end
Now you can make a new Rating object like this:
rate = Rating.new(
:rating => rating,
:label => rating_label(rating))

Add custom column to ruby collection

Ok, here is what I have
columns in wines table -> ID, Name
columns in price table -> ID, wine_id, price
#query = Wine.find(:all)
now how do I add a custom price column to the #query hash? Can this be done?
This is what I want in the end
<% #query.each do |e| %>
<%= e.price %>
<% end %>
EDIT:
Actual table structure
Wines has many Inventories
Inventories has one Wine
Inventories has one Zone
Inventories has many Availabilities
Availabilities has one Inventory
Availabilities has one Markupprofile_id
Availabilities has one Discountprofile_id
This is the process I follow
#special offers
#q_array = Array.new
#q = SpecialOffers.find(:all, :conditions => ["client_id = ? AND application_id = ?", #client_id, #app_id])
#q.each do |e|
#q_array << e.availability_id
end
#filter, Special Offers, Client App Id, Zone, Available
#special_offers = Wine.find(:all, :include => [:availabilities, :zones], :conditions => ["availabilities.available = ? AND availabilities.id IN (?) AND availabilities.client_application_id = ? AND zones.id = ?", true, #q_array, #client_app_id, session[:zone_id]], :order => 'wines.name ASC')
#search page = 1, new arrivals = 2, special offers = 3, best sellers = 4, show page = 5
add_markup(#special_offers, 3)
Now I have the wines I want and I run them through add_markup()
def add_markup(collection, unique)
#price_array ||= [] #if null create blank array
collection.each do |e|
e.inventories.each do |f|
if session[:zone_id] == f.zone_id
#availability = Availability.find_by_inventory_id_and_client_application_id(f.id, #client_app_id)
#price = f.price
if #availability
if #availability.discount == true
price = Pricingmodel.find_by_discountprofile_id(#availability.discountprofile_id)
if price
was_price = Pricingmodel.find_by_markupprofile_id(#availability.markupprofile_id)
f.price = ((price.markup.to_f / 100) + 1) * #price * 6 #this is the normal price
f.stock = ((was_price.markup.to_f / 100) + 1) * #price * 6 #this is the discounted price
else
price = Pricingmodel.find_by_markupprofile_id(#availability.markupprofile_id)
f.price = ((price.markup.to_f / 100) + 1) * #price * 6 #this is the normal price
f.stock = ((price.markup.to_f / 100) + 1) * #price * 6 #this is the discounted price
end
else
price = Pricingmodel.find_by_markupprofile_id(#availability.markupprofile_id)
f.price = ((price.markup.to_f / 100) + 1) * #price * 6 #this is the normal price
f.stock = ((price.markup.to_f / 100) + 1) * #price * 6 #this is the discounted price
end
end
end
end
end
end
f.price is fine, its the normal price.
The problem is, I want to also display the discounted price somewhere
I used the stock column wich is of type int for this discounted price (f.stock = ((was_price.markup.to_f / 100) + 1) * #price * 6)
Is there any way I could "add" a of_type float column to this collection? Lets say discounted_price column of type float? Can this be done?
Let us assume you have has_one association between Wine and Price
Then
class Wine < ActiveRecord::Base
has_one :price
end
class Price < ActiveRecord::Base
belongs_to :wine
end
Then in your view display the price like below.
<% #query.each do |e| %>
<%= e.price.price %>
<% end %>
Do you have an association between both classes?
class Wine < ActiveRecord
has_one :price
end
class Price < ActiveRecord
belongs_to :wine
end
Then you could simply call:
<% #query.each do |e| %>
<%= e.price.price %>
<% end %>
I'd suggest you to use delegate.
class Wine < ActiveRecord::Base
has_one :price
delegate :rate, :to => :price # Notice 'rate' instead of 'price'. So that you could still get the associated price record.
end
class Price < ActiveRecord::Base
belongs_to :wine
end
You could simply do
<% #query.each do |e| %>
<%= e.rate %>
<% end %>
I think you can achieve this using attr_accessor!
Take a look at:
usage of attr_accessor in Rails
Why use Ruby's attr_accessor, attr_reader and attr_writer?

Resources