I'm starting my adventure with form objects.
I have a problem with create form object for new/create and edit/update.
Maybe exist a trick, which I do not know?
I have Branch, branch has one BranchAddress.
BranchAddress always is create after create Branch.
This is my code. This is well written?
class BranchesController < ApplicationController
def new
#branch_form = BranchForm.new
end
def edit
#branch = Branch.find(params[:id])
#branch_form = BranchForm.new(branch: #branch)
end
def update
#branch = Branch.find(params[:id])
#branch_form = BranchForm.new(branch: #branch, attributes: branch_params)
if #branch_form.update
render root_path
else
render :edit
end
end
def create
#branch_form = BranchForm.new(user: current_user, attributes: branch_params)
if #branch_form.save
redirect_to root_path
else
render :new
end
end
private
def branch_params
params.require(:branch).permit( :branch_name,
:branch_description,
:branch_url,
:branch_publish,
:branch_main,
:branch_date_published,
:address_country,
:address_street,
:address_zip_code,
:address_city,
:address_real_estate,
:address_apartment,
:address_lat,
:address_lng )
end
I have form object
class BranchForm < BaseForm
attr_reader :branch
attr_reader :address
#
## branch address
#
attribute :branch_name, String
attribute :branch_description, String
attribute :branch_url, String
attribute :branch_publish, Boolean, default: false
attribute :branch_main, Boolean
attribute :branch_date_published, Date
# branch address
attribute :address_country, String
attribute :address_street, String
attribute :address_zip_code, String
attribute :address_city, String
attribute :address_real_estate, String
attribute :address_apartment, String
attribute :address_lat, String
attribute :address_lng, String
#
## branch validator
#
validates :branch_name,
presence: true,
length: { maximum: 255 }
validates :branch_description,
presence: true,
length: { maximum: 65535 }
validates :branch_url, length: { maximum: 255 }
#
## branch address validator
#
validates :address_country, length: { maximum: 255 }
validates :address_street, length: { maximum: 255 }
validates :address_zip_code, length: { maximum: 255 }
validates :address_city, length: { maximum: 255 }
validates :address_real_estate, length: { maximum: 255 }
validates :address_apartment, length: { maximum: 255 }
validates :address_lat, length: { maximum: 255 }
validates :address_lng, length: { maximum: 255 }
def initialize(user: nil, branch: nil, attributes: {})
if !branch.nil?
#branch = branch
#branch_address = #branch.branch_address
#user = user
super(branch_attributes.merge(attributes))
super(branch_address_attributes.merge(attributes))
end
super(attributes)
end
def save
if valid?
create_branch
update_branch_address
true
else
false
end
end
def update
if valid?
update_branch
update_branch_address
true
else
false
end
end
def update_branch
#branch.update_attributes(branch_permitted_parameters)
end
def create_branch
#branch = #user.branches.create(branch_permitted_parameters)
end
def update_branch_address
#branch.branch_address.update_attributes(branch_address_permitted_parameters)
end
def branch_permitted_parameters
{
name: branch_name,
description: branch_description,
url: branch_url,
publish: branch_publish,
main: branch_main,
date_published: branch_date_published,
}
end
def branch_address_permitted_parameters
{
country: address_country,
street: address_street,
zip_code: address_zip_code,
city: address_city,
real_estate: address_real_estate,
apartment: address_apartment,
lat: address_lat,
lng: address_lng,
}
end
def branch_attributes
{
branch_name: branch.name,
branch_description: branch.description,
branch_url: branch.url,
branch_publish: branch.publish,
branch_main: branch.main,
branch_date_published: branch.date_published
}
end
def branch_address_attributes
{
address_country: #branch_address.country,
address_street: #branch_address.street,
address_zip_code: #branch_address.zip_code,
address_city: #branch_address.city,
address_real_estate: #branch_address.real_estate,
address_apartment: #branch_address.apartment,
address_lat: #branch_address.lat,
address_lng: #branch_address.lng,
}
end
end
It's my simple_form
= simple_form_for(#branch_form, as: :branch, url: generate_url, method: generate_method) do |f|
= f.input :branch_name
= f.input :branch_description
= f.input :branch_url
= f.input :branch_main, as: :boolean, wrapper: :icheck_form_left_text
= f.input :branch_publish, as: :boolean, wrapper: :icheck_form_left_text
= f.input :branch_date_published, wrapper: :datepicker, icon: "fa fa-calendar", :input_html => {minlength: 8, required: true, data: { :mask => "9999-99-99"}}
= f.input :address_lat
= f.input :address_lng
= f.input :address_country
= f.input :address_city
= f.input :address_zip_code
.form-group
= f.input :address_street, wrapper: :address, maxlength: 255
= f.input :address_real_estate, wrapper: :address_nr, input_html: {:min => 1}
#slash_adress
= f.input :address_apartment,wrapper: :address_nr, input_html: {:min => 1}
.hr-line-dashed
.form-group
.col-sm-4.col-sm-offset-2
= f.submit "Save", class: "btn btn-primary"
Url and method for simple_form I generate in helper.
In class BranchForm, can someone wrote better or have a better idea?
Related
So I have a form that I'm trying to allow the user to copy over text from billing to shipping address. Unfortunately at the moment it's automatically saving billing address in the shipping address.
My form looks like the following (albeit truncated for massive amount of fields):
=form_for #customer, url: create_customer_path, html: {class: 'new-customer} do |f|
.row
.col-md-4
= f.label :first_name
.col-md-8
= f.text_field :first_name, class: 'form-control', required:true
.row
.col-md-4
= f.label :billing_address1
.col-md-8
= f.text_field :billing_address1, class: 'form-control', required:true
.row
.col-md-12
= f.check_box :shipping_is_billing
= f.label :shipping_is_billing, 'Same as billing address'
.row
.col-md-4
= f.label :shipping_address1
.col-md-8
= f.text_field :shipping_address1
CustomersController
class CustomersController < ApplicationController
def new
#customer = CustomerForm.new
end
def create
#customer = CustomerForm.new(customer_params)
if #customer.save
redirect_to customer_success_path
else
render 'new'
end
end
private
def customer_params
params.require(:customer_form).permit!.tap do |p|
p[:captcha_response] = params['g-recaptcha-response']
end
end
end
CustomerForm (truncated for massive fields)
class CustomerForm
include ActiveModel::Model
CUSTOMER_ATTRS = %w[
first_name
].freeze
ADDRESS_ATTRS = %w[
address1 address2 city state zip
].freeze
attr_accessor(*CUSTOMER_ATTRS)
attr_accessor(*ADDRESS_ATTRS.map { |attr| 'billing_' + attr })
attr_accessor(*ADDRESS_ATTRS.map { |attr| 'shipping_' + attr })
attr_accessor :confirm_email, :captcha_response, :shipping_is_billing
validates :first_name, presence: true
validates :billing_address1, presence: true
validates :shipping_address1, presence: true, unless: :shipping_is_billing
def save
return false unless valid?
persist!
end
private
def captcha_passes
captcha = Captcha.new
return if captcha.valid?(captcha_response)
errors.add(:captcha_response, 'is invalid')
end
def persist!
customer = Customer.new(attrs_for_customer)
customer.billing_address = CustomerAddress.new(attrs_for_address('billing_'))
customer.shipping_address = CustomerAddress.new(
attrs_for_address(shipping_is_billing ? 'billing_' : 'shipping_')
)
customer.save!
customer
end
def attrs_for_customer
Hash[
CUSTOMER_ATTRS.map { |attr| [attr, send(attr)] }
]
end
def attrs_for_address(prefix)
Hash[
ADDRESS_ATTRS.map { |attr| [attr, send(prefix + attr.to_s)] }
]
end
end
JS
app.newCustomer = () => {
function init() {
let check = document.querySelector('#customer_form_shipping_is_billing')
check.addEventListener('change', toggledUseBilling)
}
let toggledUseBilling = event => {
shippingFields().forEach(field => {
if(event.target.checked) {
field.value = null;
field.removeAttribute('required');
field.setAttribute('disabled', true);
} else {
field.setAttribute('required', true);
field.removeAttribute('disabled');
}
})
}
let shippingFields = () => {
let selectors = [
'#customer_form_shipping_address1',
'#customer_form_shipping_address2',
'#customer_form_shipping_city',
'#customer_form_shipping_state',
'#customer_form_shipping_zip',
]
return document.querySelectorAll(selectors.join(', '));
}
return init();
}
Under the persist! method I'm using the ternary operator on the checkbox to determine the attributes for the address to be billing/shipping. But it doesn't look like it's actually working. How do I grab from the form the checkbox being marked?
Here are the things I've tried:
Switched = f.check_box :shipping_is_billing to =check_box_tag :shipping_is_billing. Then I had to update my JS to make sure I was grabbing the right checkbox. This stores the shipping data but when checked the business data isn't being copied.
Attempted to switch attrs_for_address(shipping_is_billing ? 'billing_' : 'shipping_') to attrs_for_address(shipping_is_billing ? 'shipping_' : 'billing_'). If I use the check_box this will populate the data over to shipping but the checkbox becomes ineffectual.
Put a form_tag around the check_box_tag but this actually stripped out the check box
Changed the check_box to check_box_tag, updated my JS to look for the correct ID on the checkbox. Can save the shipping address if typed in but if checking the box it does not apply the billing address and I get prompts that the shipping address can't be blank
Did a binding.pry within the persist! method. Looks like shipping_is_billing is pulling a string which is hitting truthy. Changed to
customer.shipping_address = CustomerAddress.new(
attrs_for_address(shipping_is_billing == "1" ? 'billing_' : 'shipping_')
I have been battling a major refactor to slim down a payments controller and could use a hand. Step one I am trying to fix my factories. Right now all of the factories work great on their own, but when I try to build associations the FactoryGirl.create(:job, :purchased_with_coupon) it will setup the association correctly on the coupon but not the payment. This means that the price paid is always is always 1. I just noticed this which you can see the other section commented out. Before I start tackling the bloated controller I need to figure this out for my tests. Thoughts?
Factories
FactoryGirl.define do
factory :job do
category
company
title { FFaker::Company.position }
location { "#{FFaker::Address.city}, #{FFaker::AddressUS.state}" }
language_list { [FFaker::Lorem.word] }
short_description { FFaker::Lorem.sentence }
description { FFaker::HTMLIpsum.body }
application_process { "Please email #{FFaker::Internet.email} about the position." }
trait :featured do |job|
job.is_featured true
end
trait :reviewed do |job|
job.reviewed_at { Time.now }
end
trait :purchased do |job|
job.reviewed_at { Time.now }
job.start_at { Time.now }
job.end_at { AppConfig.product['settings']['job_active_for_day_num'].day.from_now }
job.paid_at { Time.now }
payments { |j| [j.association(:payment)] }
end
trait :purchased_with_coupon do |job|
job.reviewed_at { Time.now }
job.start_at { Time.now }
job.end_at { AppConfig.product['settings']['job_active_for_day_num'].day.from_now }
job.paid_at { Time.now }
association :coupon, factory: :coupon
payments { |j| [j.association(:payment)] }
end
trait :expired do |job|
start_at = (200..500).to_a.sample.days.ago
job.reviewed_at { start_at }
job.start_at { start_at }
job.end_at { |j| j.start_at + AppConfig.product['settings']['job_active_for_day_num'].days }
job.paid_at { start_at }
payments { |j| [j.association(:payment)] }
end
end
end
FactoryGirl.define do
factory :payment do
job
# price_paid { rand(100..150) }
price_paid { 1 }
stripe_customer_token { (0...50).map { (65 + rand(26)).chr }.join }
end
end
FactoryGirl.define do
factory :coupon do
code { rand(25**10) }
percent_discount { rand(100**1) }
start_at { 2.days.ago }
end_at { 30.day.from_now }
trait :executed do |c|
association :job, factory: [:job, :purchased]
c.executed_at { Time.now }
end
end
end
Models
class Job < ActiveRecord::Base
acts_as_paranoid
strip_attributes
acts_as_taggable
acts_as_taggable_on :languages
belongs_to :company
before_validation :find_company
belongs_to :category
has_one :coupon
has_many :payments
before_create :create_slug, :set_price
after_create :update_vanity_url
accepts_attachments_for :company
accepts_nested_attributes_for :company
accepts_nested_attributes_for :coupon
accepts_nested_attributes_for :payments
validates :title,
:location,
:short_description,
presence: true,
format: { with: /\A[\w\d .,:-#]+\z/, message: :bad_format }
validates :application_process,
presence: true,
format: { with: %r{\A[\w\d .,:/#&=?-]+\z}, message: :bad_format }
validates :title, length: { minimum: 10, maximum: 45 }
validates :location, length: { minimum: 10, maximum: 95 }
validates :short_description, length: { minimum: 10, maximum: 245 }
validates :application_process, length: { minimum: 10, maximum: 95 }
validates :description,
:category_id,
:language_list,
presence: true
validates :reviewed_at,
:start_at,
:end_at,
:paid_at,
date: { allow_blank: true }
validates :start_at, date: { before: :end_at, message: :start_at_before_end_at }, if: proc { start_at? }
validates :end_at, date: { after: :start_at, message: :end_at_after_start_at }, if: proc { end_at? }
scope :active, -> { where.not(reviewed_at: nil, paid_at: nil).where('end_at >= ?', Date.today) }
def expired?
end_at.present? && end_at < Date.today
end
def reviewed?
reviewed_at.present?
end
def paid_for?
reviewed? && paid_at.present?
end
def active?
reviewed? && paid_at.present? && end_at <= Date.today
end
private
def set_price
self.price = AppConfig.product['settings']['job_base_price']
end
def create_slug
self.slug = title.downcase.parameterize
end
def update_vanity_url
self.vanity_url = '/jobs/' + company.slug + '/' + slug + '/' + id.to_s + '/'
save
end
def find_company
existing_company = Company.where(email: company.email) if company
self.company = existing_company.first if existing_company.count > 0
end
end
class Coupon < ActiveRecord::Base
acts_as_paranoid
strip_attributes
belongs_to :job
validates :start_at, date: { before: :end_at }
validates :executed_at, date: { allow_blank: true }
validates_presence_of :job, if: proc { executed_at? }
validates_presence_of :executed_at, if: :job
validates :code,
presence: true,
length: { minimum: 10, maximum: 19 },
uniqueness: { case_sensitive: false },
numericality: { only_integer: true }
validates :percent_discount,
inclusion: { in: 1..100 },
length: { minimum: 1, maximum: 3 },
numericality: { only_integer: true },
presence: true
scope :active, -> { where('start_at < ? AND end_at > ? AND executed_at IS ?', Date.today, Date.today, nil) }
def active?
start_at < Date.today && end_at > Date.today && executed_at.nil?
end
def executed?
job_id.present?
end
end
class Payment < ActiveRecord::Base
belongs_to :job
belongs_to :coupon
validates_presence_of :job
validate :coupon_must_be_active
before_create :net_price
Numeric.include CoreExtensions::Numeric::Percentage
attr_accessor :coupon_code
def coupon_code=(code)
#coupon = Coupon.find_by_code(code)
end
def net_price
return job.price unless #coupon
job.price = #coupon.percent_discount.percent_of(job.price)
self.coupon = #coupon
end
private
def coupon_must_be_active
if #coupon
errors[:coupon] << I18n.t('flash_messages.coupons.id.inactive') unless #coupon.active?
elsif #coupon_code.present?
errors[:coupon_code] << I18n.t('flash_messages.coupons.id.not_found')
end
end
end
It looks like the problem is that there is logic outside of your models that is updating the price_paid column on your Payment, and possibly setting the coupon_id on it as well.
So I would recommend duplicating any extra logic that might be coming from your controllers, service classes, etc. into an after(:create) callback on your factory.
trait :purchased_with_coupon do
# ...other attributes...
association :coupon
after(:create) do |job, evaulator|
discount_value = 100 - job.coupon.percent_discount) / 100.0
calculated_price_paid = job.price * discount_value
create(:payment, price_paid: price_paid, job: job, coupon: coupon)
end
end
Now ultimately, that code belongs in some kind of abstraction, such as a service class that can easily be tested (and used in other tests). However, you mentioned you are getting started on a refactor and want passing tests. I think this is a reasonable compromise until you're ready to abstract it. Ultimately, I would do something like this:
class CreatePaymentWithCoupon
attr_reader :job
def initialize(job)
#job = job
end
def call
job.payments.create(coupon: job.coupon, price_paid: discounted_price)
end
private
def discounted_price
discount_value = (100 - job.coupon.percent_discount) / 100.0
job.price * discount_value
end
end
Then, in your specs:
it "calculates discounted price" do
coupon = create(:coupon, percent_discount: 25)
job = create(:job, :purchased_with_coupon, price: 100)
CreatePaymentWithCoupon.new(job).call
expect(job.payments.first.price_paid).to eq(75.0)
end
I have the following model:
class Man < User
field :surname, type: String, default: ""
field :name, type: String, default: ""
NAME_LENGTH = 1..50
validates :surname, length: {in: NAME_LENGTH}, allow_blank: true
validates :name, length: {in: NAME_LENGTH}, allow_blank: true
validates :father, length: {in: NAME_LENGTH}, allow_blank: true
field :birthday, type: Integer #Timestamp
field :about, type: String
ABOUT_LENGTH = 2000
validates :about, length: {maximum: ABOUT_LENGTH}, allow_blank: true
embeds_many :skills
MAX_SKILLS_COUNT = 10
validates :skills, length: {maximum: MAX_SKILLS_COUNT}
#user got unique skills
index({login: 1, 'skills.caption_to_index': 1}, {unique: true, sparse: true})
end
And embedded skills model:
class Skill
include Mongoid::Document
CAPTION_LENGTH = 1..50
CAPTION_FILTER = /[^a-zа-я0-9]/
field :caption, type: String
field :caption_to_index, type: String
field :order, type: Integer
embedded_in :man
validates :caption, length: {in: CAPTION_LENGTH}
validates :order, presence: true
before_save do |document|
document.caption_to_index = document.caption.downcase.gsub(CAPTION_FILTER,'')
end
end
The trouble is, mongoid ignores length validation when I do something like this:
pry(main)> u = Man.find_by(login:'tester')
pry(main)> s3 = Skill.new(caption:'JavaScript',order:1)
pry(main)> u.skills << s3
I can repeat u.skills << s3 infinite times, and driver saves this to database immidiately.
Another problem is that unique index is not working:
index({login: 1, 'skills.caption_to_index': 1}, {unique: true, sparse: true})
All indexes were created successfully via rake:mongoid:create_indexes
Driver allows to save document with the same caption, here is part of the document in mongo shell as the proof:
{
"_id" : ObjectId("56a0218167e92c3b8b000000"),
"caption" : "JavaScript",
"order" : 1,
"caption_to_index" : "javascript"
},
{
"_id" : ObjectId("56a0218167e92c3b8b000000"),
"caption" : "JavaScript",
"order" : 1,
"caption_to_index" : "javascript"
},
{
"_id" : ObjectId("56a0218167e92c3b8b000000"),
"caption" : "JavaScript",
"order" : 1,
"caption_to_index" : "javascript"
},
{
"_id" : ObjectId("56a0218167e92c3b8b000000"),
"caption" : "JavaScript",
"order" : 1,
"caption_to_index" : "javascript"
}
Test case for uniqueness also fails:
require 'rails_helper'
include ApplicationHelper
describe Skill do
before :each do
#attrs = attributes_for :tester_man
#tester = create :tester_man
expect(#tester).to be_valid
expect(#tester.persisted?).to be true
end
it "skill caption is unique" do
#debugger
s1_good = Skill.new(caption:'<<cap Tion ..%#',order:2)
s2_good = Skill.new(caption:'other_caption',order:4)
s3_bad = Skill.new(caption:'CAPTION',order:1)
expect(s1_good).to be_valid
expect(s2_good).to be_valid
#tester.skills = [s1_good,s2_good]
#tester.save
expect(#tester).to be_valid
s3_bad = Skill.new(caption:'CAPTION',order:1)
#tester.skills << s3_bad
expect(#tester).not_to be_valid
end
end
How can I get this code work as expected?
I've got below code in my model which I want to use for validation:
class Payslip < ActiveRecord::Base
include ActiveModel::Validations
attr_accessor :first_name, :last_name, :annual_salary, :super, :payment_start_date
validates :annual_salary, :super, numericality: { only_integer: true },
presence: true
validates :super, inclusion: { in: 0..50 }
validates :first_name, :last_name, presence: true,
length: { maximum: 100 }
validates_date :payment_start_date
validates :payment_start_date, presence: true
end
I have CSV import from the form and this gets passed over to concern:
module CSV_Manager
extend ActiveSupport::Concern
class << self
def extract_csv(csv_file, headers)
results = []
CSV.foreach(csv_file.path, {headers: false, :encoding => 'UTF-8'}) do |row|
data = row.split(',')
Payslip.first_name = data[0]
Payslip.last_name = data[1]
Payslip.super = data[2]
results.push(row) unless $. == headers.to_i
end
return results
end
def prepare_csv(rows, headers)
csv_file = CSV.generate do |csv|
csv << headers
rows.map { |row| csv << row }
end
return csv_file
end
end
end
I've added Payslip.first_name etc in an attempt to validate and throw an error if not validated.
Is this the right approach?
Here's a rough suggestion on how I would handle the problem you're trying to solve. Feel free to comment if this isn't what you're looking for:
def extract_csv(csv_file, headers)
results = []
CSV.foreach(csv_file.path, {headers: false, :encoding => 'UTF-8'}) do |row|
data = row.split(',')
payslip = Payslip.create({
first_name: data[0],
last_name: data[1],
super: data[2]
})
results.push(row) unless $. == headers.to_i
end
return results
end
Also, super is a reserved keyword, so I would suggest possibly not using it if you an avoid it.
theres an excerpt of my code:
module Configuracao
extend self
class Key
include ActiveModel::Validations
attr_accessor :name, :type, :default, :validations, :group, :available_values
def initialize(params)
params.symbolize_keys!.assert_valid_keys(:name, :type, :default, :validations, :group, :available_values)
#group = params[:group]
#name = params[:name]
#type = params[:type]
#available_values = params[:available_values]
#default = params[:default]
#validations = params[:validations]
#in this way each validation is being added for all keys
Configuracao::Key.class_eval do
validates :value, params[:validations]
end
end
end
end
so for every instance key i will have a diferent validation passed in a hash, example:
Key.new( validations: { presence: true, numericality: true } )
Key.new( validations: { length: { maximum: 30 } } )
There's a way to do it?
well i found a solution, maybe not so elegant or best way to do, but it works
def initialize(params)
params.symbolize_keys!.assert_valid_keys(:name, :type, :default, :validations, :group, :available_values)
#group = params[:group]
#name = params[:name]
#type = params[:type]
#available_values = params[:available_values]
#default = params[:default]
##current_validations = nil
##current_validations = #validations = params[:validations]
class << self
validates :value, ##current_validations unless ##current_validations.blank?
end
end
now each time i instantiate a Key, the class will be modified only for that instance
Will this work?
...
validates :all_hash_validations_pass
...
def all_hash_validations_pass
...iterate through the hash here, and validate each of them
end
If not, you should be able to use a custom validator for more control.