alias_attribute vs read_attribute and write_attribute - ruby

I'm using ActiveRecord without rails. Everything works fine except for a weird quirk with some helper methods I'm writing that I'm hoping someone can explain. I've got a model of a legacy database. Some columns names have a "#" in them so I defined them in the model using read_attribute and write_attribute. For example (accurate example but simplified):
class Product < ActiveRecord::Base
alias_attribute :name, :pname
def sale_number
read_attribute 'sale#'
end
def sale_number=(value)
write_attribute 'sale#', value
end
def self.helper_with_alias
where(name: 'My Product Name')
end
def self.helper_with_attribute
where(sale_number: 5)
end
end
If I call Product.helper_with_alias everything works as expected. But when I call Product.helper_with_attribute I get a ActiveRecord::StatementInvalid error saying that the column sale_number could not be found. In addition, if I replace the code in helper_with_attribute to where('sale#' => 5) everything works fine.
Why does ActiveRecord correctly alias pname to name but not correctly alias sale# to sale_number?

The error is because you are using an in-existent column in the where clause. You also need to define alias_attribute for sale# to sale_number.
In your model, you can do:
class Product < ActiveRecord::Base
alias_attribute :sale_number, :"sale#"
def self.helper_with_attribute
where(sale_number: 5)
end
end
With this you don't need to define setters and getters just for assignment and retrieval purposes, so you can remove sale_number and sale_number=(value) methods. With alias_attribute, getters, setters and query methods are already aliased!
Why does ActiveRecord correctly alias pname to name but not correctly
alias sale# to sale_number?
This is because you have defined alias_attribute :name, :pname which provided the setters, getters and query methods as alias to your existing pname attribute. But, for sale_number, you've only defined a getter and setter but not the query methods.

Related

Missing ActiveRecord methods (find_by) for object

I am trying to do an assignment which requires me to create and save an ActiveRecord within my Model class, and then return it. The rspec is expecting to use the find_by method to verify this. Here's my Model:
-----------------------
class User < ActiveRecord::Base
attr_accessor :id, :username, :password_digest, :created_at, :updated_at
after_initialize :add_user
def initialize(attributes={})
#username = attributes[:username]
#password_digest = attributes[:password_digest]
super
end
def add_user
self[:username] = #username
self[:password_digest] = #password_digest
self.save
self[:id] = self.id
end
end
----------------
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object. So, rspec fails. I have looked everywhere but can't seem to find the solution. I am a noob, so sorry if the answer is obvious and I can't see it.
You say
If I do User.new(params), the record is in fact stored properly to the DB. But, the find_by method is missing for the returned object
This is expected behavior. Hopefully you understand by now the difference between class and instance methods. The main important point is that query methods such as find_by are not made available to model instances. If you do something like user = User.find_by(id: params[:id]), you're calling the find_by class method on the User model.
There are a number of methods like where, order, limit, etc. that are defined in ActiveRecord::QueryMethods - these are made available to ActiveRecord::Relation object and your model class. Most of these methods will return ActiveRecord::Relation objects, which is why they're chainable, e.g.
User.where(params).order(created_at: :desc).limit(5)
However find_by is an exception - it returns a model instance so you can't continue to query on the results. In summary User.new(params) returns an instance of the model which doesn't have find_by available

How do you call the where method in an ActiveRecord abstract class?

