Where would I store static/constant values in a rails application? - ruby-on-rails-3

I have rails app and am wondering the best place to store the constants?
For example:
HELLO_EVERYONE = "hiz"
and then in a few controllers and views:
arr_used = [HELLO_EVERYONE]

It depends on where you need to access them.
If you need to use them throughout your application, you can put them in environment.rb
# environment.rb
#
# other global config info
HELLO_EVERYONE = "hiz"
If you need to access them only inside a specific class, you can define them in that model.
class Test < ActiveRecord::Base
HELLO_EVERYONE = "hiz"
end
EDIT
The second case (where the constant is defined in the Test class), can also be accessed outside of the Test class, only it needs to be referenced as Test::HELLO_EVERYONE.
This may be helpful in cases where you have a list of items relevant to the domain of that object (like a list of US states) that you might use in a view (e.g. select_tag :address, :state, options_for_select(Address::STATES)). Although I might consider wrapping this inside of a class method instead of exposing the internal structure of the class.
class Address< ActiveRecord::Base
STATES = ["AL", "AK", "AZ", "AR", ...]
def self.states
STATES
end
end

Related

Why is Model.associations an Array, but still supports sql queries? [duplicate]

In Rails, I have created a Model that retrieves users from an LDAP database rather than from ActiveRecord. Now I am attempting to integrate my ActiveRecord models with the LDAP-based models, so I am writing methods in my models that emulate some common ActiveRecord methods.
One of the methods I am trying to emulate is one that is normally created by the has_many through relationship on ActiveRecord. In ActiveRecord, this relationship would allow the following:
user = User.first
groups = user.groups # == Array of Groups
groups << Group.create(name: "Test") # How does Rails allow this?
How exactly does Rails allow this? I've tried dynamically assigning methods to the array instance returned by user.groups, but there doesn't seem to be any way to make those methods aware of which user record the array was created from. (So they can assign user_id on the new relationship record.) What am I missing?
Though user.groups appears to be an array of groups, it's actually an entirely separate class -- a Rails internal class that you usually don't know much about called an association proxy. The proxy responds to methods like <<, create, new and so on by proxying requests to the target class and then setting the association appropriately.
If you want similar functionality you'll have to implement your own kind of proxy associations. Doing so will be pretty complicated, but this might get you started.
module LDAP
class Association
attr_accessor :source, :target
def initialize(source, target)
#source = source
#target = target
end
def <<(obj)
#source.group_ids = [group_ids + obj].flatten.uniq
#source.save
end
end
end
class User
def groups
LDAP::Association.new(self, Group)
end
end
This is not even particularly close to how ActiveRecord implements association proxies. However, this is quite a bit simpler than ActiveRecord's solution and should be enough to duplicate some basic ActiveRecord functionality.
I would go about doing this by peeking into the Rails Source Code, e.g. the code for the
Group.create example above can be found in
http://api.rubyonrails.org/classes/ActiveRecord/Persistence/ClassMethods.html
def create(attributes = nil, options = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, options, &block) }
else
object = new(attributes, options, &block)
object.save
object
end
end
end

Rails + Devise - Session Controller explaination

