How to choose a continent by the country? - ruby-on-rails

I use Ruby on Rails 5.2 and mongoid 7.0
I need to choose a continent by the country
I understand that it should look something like this:
class Place
field :country, type: String
field :continent, type: String
after_save :update_continent
def update_continent
cont = self.country
case cont
when 'United States', 'Grenada'
'NA'
when 'Netherlands', 'Spain'
'EU'
end
self.continent = cont
end
end

Since you indicated you are using Mongoid:
Each Mongoid model class must include Mongoid::Document, per the documentation in https://docs.mongodb.com/mongoid/master/tutorials/mongoid-documents/.
after_save callbacks are normally used for things like creating external jobs, not for setting attributes, because the attribute changes won't be persisted (as the model was already saved). Usually attribute changes are done in before_validation or before_save callbacks. See https://guides.rubyonrails.org/active_record_callbacks.html for the list of available callbacks.
As pointed out by Toby, the case statement is not correctly used. Its result should be assigned like this:
.
def update_continent
self.continent = case self.country
when 'United States', 'Grenada'
'NA'
when 'Netherlands', 'Spain'
'EU'
end
end

You haven't given enough context to be able to answer your question, but since you just want to be pointed in the right direction, and since you seem to be new here I'm happy to give you some pointers.
You're class uses the after_save method as if it is an ActiveRecord Model, but without extending or including anything it's just a Plain Old Ruby Object. To make the after_save callback work you need to at least extend ActiveModel::Callbacks but probably you want to make it a full ActiveRecord Model. To do that in Rails 4 you subclass ActiveRecord::Base and in rails 6 you subclass ApplicationRecord But I don't actually know how it's done in Rails 5.
If you have a normal database in the back end as is usual for rails you don't need to declare the fields, it automatically gets them from the equivalent table in the database (though perhaps this is not true when using Mongoid. I don't know). if you run this command in your terminal in your app base directory: rails generate model Place country:string continent:string it will create the migration file needed to make the database table and the Model file (with whatever the correct superclass is) and you wont need to do all the boilerplate stuff yourself.
You have a variable named cont and you assign a country to it. This will get very confusing given that you also have a separate concept of "continent" Better to not abbreviate your variable names and choose sensible naming.
You're not using the case statement correctly. The output of the statement doesn't automatically get assigned to the the variable you're switching on. You need to read up on Ruby syntax.
Overall I suspect in the long run you would do well to have separate models for Continent and Country. With a Continent having many countries and a country belonging to a continent. Rails is a framework that makes that sort of thing very easy to do and manage. You probably need to read some more and look at examples and videos about the basics of Ruby on Rails.
I highly recommend The Rails Tutorial by Hartl. It's free online. Working through that or an equivalent should give you a much better understanding of how Rails is equipped to handle your situation and how to best utilise it to get the outcome you need. This was indispensable for me when I was first starting out with Rails.

Related

How do I specify column name and types while extending ActiveRecord::Base in Ruby on Rails?