We use database resource tables to provide the values that appear in our application's combo boxes. Each resource table has a display_name column and a service_name column. The display name is what the user sees and the service name is sent to the web service. Some of our fields are optional and I have added an is_default column. I would like to create an abstract class that returns the default service name. Here is my first effort. However, when the CondenserPumpControlType.default_service_name method is called I get:
Exception message: undefined method where' for Class:Class Stack
trace: ["/apps/ar2/app/models/defaultable_record.rb:5:in
default_service_name'"
I expected the abstract class to invoke CondenserPumpControlType.where and instead it appears to invoke Class.where. What am I doing wrong. I can't seem to find much documentation on ActiveRecord abstract classes.
class DefaultableRecord < ActiveRecord::Base
self.abstract_class = true
def self.default_service_name
default_value = self.class.where(is_default: 1).first
default_value.service_name
end
end
class CondenserPumpControlType < DefaultableRecord
attr_accessible :display_name, :service_name, :sort_order, :is_default
end
self.class.where(is_default: 1).first
should be
self.where(is_default: 1).first

Make friendly_id scope play nice with subclassed ActiveRecord model

I have a subclassed ActiveRecord model which uses a separate table to store records and friendly_id (4.1.0.beta.1) to generate slugs. Problem is friendly_id is using the parent class's table to check for existing slugs, instead of using the child table. Basically I'd like friendly_id to scope its checks to the right table.
Example:
class Parent
friendly_id :name, :use => :slugged
end
class Child < Parent
self.table_name = 'children'
end
Parent.create(name: 'hello').slug
> 'hello'
Child.create(name: 'hello').slug
> 'hello--2'
I want friendly_id to generate the 'hello' slug for the second create, because there are no records in the children table with that slug. Is there a way to configure or monkey patch the class friendly id uses for its queries?
EDIT: added friendly_id version for future reference
I'm posting my own solution to this problem, just in case someone is having the same problem. I should reiterate that this problem was found on version 4.1.0.beta.1 of the friendly_id gem (which at the time was the most recent version), so this issue may not occur any more.
To solve this problem, I basically configured slug_generator_class to use my own class, so I could monkey patch the culprit method.
In my model:
friendly_id do |config|
config.slug_generator_class = SubclassScopableSlugGenerator
end
In an initializer, I overrode the FriendlyId::SlugGenerator.conflicts method so I could access the sluggable_class var:
# Lets a non-STI subclass of a FriendlyId parent (i.e. a subclass with its
# own dedicated table) have independent slug uniqueness.
class SubclassScopableSlugGenerator < FriendlyId::SlugGenerator
private
def conflicts
# this is the only line we're actually changing
sluggable_class = friendly_id_config.model_class
pkey = sluggable_class.primary_key
value = sluggable.send pkey
base = "#{column} = ? OR #{column} LIKE ?"
# Awful hack for SQLite3, which does not pick up '\' as the escape character without this.
base << "ESCAPE '\\'" if sluggable.connection.adapter_name =~ /sqlite/i
scope = sluggable_class.unscoped.where(base, normalized, wildcard)
scope = scope.where("#{pkey} <> ?", value) unless sluggable.new_record?
length_command = "LENGTH"
length_command = "LEN" if sluggable.connection.adapter_name =~ /sqlserver/i
scope = scope.order("#{length_command}(#{column}) DESC, #{column} DESC")
end
end

Issue loading classes order EDIT: works, although some odd behavior along the way

I'm working on a project to recreate some of the functionality of ActiveRecord. Here's the portion that isn't working
module Associations
def belongs_to(name, params)
self.class.send(:define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
self.class.send(:define_method, :other_table_name) do |other_class|
other_class.table_name
end
.
.
.
o_c = other_class(name, params)
#puts this and other (working) values in a query
query = <<-SQL
...
SQL
#sends it off with db.execute(query)...
I'm building towards this testing file:
require 'all_files' #holds SQLClass & others
pets_db_file_name = File.expand_path(File.join(File.dirname(__FILE__), "pets.db"))
DBConnection.open(pets_db_file_name)
#class Person
#end
class Pet < SQLClass
set_table_name("pets")
set_attrs(:id, :name, :owner_id)
belongs_to :person, :class_name => "Person", :primary_key => :id, :foreign_key => :owner_id
end
class Person < SQLClass
set_table_name("people")
set_attrs(:id, :name)
has_many :pets, :foreign_key => :owner_id
end
.
.
.
Without any changes I received
.../active_support/inflector/methods.rb:230:in `block in constantize': uninitialized constant Person (NameError)
Just to make sure that it was an issue with the order of loading the classes in the file I began the file with the empty Person class, which, as predicted gave me
undefined method `table_name' for Person:Class (NoMethodError)
Since this is a learning project I don't want to change the test to make my code work (open all the classes, set all the tables/attributes then reopen them them for belongs_to. But, I'm stuck on how else to proceed.)
EDIT SQLClass:
class SQLClass < AssignmentClass
extend SearchMod
extend Associations
def self.set_table_name(table_name)
#table_name = table_name
end
def self.table_name
#table_name
end
#some more methods for finding rows, and creating new rows in existing tables
And the relevant part of AssignmentClass uses send on attr_accessor to give functionality to set_attrs and makes sure that before you initialize a new instance of a class all the names match what was set using set_attrs.
This highlights an important difference between dynamic, interpreted Ruby (et al) and static, compiled languages like Java/C#/C++. In Java, the compiler runs over all your source files, finds all the class/method definitions, and matches them up with usages. Ruby doesn't work like this -- a class "comes into existence" after executing its class block. Before that, the Ruby interpreter doesn't know anything about it.
In your test file, you define Pet first. Within the definition of Pet, you have belongs_to :person. belongs_to does :person.constantize, attempting to get the class object for Person. But Person doesn't exist yet! Its definition comes later in the test file.
There are a couple ways I can think that you could try to resolve this:
One would be to do what Rails does: define each class in its own file, and make the file names conform to some convention. Override constant_missing, and make it automatically load the file which defines the missing class. This will make load order problems resolve themselves automatically.
Another solution would be to make belongs_to lazy. Rather than looking up the Person class object immediately, it could just record the fact that there is an association between Pet and Person. When someone tries to call pet.person, use a missing_method hook to actually define the method. (Presumably, by that time all the class definitions will have been executed.)
Another way would be do something like:
define_method(belongs_to) do
belongs_to_class = belongs_to.constantize
self.class.send(:define_method, belongs_to) do
# put actual definition here
end
self.send(belongs_to)
end
This code is not tested, it's just to give you an idea! Though it's a pretty mind-bending idea, perhaps. Basically, you define a method which redefines itself the first time it is called. Just like using method_missing, this allows you to delay the class lookup until the first time the method is actually used.
If I can say one more thing: though you say you don't want to "overload" method_missing, I don't think that's as much of a problem as you think. It's just a matter of extracting code into helper methods to keep the definition of method_missing manageable. Maybe something like:
def method_missing(name,*a,&b)
if has_belongs_to_association?(name)
invoke_belongs_to_association(name,a,b)
elsif has_has_many_association?(name)
invoke_has_many_association(name,a,b)
# more...
else
super
end
end
Progress! Inspired by Alex D's suggestion to use method_missing to delay the creation I instead used define_methodto create a method for the name, like so:
define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
define_method(:other_table_name) do |other_class|
other_class.table_name
end
#etc
define_method(name) do #|params| turns out I didn't need to pass in `params` at all but:
#p "---#{params} (This is line 31: when testing this out I got the strangest error
#.rb:31:in `block in belongs_to': wrong number of arguments (0 for 1) (ArgumentError)
#if anyone can explain this I would be grateful.
#I had declared an #params class instance variable and a getter for it,
#but nothing that should make params require an argument
f_k = foreign_key(name, params)
p f_k
o_c = other_class(name, params)
o_t_n = other_table_name(o_c)
p_k = primary_key(params)
query = <<-SQL
SELECT *
FROM #{o_t_n}
WHERE #{p_k} = ?
SQL
row = DBConnection.execute(query, self.send(f_k))
o_c.parse_all(row)
end

Clean association definitions with Ruby Sequel

I am using Jeremy Evan's Sequel to populate an (SQLite) database with data I scrape from web pages.
The database involves a number of many_to_many relationships that I express with Associations.
The associations are created in class definitions, which are always evaluated when the script is run.
Importantly, the association class definitions need to have the necessary tables in place.
Thus the table creation methods should be in the top level with the association definitions.
Here is an example:
module Thing
db = Sequel.Sqlite('data.sqlite')
db.create_table(:clients)
String :client_id, :primary_key => true
String :client_data
end
db.create_table(:orders)
String :order_id, :primary_key => true
String :order_data
end
db.create_table(:orders_clients)
String :order_id
String :client_id
primary_key [:order_id,:client_id]
end
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
end
First of all, I think that this is a rather dirty solution, since my method calls and class definitions sit in the same namespace.
If I try to separate the class definitions, I get No database associated with Sequel::Model error (which makes sense, but I want to defer the evaluation of the association definitions, having those after the table calls, whenever they might happen).
I want to be able to create the tables and associations in a method call. Thus, I could for example pass the name of the new database file:
def create_tables_and_schema (database_name)
db = Sequel.Sqlite(database_name)
db.create_table... #three of those, as above
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
end
What I think is needed is a different way to express table relations.
Any suggestions on approach and style are appreciated. Please ask for clarifications if the explanation is confusing.
Your method calls and class definitions do not need to sit in the same namespace, it's just that the tables need to be created before the model classes. An easy way to separate them is to move the table creation to a separate file. Also, usually you assign the database object to a constant.
create_tables.rb:
DB.create_table(:clients)
String :client_id, :primary_key => true
String :client_data
end
DB.create_table(:orders)
String :order_id, :primary_key => true
String :order_data
end
DB.create_table(:orders_clients)
String :order_id
String :client_id
primary_key [:order_id,:client_id]
end
models.rb:
DB = Sequel.sqlite('data.sqlite')
require 'create_tables'
class Person < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
class Order < Sequel::Model
unrestrict_primary_key
many_to_many :orders
end
You mentioned that you want to create the tables and associations in a method call, but that doesn't make sense if you are creating classes with constants. The main reason to create them via a method call is to allow for multiple databases at runtime, but that wouldn't work with your model classes since they are defined with constant names.
If you don't need multiple databases at runtime, the example above should work if you just want to separate the table creation from the model creation.
If you do need multiple databases at runtime, then creating the tables and models via a method call makes sense, but you need to create anonymous model classes, as otherwise you will have problems.

Resources