I am playing around with Devise in a project, and am just trying to better understand how it all works. The Sessions controller in particular is doing a few things that I don't understand:
class Devise::SessionsController < ApplicationController
def new
# What benefit is this providing over just "resource_class.new"?
self.resource = resource_class.new(sign_in_params)
clean_up_passwords(resource)
# What is "serialize_options" doing in the responder?
respond_with(resource, serialize_options(resource))
end
def create
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => after_sign_in_path_for(resource)
end
...
protected
...
def serialize_options(resource)
methods = resource_class.authentication_keys.dup
methods = methods.keys if methods.is_a?(Hash)
methods << :password if resource.respond_to?(:password)
{ :methods => methods, :only => [:password] }
end
def sign_in_params
devise_parameter_sanitizer.sanitize(:sign_in)
end
end
I assume that these methods are adding some sort of security. I'd just like to know what exactly they are protecting against.
The implementation of devise_parameter_sanitizer is creating a ParameterSanitizer instance. I often find looking at tests to be helpful in understanding the purpose of code; and this test I think illustrates it best-- since you're creating a new user, you don't want to allow users to assign any value they want to any parameter they want, so sanitize means "strip out any attributes other than the ones we need for this action". If this wasn't here, and you had a role attribute, a user could send a specially-crafted POST request to make themselves an admin when signing up for your site. So this protects against what's called, in the general case, a mass assignment vulnerability. Rails 3 and Rails 4 protect against this in different ways but the protections can still be turned off, and I'm guessing Devise is trying to set some good-practice defaults.
The serialize_options method is creating a hash of options to support rendering to XML or JSON. I found this out by looking at the implementation of responds_with, which calls extract_options! which uses the last argument as options if the last argument is a Hash. The documentation for responds_with says "All options given to #respond_with are sent to the underlying responder", so I looked at ActionController::Responder, whose documentation explains the process it takes to look for a template that matches the format, then if that isn't found, calling to_#{format}, then calling to_format. There's a view for HTML, and ActiveRecord objects respond to to_xml and to_json. Those methods use those options:
The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
attributes included, and work similar to the +attributes+ method.
To include the result of some method calls on the model use <tt>:methods</tt>.
So this keeps you from exposing more information than you might want to if someone uses the XML or JSON format.

Rails: create lots of almost-duplicate records

I want to write a method that creates a bunch of almost-duplicate records, just with one or two parameters changed. I'll make a form to control those parameters, I'm just wondering about how best to write the method, and where do keep it.
Presently in my document.rb I've written this:
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create(:component_ids => components, :message => 'Message.', :template_id => template.id, :user_id => user.id)
end
end
It doesn't feel right though. Is there a better way to do this?
This code is fine if your security model allows all these fields to be bulk assignable by mention in attr_accessible in the model. If it doesn't then you're better off using the block form of create. Also, if Document, Template and User are ActiveRecord instances, you should let Rails manage the details of ids.
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create do |doc|
doc.component_ids = components,
doc.message 'Message.',
doc.template = template,
doc.user = user
end
end
end
One final note is that component_ids must be serialized to store a list. This is probably a flaw in your model design. The better way is (probably) to specify Component belongs_to User and also User has_many Components. I.e. Component contains a foreign key to User. If it's necessary for a Component to belong also to many users, then you'll need either has_and_belongs_to_many or has_many ... through. The Rails guide on relations describes all this in more detail.
With the right relations set up, the code will become:
def self.publish(brand, components, template)
brand.users.each do |user|
Document.create do |doc|
doc.components = components, # Components is now a list of active records.
doc.message 'Message.',
doc.template = template,
doc.user = user
end
end
end
The resulting SQL will get all the foreign keys and (if necessary) relation tables filled in correctly.

Virtual attribute not moved to the model hash inside params