Forgive my ignorance if I am missing something really trivial, I am very new to RoR.
Coming from Django background I remember models being like
class Post(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
both column name and field type are clearly specified.
Where as, when I write this rails command
> rails g model Post title:string description:text
all I get is
class Post < ActiveRecord::Base
attr_accessible :description, :title
end
Is there a way to write column names and field types while extending ActiveRecord::Base instead of having them only in the migration file?
Thanks, any help is appreciated
attr_accessible is deprecated and strong params replaces it functionality in the controller.
If you want to get a list of the schema information in your model file you can annotate it at the top of the file. This was best practices at some point, however I do not think it is used as much. I personally do not like this and just use the schema.
Post on best practices and annotating:
http://rails-bestpractices.com/posts/68-annotate-your-models
Gem to auto annotate:
https://github.com/ctran/annotate_models
The most wonderful thing about ActiveRecord is that you don't need to do any mapping, as all the fields are being automatically mapped using current schema. Of course default mapping can be overridden if needed, however this is a very rare case.
This approach is called "convention over configuration" and is present all over Rails - it assumes most common parameters for what you are trying to achieve so it saves a lot of unnecessary coding and mapping. It might feel weird at start, especially that you'll need to learn how to override those defaults, but I promise you are gonna love it when you get used to it. :)

Rails gem/plugin for dynamic custom fields in model

Is there any gem/plugin for ruby on rails which gives the ability to define custom fields in a model at runtime with no need to change the model itself for every different field.
I'm looking for something like Redmine acts_as_customizable plugin which is packaged as a gem usable in the rails way, i.e.
gem 'gemname'
rails g something
rails db:migrate
class Model < ActiveRecord::Base
acts_as_something
end
Here are the CustomField and the CustomValue classes used in Redmine.
Edit:
Since my question is not clear I add a brief use case which explains my need better:
I want users to be able to design their own forms, and collect data
submitted on those forms. An important decision is the design of how
these custom dynamic records are stored and accessed.
Taken from here, in this article approach the problem with different ideas, but they all have drawbacks. For this reason I'm asking if the issue has been approached in some gem with no need to rethink the whole problem.
I'm not aware of a gem that does this, but serialize works quite well and it's a built-in. You get a NoSQL-ish document store backed by JSON/YAML.
If you allow user to create a custom form, you can pass nested arrays et cetera directly into the attribute. However, if you need to validate the structure, you're on your own.
I'm afraid it could be tricky and complicated to do it in ActiveRecoand (generally in standard relational database). Take a look at http://mongoid.org/docs/documents/dynamic.html - this mechanism is using nosql feature.
You can also may try the following trick:
1/ Serialize a hash with your custom fields in the database column, for example { :foo => 'bar', :fiz => 'biz' }
2/ After load a record from database do some metaprogramming and define corresponding methods on the record's singleton class, for instance (assume that custom fields are stored and serialized in custom_fields column):
after_initialize :define_custom_methods
# ..or other the most convinient callback
def define_custom_methods
# this trick will open record's singleton class
singleton_class = (class << self; self; end)
# iterate through custom values and define dynamic methods
custom_fields.each_with_key do |key, value|
singleton_class.send(:define_method, key) do
value
end
end
end
Since rails 3.2 you can use store method. Just include following in your model:
store :properties, accessors: [:property1, :property2, :property3...]
You only need to change your model once (to add properties field to db table). You can add more properties later without altering the schema.
The way this works is by serializing properties hash into YAML and saving it into database. It it suitable for most cases, but not if you'd like to use these values in db queries later.
I don't know a gem, but this can be accomplished be creating a table called custom_fields with a name column and possibly a datatype column if you wanted to restrict fields by datatype.
Then you create a join table for a custom field to your desired table and a value and do whatever validations you want.

Rails Best Practice for User-Configurable Global Attribute "Defaults"

Sorry about the awkward phrasing of the title -- not quite sure of the best way to title this but here's what I'm seeing assistance with:
In a Rails app, let's say we've got a model for a Product, and one of the attributes of the product is Price.
On the admin side of my app, I'd like to be able to set a "default" price that could be referred to if any new Product created isn't assigned a Price. If a Product does have a value for Price, then it would be used.
This is, of course, an example -- and I ask this question because I've got to imagine this is a common pattern. This can be applied to any resource that might have user-configurable global defaults or resource-specific values.
In pure Ruby, this would be solved, I think, with a class variable, so I'd be able to define ##default_price within the Product class and be able to refer to Product.default_price if the instantiated object's value doesn't exist.
My research here has pointed me towards the rails-settings-cached gem, which would allow for something like MyApp.default_price, but I'm wondering if there's a more elegant (non-plugin) way to accomplish this within the base Rails framework.
Note I'd like to setup this structure in code, but I want to be able to define the actual values through my app (i.e. config files aren't the solution I'm looking for).
Can someone enlighten me with the Rails way of handling this?
ActiveRecord picks up default attribute values from the database schema. However, these are baked into the migration and table schema and not configurable.
If you want configurability, the pattern that I've used is a before_validation callback method to set a value if the attribute is blank, e.g.:
class Product < ActiveRecord::Base
before_validation :set_price_if_blank
validates :price, :presence => true # sanity check in case the default is missing
has_one :price
private
def set_price_if_blank
self.price = Price.default if self.price.blank?
end
end
class Price < ActiveRecord::Base
def self.default
##default ||= Price.where(:default => true).first
end
end
This assumes that your price table is populated with a row that has a default flag. You could achieve this, e.g. through a seeds.rb file. I've added a validation rule to make sure that you still get an error if no default exists. It adds robustness to your application.
Also note that it's best to use Integers or Decimals for price data, not floats. See this answer.

Creating readable models in rails

I have just started with Rails and coming from a .net background I find the model inheriting from ActiveRecord is hard to understand, since the don't contain the corresponding attributes for the model. I cannot imagine a new developer exposed to a large code where the models only contains references to other models and business logic.
From my point of view the DataMapper model is much easier to grasp but since ActiveRecord is the defacto standard it feels weird to change the ORM just for this little problem.
DataMapper
class Post
include DataMapper::Resource
property :id, Serial # An auto-increment integer key
property :title, String # A varchar type string, for short strings
property :body, Text # A text block, for longer string data.
property :created_at, DateTime # A DateTime, for any date you might like.
end
ActiveRecord
class Post < ActiveRecord::Base
end
I'm not sure if this is an issue and that people get used to the models without attributes, or how does experienced rails user handle this?
I don't think using the database manager or looking at loads of migrations scripts to find the attributes is an option?
Specifying attr_accessible will make the model more readable but I'm not sure if it's a proper solution for my problem?
Check out the annotate_models plugin on github. It will insert a commented schema for each model in a comment block. It can be installed to run when migrate is.
You don't have to "look at loads of migration scripts to find the attributes" - they're all defined in one place in db/schema.rb.
A few tips:
Load up the Rails console and enter
Post.column_names for a quick
reminder of the attribute names.
Post.columns gives you the column
objects, which shows the datatypes
db/schema.rb contains all the
migration code in one place, so you
can easily see all the column
definitions.
If you are using a
decent editor/IDE there should be a way to
allowing you to jump from the model file
to the migration file. (e.g. Emacs
with ROR or Rinari)

Best practice for converting integer value column to string representation

Lets say you have a model like the following:
class Stock < ActiveRecord::Base
# Positions
BUY = 1
SELL = 2
end
And in that class as an attribute of type integer called 'position' that can hold any of the above values. What is the Rails best practice for converting those integer values into human readable strings?
a) Use a helper method, but then you're force to make sure that you keep the helper method and model in sync
def stock_position_to_s(position)
case position
when Stock::BUY
'buy'
when Stock::SELL
'sell'
end
''
end
b) Create a method in the model, which sort of breaks a clean MVC approach.
class Stock < ActiveRecord::Base
def position_as_string
...snip
end
end
c) A newer way using the new I18N stuff in Rails 2.2?
Just curious what other people are doing when they have an integer column in the database that needs to be output as a user friendly string.
Thanks,
Kenny
Sounds to me like something that belongs in the views as it is a presentation issue.
If it is used widely, then in a helper method for DRY purposes, and use I18N if you need it.
Try out something like this
class Stock < ActiveRecord::Base
##positions => {"Buy" => 1, "Sell" => 2}
cattr_reader :positions
validates_inclusion_of :position, :in => positions.values
end
It lets you to save position as an integer, as well as use select helpers easily.
Of course, views are still a problem. You might want to either use helpers or create position_name for this purpose method
class Stock < ActiveRecord::Base
##positions => {"Buy" => 1, "Sell" => 2}
cattr_reader :positions
validates_inclusion_of :position, :in => positions.values
def position_name
positions.index(position)
end
end
Is there a good reason for the app be converting the integer to the human readable string programmatically?
I would make the positions objects which have a position integer attribute and a name attribute.
Then you can just do
stock.position.name
#HermanD: I think it's a lot better to store the values in an integer column rather than a string column for numerous reasons.
It saves database space.
Easier/faster to index on an integer than a string.
Your not hard coding a human readable string as values in a database. (What happens if the client says that "Buy" should become "Purchase"? Now the UI shows "Purchase" everywhere but you need to keep setting "Buy" in the database.)
So, if you store certain values in the database as integers, then at some point, you're going to need to show them to the user as strings, and I think the only way you can do that is programatically.
You could move this info into another object but, IMHO, I'd say this is overkill. You'd then have to add another database table. Add another 'admin' section for adding, removing and renaming these values and so on. Not to mention that if you had several columns, in different models that needed this behavior, you'd either have to create lots of these objects (ex: stock_positions, stock_actions, transaction_kinds, etc...) or you'd have to design it generically enough to use polymorphic associations. Finally, if the position name is hard coded, then you lose the ability to easy localize it at a later date.
#frankodwyer: I'd have to agree that using a helper method is probably the best way to go. I was hoping their might be a "slicker" way to do this, but it doesn't look like it. For now, I think the best method is to create a new helper module, maybe something like StringsHelper, and stuff a bunch of methods in their for converting model constants to strings. That way I can use all the I18N stuff in the helper to pull out the localized string if I need to in the future. The annoying part is that if someone needs to add a new value to the models column, then they will also have to add a check for that in the helper. Not 100% DRY, but I guess "close enough"...
Thanks to both of you for the input.
Kenny
Why not use the properties of a native data structure? example:
class Stock < ActiveRecord::Base
ACTIONS = [nil,'buy','sell']
end
Then you could grab them using Stock::ACTIONS[1] #=> 'buy' or Stock::ACTIONS[2] #=> 'sell'
or, you could use a hash {:buy => 1, :sell => 2} and access it as Stock::ACTIONS[:buy] #=> 1
you get the idea.
#Derek P. That's the implementation I first went with and while it definitely works, it sort of breaks the MVC metaphor because the model, now has view related info defined in its class. Strings in controllers are one thing, but strings in models (in my opinion) are definitely against the spirit of clean MVC.
It also doesn't really work if you want to start localizing, so while it was the method I originally used, I don't think it's the method for future development (and definitely not in an I18N world.)
Thanks for the input though.
Sincerely,
Kenny
I wrote a plugin that may help a while ago. See this. It lets you define lists and gives you nice methods ending in _str for display purposes.

Resources