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

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

Related

Running multiple SQL queries in Rails without closing ActiveRecord.connection

So I have a rails module called DbsfnpService.
Within this, I have a class
class DbnsfpAccess < ActiveRecord::Base
establish_connection :dbnsfp
end
I then have many methods within DbsfnpService similar to this.
def get_tables
sql = "select * from dbnsfp limit 1"
results = DbnsfpAccess.connection.execute(sql)
return results
end
When I call these methods from another class by including DbsnfpService, I would like to only establish one connection to :dbnsfp and handle all my queries and then close that connection. I believe how it is now, every method I call that contains DbsnfpAccess.connection.execute(sql) is making a separate connection(?). What is the best way to achieve this? Passing in a connection object into these functions? Thanks in advance!
If I'm understanding your question correctly, you want to execute calls on the database without knowing the table names and therefore unable to create rails ActiveRecord table models ahead of time.
Instead of executing sql you can use ActiveRecord::Base.connection.tables
(see this link How to list of all the tables defined for the database when using active record?)
To use these table names what you will need is to turn DbnsfpAccess into an abstract class.
class DbnsfpAccess < ActiveRecord::Base
self.abstract_class = true
establish_connection :dbnsfp
end
Then you can create the models you want to connect to DbnsfpAccess like this:
class DynamicallyGenratedDbnsfpModel << DbnsfpAccess

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.

How to hanlde list data that doesn't belong to a model an isn't a model by its own in rails?

In my rails project I have diferent list of data that I have to mantain with CRUD operations and each list doesn't deserve a model or an entire scaffolding to maitain it, what's the best way to handle this on rails?
Now I'm using a List model with name:string content:text to save each list as a list record and do some parsing when I need some list in my app. Here is my actual list model:
class NoListException < Exception
end
class List < ActiveRecord::Base
validates :name, uniqueness: true
def self.container_types
get_list('container_types').collect do |b|
b.split(',').collect {|c| c.split(':').last }
end.collect {|p| "#{p.last} - #{p.first}" }
end
def self.location_categories
get_id_value_list('location_categories')
end
def self.services_types
get_list('services_types')
end
private
def self.get_id_value_list(name)
get_list(name).collect do |b|
(b.split(',').collect {|c| c.split(':').last }).rotate
end
end
def self.get_list(name)
list = List.find_by_name(name)
raise NoListException if list.nil?
list.content.split(';')
end
end
I think is a very common problem, because of that I ask if there are a better way to handle those lists?
Its not bad to have a model with no scaffolding to support it. I often do this with category or tag like models which are often created and managed by the models they act upon. So don't feel pressured to build out a whole scaffolding for a simple model.
If you don't need to persist the data to the database then you can always use ActiveModel, or if you do need to persist and can find another model to piggy back ontop of, look into serialization, its a good way to store loose data

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