Rails: creating a custom data type / creating a shorthand
What you're looking to do is define a new column creation method that provides the options to create your custom type. Which is essentially done by adding a method that behaves like t.integer ...
in migrations. The trick is figuring out where to add that code.
Some where in your initializers directory place this snippet of code:
module ActiveRecord::ConnectionAdapters
class TableDefinition
def currency (*args)
options = args.extract_options!
column_names = args
options[:precision] ||= 8
options[:scale] ||= 2
column_names.each { |name| column(name, 'decimal', options) }
end
end
end
Now you can use the currency method do define a currency column any time you need it.
Example:
def self.up
create_table :products do |t|
t.currency :cost
t.timestamps
end
end
To add a currency column to an existing table:
def self.up
change_table :products do |t|
t.currency :sell_price
end
end
Caveat: I haven't time to test it, so there's no guarantees. If it doesn't work, it should at least put you on the right track.
creating a new column datatype in rails
Nope, you are approaching this in a wrong way. Database knows nothing about your application logic and it can only do basic validation (not null, etc.).
All of your application-specific validation, error messages and other stuff - it goes into your model. If you need that in several models and don't want duplicating the code, then create a model concern, HasIsraeliId
or something. That's what concerns are for.
# app/models/concerns/has_israeli_id.rb
module HasIsraeliId
extend ActiveSupport::Concern
included do
validate :some_field, whatever_condition: true
end
end
# app/models/user.rb
class User < ApplicationRecord
include HasIsraeliId
end
Is it possible to use a custom column type in a Rails migration?
I'm sure you can do what you are asking-- dig into the column method within the migrations code-- but I don't think it's supported naturally. I think the idea with the migrations are they are pretty close to the databases language (and further from you domain model), so the support is pretty database centric.
Look at using with_options (railscasts.com/episodes/42-with-options). This will allow you to DRY out a single migration without patching rails.
You can also make helper methods within the migration that DRYs it out. I do this all the time... the migration is just a Ruby class, so you could create:
def currency(column)
add_column :my_table, column, :deci... etc.
Rails - Render JSON for one object and another object's attribute
Use delegation to expose the method on log_date
as if it was a method on the log instance:
class Log < ApplicationRecord
belongs_to :log_date
delegate :date, to: :log_date
end
This is basically just a shorthand for:
class Log < ApplicationRecord
belongs_to :log_date
def date
log_date.date
end
end
You can include custom methods when rendering JSON through the methods
option:
render json: log, methods: [:date], status: :created
How to declare custom attribute accessor with dynamic argument in Ruby?
footnote_attrs :title, :body
is a method invocation. If you want to call the method in your class like this:
class Foo
footnote_attrs :title, :body
end
You have to define the method accordingly:
class Foo
def self.footnote_attrs(*args)
@footnote_attrs = args unless args.empty?
@footnote_attrs
end
# if footnote_attrs is to be accessed like an instance method
def footnote_attrs
self.class.footnote_attrs
end
footnote_attrs :title, :body
end
Foo.footnote_attrs #=> [:title, :body]
footnote_attrs #=> [:title, :body]
The implementation is very basic. If the method is called with arguments, it sets the instance variable accordingly. If it is called without arguments, it just returns it.
You might want to return an empty array if footnote_attrs
has not been called yet (currently it would return nil
). It might also be a good idea to return a copy (dup
) instead of the actual array to prevent modification.
How to define a Ruby Struct which accepts its initialization arguments as a hash?
Cant you just do:
def initialize(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
UPDATE:
To specify default values you can do:
def initialize(hash)
default_values = {
first_name: ''
}
default_values.merge(hash).each do |key, value|
send("#{key}=", value)
end
end
If you want to specify that given attribute is required, but has no default value you can do:
def initialize(hash)
requried_keys = [:id, :username]
default_values = {
first_name: ''
}
raise 'Required param missing' unless (required_keys - hash.keys).empty?
default_values.merge(hash).each do |key, value|
send("#{key}=", value)
end
end
Using a method for scopes in Active Record
It depends. If you want to make region validation in memory, as you write in the question's pseudocode, you have to use a class method
(or instance method
, depending on how are you going to use it). If you make region validation on a where clause, for instance, you should use a scope
method.
That is because a scope
returns an ActiveRecord::Relation
, and with that you can chain more 'scopes' methods. If you change the returned type object of the scope, you lose the chain ability, and that is pointless.
I am a little confused on how do you want to use findbylocation
method, and how it behave. Anyway, I strongly recommend you to read this article, that explains very well what is the difference between scopes
and methods
in rails.
Can you supply arguments to the map(&:method) syntax in Ruby?
You can create a simple patch on Symbol
like this:
class Symbol
def with(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
Which will enable you to do not only this:
a = [1,3,5,7,9]
a.map(&:+.with(2))
# => [3, 5, 7, 9, 11]
But also a lot of other cool stuff, like passing multiple parameters:
arr = ["abc", "babc", "great", "fruit"]
arr.map(&:center.with(20, '*'))
# => ["********abc*********", "********babc********", "*******great********", "*******fruit********"]
arr.map(&:[].with(1, 3))
# => ["bc", "abc", "rea", "rui"]
arr.map(&:[].with(/a(.*)/))
# => ["abc", "abc", "at", nil]
arr.map(&:[].with(/a(.*)/, 1))
# => ["bc", "bc", "t", nil]
And even work with inject
, which passes two arguments to the block:
%w(abecd ab cd).inject(&:gsub.with('cde'))
# => "cdeeecde"
Or something super cool as passing [shorthand] blocks to the shorthand block:
[['0', '1'], ['2', '3']].map(&:map.with(&:to_i))
# => [[0, 1], [2, 3]]
[%w(a b), %w(c d)].map(&:inject.with(&:+))
# => ["ab", "cd"]
[(1..5), (6..10)].map(&:map.with(&:*.with(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]]
Here is a conversation I had with @ArupRakshit explaining it further:
Can you supply arguments to the map(&:method) syntax in Ruby?
As @amcaplan suggested in the comment below, you could create a shorter syntax, if you rename the with
method to call
. In this case, ruby has a built in shortcut for this special method .()
.
So you could use the above like this:
class Symbol
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11]
[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]]
Here is a version using Refinements (which is less hacky than globally monkey patching Symbol
):
module AmpWithArguments
refine Symbol do
def call(*args, &block)
->(caller, *rest) { caller.send(self, *rest, *args, &block) }
end
end
end
using AmpWithArguments
a = [1,3,5,7,9]
a.map(&:+.(2))
# => [3, 5, 7, 9, 11]
[(1..5), (6..10)].map(&:map.(&:*.(2)))
# => [[2, 4, 6, 8, 10], [12, 14, 16, 18, 20]]
Rails CRUD route and controller method best practices
The simple explanation is:
because that's how God and DHH intended it.
The Rails CRUD conventions are quite pragmatic and allow you to avoid many pitfalls related to browser caching and security.
Lets take one example:
# config/routes.rb
resources :users, only: [:new, :create]
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
sign_in(@user)
redirect_to root_path
else
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation)
end
end
Here we two separate routes GET /users/new
and POST /users
.
The first route is idempotent - it should look the same for any visitor and can be cached. The second is not - it should show the result of creating or attempting to create a resource.
When a user visits /users/new
we POST the form to a different URI. That avoids history issues in the client.
We also render the form in the same request cycle if the input is invalid. This avoids the security issues that would arise if we tried to pass the form data back in redirect to /users/new
and lets us return the semantically correct response code instead of a redirect.
It also ensures that our application is stateless in the restful sense since the previous action does not influence what we see if we were to visit /users/new
.
On an architecture level, it allows you to leverage convention over configuration. You can just do:
def new
@user = User.new
end
And it will render views/users/new.html.erb
because Rails can derive that the new action should render the new template. Having each controller action perform one single task is far better from a design and testing standpoint as it eliminates the need to test two separate code paths in the same method.
How should I write multiple condition in after_create callbacks
You can also do this for a shorter readable version
after_save :update_offices_people_count if: -> {office_id_changed? || trashed_changed?}
P.S: ->
is a shorthand version of writing lambda
.
Related Topics
App Pushed to Heroku Still Shows Standard Index Page
Namespacing Thor Commands in a Standalone Ruby Executable
How to Structure a Layout Template in Haml
Carrierwave Crop Specific Version
Ruby: How to Make a Public Static Method
Bundle Install Could Not Fetch Specs from Https://Rubygems.Org/
How to Force Ruby to Show a Full Stack Trace
How to Add Two Weeks to Time.Now
How to Remove Validation Using Instance_Eval Clause in Rails
Convert JSON String to JSON Array in Rails
Unit Test in Rails - Model with Paperclip
Passing Param Values to Redirect_To as Querystring in Rails
Ruby: Get All Keys in a Hash (Including Sub Keys)
How to Introspect Things in Ruby
Can't Convert Fixnum to String During Rake Db:Create
How to Get a List of All Tags While Using the Gem 'Acts-As-Taggable-On' in Rails (Not the Counts)