what is activerecord.values.columnattribute - ruby-on-rails

I got an events helper module that somebody coded in a rails application. I am working on a form that can allow someone to create a new event.
here is a part of the form
=form.input :sponsorship_type, collection: get_event_labels(:event_types), as: :select_other
=form.input :society_name
it used to be
=form.input :event_type, collection: get_event_labels(:sponsorship_types), as: :select_other
=form.input :society_name
per the client request I had to drop the event_type column from the events table and added this instead
t.string "sponsorship_type"
the old schema has this
t.string "event_type"
this is the module
module EventsHelper
LABEL_MAP = {
institutions: [::INSTITUTIONS, 'activerecord.values.institutions.name'],
event_types: [::EVENT_TYPES, 'activerecord.values.event_types'],
industries: [::INDUSTRIES, 'activerecord.values.industries'],
referrers: [::REFERRERS, 'activerecord.values.referrers'],
regions: [::REGIONS, 'activerecord.values.regions'],
cities: [::CITIES, 'activerecord.values.cities']
}.freeze
def get_event_labels(type)
if Geokit::Geocoders::IpGeocoder.geocode(remote_ip).country_code == 'TW' and type == :event_types
return {
'活動/班' => 'Activities/Classes',
'食品和飲料' => 'Food&Beverage',
'優惠券' => 'Coupons',
'現金' => 'Cash',
'器材' => 'Equipment',
'獎品' => 'Prizes'
}
end
Hash[
LABEL_MAP[type][0].map do |constant|
[I18n.t("#{LABEL_MAP[type][1]}.#{constant}"),
constant]
end
]
end
def remote_ip
request.remote_ip
end
end
what is this? [::EVENT_TYPES, 'activerecord.values.event_types']
i tried just changing all the event_types to sponsorship_type. and then I am getting a
': uninitialized constant SPONSORSHIP_TYPES (NameError)
Its probably because activerecord.values.sponsorship_types have no values. How do I access it and put in values?
what is this?
::EVENT_TYPES
my end goal is to return the hash
return {
'活動/班' => 'Activities/Classes',
'食品和飲料' => 'Food&Beverage',
'優惠券' => 'Coupons',
'現金' => 'Cash',
'器材' => 'Equipment',
'獎品' => 'Prizes'
}
as selection option for the user on the form.

EVENT_TYPES is a constant. It must be defined somewhere in that application, perhaps in the controller or somewhere in the config folder. Find it and define your SPONSORSHIP_TYPES in the same way.
activerecord.values.event_types looks like a localization key. Look into your localization files in config/locales/... for some yaml hash with this structure. Add a new node sponsorship_types in the same way.

Related

Ruby - Ignore protected attributes