I'm having a problem in my Rails 3.2 app where a virtual attribute sent restfully via JSON is not in the right place in the params hash. Well, it isn't where I expect. It remains to be seen if my expectations are correct. :)
I have a model using the standard virtual attribute pattern, like this:
class Track < ActiveRecord::Base
def rating
# get logic removed for brevity
end
def rating=(value)
# set logic
end
def as_json(options={}) # so my method is in the JSON when I use respond_with/to_json
super(options.merge(methods: [:rating]))
end
end
The JSON sent to my controller looks like this:
{"id":1,"name":"Icarus - Main Theme 2","rating":2}
To be clear, name and id are not virtual, rating is.
I end up with this in the params hash, after rails does its magic:
{"id"=>"1", "name"=>"Icarus - Main Theme 2", "rating"=>2, "track"=>{"id"=>"1", "name"=>"Icarus - Main Theme 2"}}
As you can see, id and name make it to the nested :track hash, but rating does not. Is this expected behavior? It breaks the (somewhat) standard practice of using the nested hash in the controller because the nested hash does not contain all the parameters I need.
Track.update(params[:id], params[:track]) # :track is missing rating
Thanks for your help!
I recently ran into this gotcha as well. The problem is, the params wrapper is looking at your model Track.attribute_names to determine how to map the data into a :track => {params} hash. If you don't have a model associated, the default will be to wrap the params based on the controller name, and include all of the values:
class SinglesController < ApplicationController
def create
#params[:single] will contain all of your attributes as it doesn't
# have an activerecord model to look at.
#track_single = Track.new(params[:single])
end
end
You can call wrap_parameters in your controller to tell action controller what attributes to include when its wrapping your params, like so:
class TracksController < ApplicationController
wrap_parameters :track, :include => :rating
#other controller stuff below
end
See more here: http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html
Maybe if you assign the rating virtual attribute inside the nested hash like this:
def as_json(options={})
super(options.merge(:track => {:methods => #rating}))
end
It would behave the way you expected.
Just ran across this problem and figured out a pretty decent solution. Add the following to your ApplicationController
wrap_parameters exclude: [:controller, :action, :format] + ActionController::ParamsWrapper::EXCLUDE_PARAMETERS
This way, everything is nested under your resource (except for stuff Rails adds to the params hash) and you won't ever have to append to a controller specific call of wrap_parameters again. :D

Track dirty for not-persisted attribute in an ActiveRecord object in rails

I have an object that inherits from ActiveRecord, yet it has an attribute that is not persisted in the DB, like:
class Foo < ActiveRecord::Base
attr_accessor :bar
end
I would like to be able to track changes to 'bar', with methods like 'bar_changed?', as provided by ActiveModel Dirty. The problem is that when I try to implement Dirty on this object, as described in the docs, I'm getting an error as both ActiveRecord and ActiveModel have defined define_attribute_methods, but with different number of parameters, so I'm getting an error when trying to invoke define_attribute_methods [:bar].
I have tried aliasing define_attribute_methods before including ActiveModel::Dirty, but with no luck: I get a not defined method error.
Any ideas on how to deal with this? Of course I could write the required methods manually, but I was wondering if it was possible to do using Rails modules, by extending ActiveModel functionality to attributes not handled by ActiveRecord.
I'm using the attribute_will_change! method and things seem to be working fine.
It's a private method defined in active_model/dirty.rb, but ActiveRecord mixes it in all models.
This is what I ended up implementing in my model class:
def bar
#bar ||= init_bar
end
def bar=(value)
attribute_will_change!('bar') if bar != value
#bar = value
end
def bar_changed?
changed.include?('bar')
end
The init_bar method is just used to initialise the attribute. You may or may not need it.
I didn't need to specify any other method (such as define_attribute_methods) or include any modules.
You do have to reimplement some of the methods yourself, but at least the behaviour will be mostly consistent with ActiveModel.
I admit I haven't tested it thoroughly yet, but so far I've encountered no issues.
ActiveRecord has the #attribute method (source) which once invoked from your class will let ActiveModel::Dirty to create methods such as bar_was, bar_changed?, and many others.
Thus you would have to call attribute :bar within any class that extends from ActiveRecord (or ApplicationRecord for most recent versions of Rails) in order to create those helper methods upon bar.
Edit: Note that this approach should not be mixed with attr_accessor :bar
Edit 2: Another note is that unpersisted attributes defined with attribute (eg attribute :bar, :string) will be blown away on save. If you need attrs to hang around after save (as I did), you actually can (carefully) mix with attr_reader, like so:
attr_reader :bar
attribute :bar, :string
def bar=(val)
super
#bar = val
end
I figured out a solution that worked for me...
Save this file as lib/active_record/nonpersisted_attribute_methods.rb: https://gist.github.com/4600209
Then you can do something like this:
require 'active_record/nonpersisted_attribute_methods'
class Foo < ActiveRecord::Base
include ActiveRecord::NonPersistedAttributeMethods
define_nonpersisted_attribute_methods [:bar]
end
foo = Foo.new
foo.bar = 3
foo.bar_changed? # => true
foo.bar_was # => nil
foo.bar_change # => [nil, 3]
foo.changes[:bar] # => [nil, 3]
However, it looks like we get a warning when we do it this way:
DEPRECATION WARNING: You're trying to create an attribute `bar'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc.
So I don't know if this approach will break or be harder in Rails 4...
Write the bar= method yourself and use an instance variable to track changes.
def bar=(value)
#bar_changed = true
#bar = value
end
def bar_changed?
if #bar_changed
#bar_changed = false
return true
else
return false
end
end