I am trying to make Order number incremental in Spree 3.1.
I got only this:
Spree::Order.class_eval do
before_validation(on: :create) do
self.number = Spree::Core::NumberGenerator.new(prefix: 'S').send(:generate_permalink, Spree::Order)
end
end
but it's only change prefix.
How should I rewrite new_candidate to get right result?
#lib/spree/core/number_generator.rb
def new_candidate(length)
#prefix + length.times.map { #candidates.sample(random: #random) }.join
end
You can try this:
def new_candidate(host)
max_number = host.maximum(:number) || STARTING_NUMBER
#prefix + (max_number.gsub(#prefix, '').to_i + 1).to_s
end
def generate_permalink(host)
new_candidate(host)
end
You will need to define STARTING_NUMBER constant in the generator class. Also, you will not need the #length instance variable anymore.
I recently worked on a similar task to have an order number that contains the date of the order + a randomly generated number (for uniqueness).
To achieve this I add an order_decorator.rb into models/spree and I override the generate_number method:
// First i redefine the length for my random number
NUMBER_LENGTH = 5
def generate_number(options = {})
options[:length] ||= NUMBER_LENGTH
date = Date.today.strftime('%d-%m-%Y')
possible = (0..9).to_a
random = "-#{(0...options[:length]).map { possible.shuffle.first }.join}"
self.number ||= date + random
end
I don't like the generation of the random number (I would use `SecureRandom for it) but i left it initially was in Spree because for this it will do fine for my needs.
In your case you can add inside the method the code you need to make your number incremental.
I hope this helps!
Cheers
Related
I have to create a list of 24 months with the same day amongst them, properly handling the months that do not have day 29, 30 or 31.
What I currently do is:
def dates_list(first_month, assigned_day)
(0...24).map do |period|
begin
(first_month + period.months).change(day: assigned_day)
rescue ArgumentError
(first_month + period.months).end_of_month
end
end
end
I need to rescue from ArgumentError as some cases raise it:
Date.parse('10-Feb-2019').change(day: 30)
# => ArgumentError: invalid date
I am looking for a safe and elegant solution that might already exist in ruby or rails. Something like:
Date.parse('10-Feb-2019').safe_change(day: 30) # => 28-Feb-2019
So I can write:
def dates_list(first_month, assigned_day)
(0...24).map do |period|
(first_month + period.months).safe_change(day: assigned_day)
end
end
Does that exist or I would need to monkey patch Date?
Workarounds (like a method that already creates this list) are very welcome.
UPDATE
The discussion about what to do with negative and 0 days made me realize this function is trying to guess the user's intent. And it also hard codes how many months to generate, and to generate by month.
This got me thinking what is this method doing? It's generates a list of advancing months, of a fixed size, and modifying them in a fixed way, and guessing what the user wants. If your function description includes "and" you probably need multiple functions. We separate generating the list of dates from modifying the list. We replace the hard coded parts with parameters. And instead of guessing what the user wants, we let them tell us with a block.
def date_generator(from, by:, how_many:)
(0...how_many).map do |period|
date = from + period.send(by)
yield date
end
end
The user can be very explicit about what they want to change. No surprises for the user nor the reader.
p date_generator(Date.parse('2019-02-01'), by: :month, how_many: 24) { |month|
month.change(day: month.end_of_month.day)
}
We can take this a step further by turning it into an Enumerator. Then you can have as many as you like and do whatever you like with them using normal Enumerable methods..
INFINITY = 1.0/0.0
def date_iterator(from, by:)
Enumerator.new do |block|
(0..INFINITY).each do |period|
date = from + period.send(by)
block << date
end
end
end
p date_iterator(Date.parse('2019-02-01'), by: :month)
.take(24).map { |date|
date.change(day: date.end_of_month.day)
}
Now you can generate any list of dates, iterating by any field, of any length, with any changes. Rather than being hidden in a method, what's happening is very explicit to the reader. And if you have a special, common case you an wrap this in a method.
And the final step would be to make it a Date method.
class Date
INFINITY = 1.0/0.0
def iterator(by:)
Enumerator.new do |block|
(0..INFINITY).each do |period|
date = self + period.send(by)
block << date
end
end
end
end
Date.parse('2019-02-01')
.iterator(by: :month)
.take(24).map { |date|
date.change(day: date.end_of_month.day)
}
And if you have a special, common case, you can write a special case function for it, give it a descriptive name, and document its special behaviors.
def next_two_years_of_months(date, day:)
if day <= 0
raise ArgumentError, "The day must be positive"
end
date.iterator(by: :month)
.take(24)
.map { |next_date|
next_date.change(day: [day, next_date.end_of_month.day].min)
}
end
PREVIOUS ANSWER
My first refactoring would be to remove the redundant code.
require 'date'
def dates_list(first_month, assigned_day)
(0...24).map do |period|
next_month = first_month + period.months
begin
next_month.change(day: assigned_day)
rescue ArgumentError
next_month.end_of_month
end
end
end
At this point, imo, the function is fine. It's clear what's happening. But you can take it a step further.
def dates_list(first_month, assigned_day)
(0...24).map do |period|
next_month = first_month + period.months
day = [assigned_day, next_month.end_of_month.day].min
next_month.change(day: day)
end
end
I think that's marginally better. It makes the decision a little more explicit and doesn't paper over other possible argument errors.
If you find yourself doing this a lot, you could add it as a Date method.
class Date
def change_day(day)
change(day: [day, end_of_month.day].min)
end
end
I'm not so hot on either change_day nor safe_change. Neither really says "this will use the day or if it's out of bounds the last day of the month" and I'm not sure how to express that.
Pretty new to RoR. Wonder if anyone can help me with this issue.
I got a gem called "business_time" which calculates the business days between two dates. I have set up a method in the model which does all the calculations.
I have a field called "credit" which should hold the number of business days. Here's what I have:
MODEL
def self.calculate(from_date,to_date)
days = 0
date_1 = Date.parse(from_date)
date 2 = Date.parse(to_date)
days = date_1.business_days_until(date2)
days
end
CONTROLLER
def new
#vacation = current_user.vacations.build
#vacations = Vacation.calculate(:from_date, :to_date)
end
I got an error referencing something about a string.
Furthermore, how do I go about storing the data from the method into the field called "credit"?
Thanks guys.
I think there is no need for an extra method, since all attributes (from_date, end_date and credit) are stored in the same model.
I would just set from_date and end_date in the initializer and calculate credit with a callback before validation:
# in the model
before_validation :calculate_credit
private
def calculate_credit
if from_date && to_date
# `+ 1` because the user takes off both days (`from_date` and `to_date`),
# but `business_days_until` doesn't count the `from_day`.
self.credit = from_date.business_days_until(to_date) + 1
end
end
# in the controller
def new
#vacation = current_user.vacations.build
end
def create
#vacation = current_user.vacations.build(vacation_params)
if #vacation.save
# #vacation.credit would return the calculated credit at this point
else
# ...
end
end
private
def vacation_params
params.require(:vacation).permit(:from_date, :to_date)
end
What you need here is pass String objects instead of Symbol objects.
So instead of #vacations = Vacation.calculate(:from_date, :to_date), you probably need to pass params[:from_date] and params[:to_date] which should be strings like 20/01/2016, etc...
Your code should be
#vacations = Vacation.calculate(params[:from_date], params[:to_date])
I'm trying to convert a bunch of numbers from imperial to metric on the front end of my site depending on if the user has set their measurement_units to 'metric' or 'imperial'
I can just do #myWeight*.45 to convert the number, but what I want to do is write a helper method like this
def is_imperial?
if User.measurement_units == 'metric'
*0.453592
elsif User.measurement_units == 'imperial'
*1
end
end
then be able to do this: #myWeight*.is_imperial?
I'm just not sure how I would assign the *value to the method is_imperial?
Thanks for the help!
EDIT:
#myWeight is a float calculated from adding several numbers.
I'm just trying to find an elegant way of converting any number that shows up on the site to metric if the user has metric as the value in the measurement_units field on the User model.
I assumed I would need to create a helper method in the application_helper.rb. Is that not correct?
I think you mean something like this:
class User
def imperial
f_multiplier = 0.0
f_multiplier = !!(self.measurement_units == 'metric') ? 0.453592 : 1
imperial = self.weight * f_multiplier
end
end
puts #myWeight.imperial
If you want the measurement_units method to be dynamic based on the user, then I think you need to make it an instance method.
Modify the is_imperial? method to return the right number:
def is_imperial?
if measurement_units == 'metric'
0.453592
elsif measurement_units == 'imperial'
1
end
end
Then you can call the method with something like this:
#myWeight.send(:*, is_imperial?)
If #myWeight represents a User object you might have to change it to this:
#myWeight.weight.send(:*, is_imperial?)
Methods that end with a ? in Ruby are expected to return true or false, so you should rename the method to be something like weight_conversion_factor.
Assuming #myWeight is Float value, you seem like you are looking for how to monkey patch. Check in rails console, #myWeight.class.name returns Float.
For monkey patching in Rails,
Create config/initializers/extensions directory. This is where you will store any future monkey patched methods.
Create a file called, floats.rb.
Add the following code.
class Float
def is_imperial?
if User.measurement_units == 'metric'
self*0.453592
elsif User.measurement_units == 'imperial'
self*1
end
end
end
Make sure to restart the Rails server to reinitialize.
I have a number of these in my controller:
def ups
#ups ||= Shipper::Ups.new(
ENV['UPS_ACCESS_KEY'],
ENV['UPS_PASSWORD'],
ENV['UPS_USERNAME'],
ENV['UPS_ACCOUNT']
)
end
And then I have this block that gets called:
def type(number, carrier)
case carrier.slug
when 'ups'
number_details = ups.track(number)
when 'fedex'
number_details = fedex.track(number)
when 'usps'
number_details = usps.track(number)
end
return number_details
end
But seems I could refactor that quite a bit if I could take the carrier.slug and prepend it to the lines like ups.track(number).
Is there a way to do that?
you can use send to do this but before that we need to make sure that you have the right carrier slug
if %w[ups fedex usps].include?(carrier.slug)
send(carrier.slug).track(number)
end
How can I update these very similar text fields in a less verbose way? The text fields below are named as given - I haven't edited them for this question.
def update
company = Company.find(current_user.client_id)
company.text11 = params[:content][:text11][:value]
company.text12 = params[:content][:text12][:value]
company.text13 = params[:content][:text13][:value]
# etc
company.save!
render text: ""
end
I've tried using send and to_sym but no luck so far...
[:text11, :text12, :text13].each do |s|
company.send("#{s}=".to_sym, params[:content][s][:value])
end
If they are all incremental numbers, then:
11.upto(13).map{|n| "text#{n}".to_sym}.each do |s|
company.send("#{s}=".to_sym, params[:content][s][:value])
end
I'd consider first cleaning up the params, then move onto dynamically assigning attributes. A wrapper class around your params would allow you to more easily unit test this code. Maybe this helps get you started.
require 'ostruct'
class CompanyParamsWrapper
attr_accessor :text11, :text12, :text13
def initialize(params)
#content = params[:content]
content_struct = OpenStruct.new(#content)
self.text11 = content_struct.text11[:value]
self.text12 = content_struct.text12[:value]
self.text13 = content_struct.text13[:value]
end
end
# Company model
wrapper = CompanyParamsWrapper.new(params)
company.text11 = wrapper.text11
# now easier to use Object#send or other dynamic looping