How can I tell Ruby (Rails) to ignore protected variables which are present when mass-assigning?
class MyClass < ActiveRecord::Base
attr_accessible :name, :age
end
Now I will mass-assign a hash to create a new MyClass.
MyClass.create!({:name => "John", :age => 25, :id => 2})
This will give me an exception:
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: id
I want it to create a new MyClass with the specified (unprotected) attributes and ignore the id attribute.
On the side note: How can I also ignore unknown attributes. For example, MyClass doesn't have a location attribute. If I try to mass-assign it, just ignore it.
Use Hash#slice to only select the keys you're actually interested in assigning:
# Pass only :name and :age to create!
MyClass.create!(params.slice(:name, :age))
Typically, I'll add wrapper method for params to my controller which filters it down to only the fields that I know I want assigned:
class MyController
# ...
def create
#my_instance = MyClass.create!(create_params)
end
protected
def create_params
params.slice(:name, :age)
end
end
Setting mass_assignment_sanitizer to :logger solved the issue in development and test.
config.active_record.mass_assignment_sanitizer = :logger
You can use strong_parameters gem, that will be in rails 4.
See the documentation here.
This way you can specify the params you want by action or role, for example.
If you want to get down and dirty with it, and dynamically let only a model's attributes through, without disabling ActiveModel::MassAssignmentSecurity::Errors globally:
params = {:name => "John", :age => 25, :id => 2}
MyClass.create!(params.slice(*MyClass.new.attributes.symbolize_keys.keys)
The .symbolize_keys is required if you are using symbols in your hash, like in this situation, but you might not need that.
Personally, I like to keep things in the model by overriding assign_attributes.
def assign_attributes(new_attributes, options = {})
if options[:safe_assign]
authorizer = mass_assignment_authorizer(options[:as])
new_attributes = new_attributes.reject { |key|
!has_attribute?(key) || authorizer.deny?(key)
}
end
super(new_attributes, options)
end
Use it similarly to :without_protection, but for when you want to ignore unknown or protected attributes:
MyModel.create!(
{ :asdf => "invalid", :admin_field => "protected", :actual_data => 'hello world!' },
:safe_assign => true
)
# => #<MyModel actual_data: "hello world!">

Params.sort.key how does it works?

Can anybody using this in own projects? If yes, how? I know that it helps to minimize of attacks.
If I have in the following in my controller:
mail_id = params[:mail_id].to_i
user_id = params[:user_id].to_i
token = params[:token]
where should I write params.sort.key [and what i should write here?]
I believe that concept should work as follows:
You expect 'params' hash to look somewhat like this:
params = { :mail_id => 1, :user_id => 2, :token => 'foo' }
which means params.keys will return [:mail_id, :user_id, :token]
params.keys.sort will return keys sorted alphabetically:
[:mail_id, :token, :user_id]
So to verify if params are holding the exact keys you expect it to have, you can do:
if params.keys.sort == [:mail_id, :token, :user_id]
render :text => 'Serving your request'
else
render :text => 'Server understood the request, but refuses to serve it, since some of the requested data is missing'
end
You can verify the exact contents of your 'params' hash using logger.debug(params.keys) in your controller

Is there find_or_create_by_ that takes a hash in Rails?

Here's some of my production code (I had to force line breaks):
task = Task.find_or_create_by_username_and_timestamp_and_des \
cription_and_driver_spec_and_driver_spec_origin(username,tim \
estamp,description,driver_spec,driver_spec_origin)
Yes, I'm trying to find or create a unique ActiveRecord::Base object. But in current form it's very ugly. Instead, I'd like to use something like this:
task = Task.SOME_METHOD :username => username, :timestamp => timestamp ...
I know about find_by_something key=>value, but it's not an option here. I need all values to be unique. Is there a method that'll do the same as find_or_create_by, but take a hash as an input? Or something else with similat semantics?
Rails 3.2 first introduced first_or_create to ActiveRecord. Not only does it have the requested functionality, but it also fits in the rest of the ActiveRecord relations:
Task.where(attributes).first_or_create
In Rails 3.0 and 3.1:
Task.where(attributes).first || Task.create(attributes)
In Rails 2.1 - 2.3:
Task.first(:conditions => attributes) || Task.create(attributes)
In the older versions, you could always write a method called find_or_create to encapsulate this if you'd like. Definitely done it myself in the past:
class Task
def self.find_or_create(attributes)
# add one of the implementations above
end
end
I also extend the #wuputah's method to take in an array of hashes, which is very useful when used inside db/seeds.rb
class ActiveRecord::Base
def self.find_or_create(attributes)
if attributes.is_a?(Array)
attributes.each do |attr|
self.find_or_create(attr)
end
else
self.first(:conditions => attributes) || self.create(attributes)
end
end
end
# Example
Country.find_or_create({:name => 'Aland Islands', :iso_code => 'AX'})
# take array of hashes
Country.find_or_create([
{:name => 'Aland Islands', :iso_code => 'AX'},
{:name => 'Albania', :iso_code => 'AL'},
{:name => 'Algeria', :iso_code => 'DZ'}
])

Globalize2 and migrations

I have used globalize2 to add i18n to an old site. There is already a lot of content in spanish, however it isn't stored in globalize2 tables. Is there a way to convert this content to globalize2 with a migration in rails?
The problem is I can't access the stored content:
>> Panel.first
=> #<Panel id: 1, name: "RT", description: "asd", proje....
>> Panel.first.name
=> nil
>> I18n.locale = nil
=> nil
>> Panel.first.name
=> nil
Any ideas?
I'm sure you solved this one way or another but here goes. You should be able to use the read_attribute method to dig out what you're looking for.
I just used the following to migrate content from the main table into a globalize2 translations table.
Add the appropriate translates line to your model.
Place the following in config/initializers/globalize2_data_migration.rb:
require 'globalize'
module Globalize
module ActiveRecord
module Migration
def move_data_to_translation_table
klass = self.class_name.constantize
return unless klass.count > 0
translated_attribute_columns = klass.first.translated_attributes.keys
klass.all.each do |p|
attribs = {}
translated_attribute_columns.each { |c| attribs[c] = p.read_attribute(c) }
p.update_attributes(attribs)
end
end
def move_data_to_model_table
# Find all of the translated attributes for all records in the model.
klass = self.class_name.constantize
return unless klass.count > 0
all_translated_attributes = klass.all.collect{|m| m.attributes}
all_translated_attributes.each do |translated_record|
# Create a hash containing the translated column names and their values.
translated_attribute_names.inject(fields_to_update={}) do |f, name|
f.update({name.to_sym => translated_record[name.to_s]})
end
# Now, update the actual model's record with the hash.
klass.update_all(fields_to_update, {:id => translated_record['id']})
end
end
end
end
end
Created a migration with the following:
class TranslateAndMigratePages < ActiveRecord::Migration
def self.up
Page.create_translation_table!({
:title => :string,
:custom_title => :string,
:meta_keywords => :string,
:meta_description => :text,
:browser_title => :string
})
say_with_time('Migrating Page data to translation tables') do
Page.move_data_to_translation_table
end
end
def self.down
say_with_time('Moving Page translated values into main table') do
Page.move_data_to_model_table
end
Page.drop_translation_table!
end
end
Borrows heavily from Globalize 3 and refinerycms.

Ruby Style Question: storing hash constant with different possible values

This is more of a style question, I'm wondering what other people do.
Let's say I have a field in my database called "status" for a blog post. And I want it to have several possible values, like "draft", "awaiting review", and "posted", just as an example.
Obviously we don't want to "hard code" in these magic values each time, that wouldn't be DRY.
So what I sometimes do is something like this:
class Post
STATUS = {
:draft => "draft",
:awaiting_review => "awaiting review",
:posted => "posted"
}
...
end
Then I can write code referring to it later as STATUS[:draft] or Post::STATUS[:draft] etc.
This works ok, but there are a few things I don't like about it.
If you have a typo and call something like STATUS[:something_that_does_not_exist] it won't throw an error, it just returns nil, and may end up setting this in the database, etc before you ever notice a bug
It doesn't look clean or ruby-ish to write stuff like if some_var == Post::STATUS[:draft] ...
I dunno, something tells me there is a better way, but just wanted to see what other people do. Thanks!
You can use Hash.new and give it a block argument which is called if a key is unknown.
class Post
STATUS = Hash.new{ |hash, key| raise( "Key #{ key } is unknown" )}.update(
:draft => "draft",
:awaiting_review => "awaiting review",
:posted => "posted" )
end
It's a bit messy but it works.
irb(main):007:0> Post::STATUS[ :draft ]
=> "draft"
irb(main):008:0> Post::STATUS[ :bogus ]
RuntimeError: Key bogus is unknown
from (irb):2
from (irb):8:in `call'
from (irb):8:in `default'
from (irb):8:in `[]'
from (irb):8
This is a common problem. Consider something like this:
class Post < ActiveRecord::Base
validates_inclusion_of :status, :in => [:draft, :awaiting_review, :posted]
def status
read_attribute(:status).to_sym
end
def status= (value)
write_attribute(:status, value.to_s)
end
end
You can use a third-party ActiveRecord plugin called symbolize to make this even easier:
class Post < ActiveRecord::Base
symbolize :status
end
You could use a class method to raise an exception on a missing key:
class Post
def self.status(key)
statuses = {
:draft => "draft",
:awaiting_review => "awaiting review",
:posted => "posted"
}
raise StatusError unless statuses.has_key?(key)
statuses[key]
end
end
class StatusError < StandardError; end
Potentially, you could also use this method to store the statuses as integers in the database by changing your strings to integers (in the hash), converting your column types, and adding a getter and a setter.
I do it like this:
class Post
DRAFT = "draft"
AWAITING_REPLY = "awaiting reply"
POSTED = "posted"
STATUSES = [DRAFT, AWAITING_REPLY, POSTED]
validates_inclusion_of :status, :in => STATUSES
...
end
This way you get errors if you misspell one. If I have multiple sets of constants, I might do something like DRAFT_STATUS to distinguish.
Take a look at the attribute_mapper gem.
There's a related article that shows how you can handle the problem declaratively, like this (borrowed from the article):
class Post < ActiveRecord::Base
include AttributeMapper
map_attribute :status, :to => {
:draft => 1,
:reviewed => 2,
:published => 3
}
end
...which looks rather stylish.
Even though this is an old post, for somebody stumbling across this, you can use the fetch method on Hash, which raises an error (when no default is passed) if the given key is not found.
STATUS = {
:draft => "draft",
:awaiting_review => "awaiting review",
:posted => "posted"
}
STATUS.fetch(:draft) #=> "draft"
STATUS.fetch(:invalid_key) #=> KeyError: key not found: invalid_key

Resources