Rails: Creating a Custom Data Type/Creating a Shorthand

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



Leave a reply



Submit