What is difference between ActiveRecord::Base and ActiveRecord?
And why we have to extend ActiveRecord::Base but not ActiveRecord ?
class User < ActiveRecord::Base
def self.authenticate_unsafely(user_name, password)
where("user_name = '#{user_name}' AND password = '#{password}'").first
end
def self.authenticate_safely(user_name, password)
where("user_name = ? AND password = ?", user_name, password).first
end
def self.authenticate_safely_simply(user_name, password)
where(user_name: user_name, password: password).first
end
end
http://api.rubyonrails.org/classes/ActiveRecord/Base.html
There is a great convention, that when you create a gem, you do not create many top level constants. Instead, you should create a single module, which acts like a namespace for your gem. This is exactly what ActiveRecord module is.
ActiveRecord::Base is one of many components of ActiveRecord - others includes classes like Schema, SchemaDumper Relation, ImmutableRelation Validation, Scoping and many many more. You can see the whole list with ActiveRecord::Base. If creators of ActiveRecord put all of those constants in the main namespace, you would be largely limited in names you could give to your constants without having conflicts, hence the need of the wrapping module. This module also can contain some extra meta data, so at any point you can check the current version of the gem with ActiveRecord.version.
Related
I am using the Pundit gem to role scope my application and have found some difficulties merging ActiveRecord queries. I am working with Rails 5.1.4.
See I have three models, lets say Classroom, Student and Exam with:
Classroom has_many :students & has_many :exams, through: :students
Student belongs_to: :classroom & has_many :exams
Exam belongs_to: :student
Each have a policy scope with varying queries that I want to merge together to ensure a user has access to the Exam model that's within the Student scope, that's also within the Classroom scope.
Pundit let us do that by passing whatever ActiveRecord relationship from the controller to the Scope class inside a scope variable.
With that in mind, my scope classes appear as follow:
class ClassroomPolicy::Scope
def resolve
if user.reviewer?
# A reviewer can only see specific classes
scope.where(type: :exam).where(reviewer_id: user.id)
end
end
end
class StudentPolicy::Scope
def resolve
if user.reviewer?
# A reviewer can only see students from classes he's allowed in
scope.joins(:classroom).merge(policy_scope(Classroom))
end
end
end
class ExamPolicy::Scope
def resolve
if user.reviewer?
# A reviewer can only see/grade exams from students he supervised
scope.joins(:student).merge(policy_scope(Student))
end
end
end
All works well, if I do policy_scope(Exam), I do get all exams from students who where inside a reviewer's classroom.
The issue arises when passing an already joined query such as policy_scope(Classroom.find(params[:classroom_id]).exams).
ActiveRecord effectively generates a strange query which is unusable because both a_classroom.exams and our scope do a join on :student.
In addition, removing joins from our exam scope, giving us scope.merge(policy_scope(Student)), makes our call policy_scope(Classroom.find(params[:classroom_id]).exams) work while breaking policy_scope(Exam).
Is there a way to work around this so that both use cases work ?
Or is my approach to Pundit wrong ?
Is this the limit of ActiveRecord ?
Any help regarding this would be appreciated !
policy_scope is intended to be used from Controllers. For e.g. refer the examples given in section https://github.com/varvet/pundit#scopes and you should find that this method is being demonstrated to be used either from controllers or views.
However as per your code you are trying to use policy_scope in your scope classes (i.e classes extending ApplicationPolicy::Scope class) themselves. And I think I am understanding the intention behind doing so to reuse the already defined scopes. But in my opinion that's a wrong way to think about it.
Once inside YourScopeClass#resolve method you should use normal querying mechanism like you do elsewhere in your Rails application.
I don't have a complete know-how of the fields you have in your model. But based on the comments you have added in resolve method for a reviewer, you should do something like:
class StudentPolicy::Scope
def resolve
if user.reviewer?
# A reviewer can only see students from classes he's allowed in
arel = scope.joins(:classroom)
arel = arel.where(classrooms: { type: : exam, reviewer_id: user.id })
arel
end
end
end
Then use it from controller like policy_scope(Student)
Similarly
class ExamPolicy::Scope
def resolve
if user.reviewer?
# A reviewer can only see/grade exams from students he supervised
arel = arel.joins(student: [ :classroom ])
arel = arel.where(classrooms: { type: : exam, reviewer_id: user.id })
arel
end
end
end
Then use it from controller like policy_scope(Exam)
And if you want to reuse some queries then try creating some generic methods which can be passed the query represented by Pundit Policy Scope.
For e.g.
class MyClass
class << self
def accessible_students(arel:, user_id:)
arel.where(classrooms: { type: : exam, reviewer_id: user_id })
end
end
end
class StudentPolicy::Scope
def resolve
if user.reviewer?
# A reviewer can only see students from classes he's allowed in
arel = scope.joins(:classroom)
arel = MyClass.accessible_students(arel: arel, user_id: user.id)
arel
end
end
end
class ExamPolicy::Scope
def resolve
if user.reviewer?
# A reviewer can only see/grade exams from students he supervised
arel = arel.joins(student: [ :classroom ])
arel = MyClass.accessible_students(arel: arel, user_id: user.id)
arel
end
end
end
I hope you find this helpful.
I have an Audit class which is backed by ActiveRecord.
class Audit < ActiveRecord::Base
belongs_to :user, polymorphic: true
end
I have a User class which is a plain ruby object with some ActiveModel functionality included. It's not a database model because my users are actually stored in a different database and served over an API.
class User
include ActiveModel::Conversion
include ActiveModel::Validations
extend ActiveModel::Naming
def self.primary_key
'id'
end
def id
42
end
def persisted?
false
end
end
I'm trying to assign a user to an audit like this:
audit = Audit.new
audit.user = User.new
audit.save!
From a data perspective, this should work ok. To define a polymorphic association, we need to put values into two columns on the audits table. We can set audits.user_id to the value 42 and audits.user_type to the string "User".
However, I hit an exception:
undefined method `_read_attribute' for #<User:0x007f85faa49520 #attributes={"id"=>42}> (NoMethodError)
active_record/associations/belongs_to_polymorphic_association.rb:13:in `replace_keys'
I traced that back to the ActiveRecord source and it seems to be defined here. Unfortunately, the fact that it's ActiveRecord rather than ActiveModel means that I can't include that mixin into my class.
I tried defining my own _read_attribute method but I go down a rabbit hole of having to redefine more and more ActiveRecord functionality like AttributeSet and so on.
I also realise that I can workaround the problem by assigning Audit#user_type and Audit#user_id. That is unsatisfactory however because, in reality, I would have to fork a gem and edit it to do that.
How can I modify my User class so that I can cleanly assign it to an audit.
P.S. Here's a sample app so you can try this yourself.
Instead of hacking deeper and deeper to replicate ActiveRecord functionality, you may want to consider actually inheriting from ActiveRecord::Base instead of including ActiveModel. Your only constraint is that you don't have a table. There's a gem for that:
activerecord-tableless
This class works with your sample app:
require 'active_record'
require 'activerecord-tableless'
class User < ActiveRecord::Base
has_no_table
# required so ActiveRecord doesn't try to create a new associated
# User record with the audit
def new_record?
false
end
def id
42
end
end
I have a multi domain app talking to a legacy database.
In that DB I have two tables with different names, lets call them USER_A and USER_B. Their structure and data types are exactly the same, the only difference is that they get their data from different domains.
Now, I would like to have a single scaffold (model/controller/view) that, depending on the domain, maps to the right DB table.
Domain A would work with a model/controller called User which maps internally to the db table USER_A, and Domain B would work with the same model/controller User but maps to the table USER_B.
I would also like to use resource :user in my routes to access the model the rails way.
So somehow I need to overwrite the model on initialization but I am not quite sure how to go about it.
How would one go about this using Rails ActiveRecord?
I don't have a multitable DB ready to test with, so this is an educated guess at the solution:
# respective models
class User < ActiveRecord::Base
end
class DomainAUser < User
self.table_name = "USER_A"
end
class DomainBUser < User
self.table_name = "USER_B"
end
# controller
def set_user
#user = if request.subdomain(0) == "DomainA"
DomainAUser.find(params[:id])
else
DomainBUser.find(params[:id])
end
end
Edit: Here's an alternative bit of metaprogramming hackery which does the subclass instantization within the parent class itself. Tested and working.
I really wouldn't want to maintain something like this though.
# model
class User < ActiveRecord::Base
def self.for_domain(domain_suffix)
class_eval "class DomainUser < User; self.table_name='user_#{domain_suffix}'; end"
"User::DomainUser".constantize
end
end
# controller
User.for_domain("a").new
Acutally i face some hard exercises in computer science (hard for me i think, haha).
We're doing some basic stuff with Ruby on Rails an i have to open a csv file to get additional information on my 'User' model which is a normal rails scaffold.
So at the moment i open the csv file in my users_controller.rb file and search for the right row an add them to an instance variable.
But i wonder if i can write a class that acts like an ActiveRecord Model. So i change the code to use ActiveModel. But as i read in some google results, ActiveModel can't make use of ActiveRecord like associations. But it would great to have them.
So i hope you can help me. How can i provide my model with ActiveRecors like associations?
Greetings
Melanie
It's absolutely right that the CSV file should be represented as a model, as it's data.
However, trying to incorporate Active Model sounds tricky and would almost certainly require a great deal of hacking or monkey patching.
Unless you really need associations to other models, I would create a standalone class (i.e. not inheriting from ActiveRecord::Base) in the models directory, and put the logic for parsing the CSV in there:
class User
attr_accessor :name, :email, ...
def initialize(name,email,...)
# set data
end
def self.find(param_for_search)
# Parse CSV file, find line you want
# return a User instance
self.new(name, email)
end
end
I don't know exactly how your system works, but this way you can make it behave in a similar way to Active Model stuff. You can add similar class methods and each instance method represents a CSV file row.
Every time , when you are creating your own model , it is inheritance of ActiveRecord :
class Project < ActiveRecord::Base
attr_accessible :content, :name, :user
end
Then you can tell your model to have many (let's say) Project's Tasks , which creates an association . Please , provide an example of your app's logic.
Here is a quote from RailsCasts.com :
"In Rails 3 the non-database functionality of Active Record is extracted out into Active Model. This allows you to cleanly add validations and other features to tableless models."
There is also a nice description how to add functionality in you model by adding modules .
I understand, that using ActiveRecord to use an non database source is difficult, but i think it would be vewy charming if i could write something like this:
user.worktimes.first.value
in my view and get the information like it is a database table. I visit railscast.com an i found a episode where this ist discussed. But i would like to digg deeper in this. Are there any further ressources i could read?
As i understand, ActiveModel does not support associations? I wonder why associations wasn't moved to ActiveModel as it is a very useful thing. :)
So here is my code, that i was working on:
User-Model:
class User < ActiveRecord::Base
attr_accessible :department_id, :name
belongs_to :department
end
Department-Model:
class Department < ActiveRecord::Base
attr_accessible :name
has_many :users
end
And here is my CSV Model, that i created:
class Worktime
attr_accessor :user_id,:date,:value
def initialize(params)
dir = Rails.root.join('app', 'models', 'worktimes.csv').to_s
source = File.open(dir,'r')
while(line=source.gets)
data = line.split(';')
if data[0] = params[:user_id] && data[1] = params[:date]
#value = data[2]
end
end
end
end
I am very thankful for your help as its my first time using rails.
I have been researching on the best approach for my problem which I originally had implemented as a single table inheritance but am deeply concerned about the scalability, as potentially will have thousands of columns in the table.
So the problem is I would like to have products which the methods of each are exactly the same the only difference being the attributes each one contains. It seems that in this situation that mutli-class inheritance (not supported natively in rails?) would be the best approach or some sort of polymorphic associations.
I want to work towards the following
#product.rb
Class Product < ActiveRecord::Base
attr_accessible :title .....
def to_s # some arbitrary method used by all extending classes
....
end
end
#book.rb
class Book < Product
attr_accessible :author...
end
So I want the book to inherit the methods from product and not for the product to know about the attributes required by each subclass. And if possible get all of the products through one query.
I need to know the best way of approaching this, and if I am doing it completely wrong, please note the code written above is just for example to simplify my problem.
What you can do is create a module and include it in several different models.
First, create a file in your lib directory
i.e.) my_module.rb
module MyModule
def full_name
"#{first_name} #{last_name}"
end
end
Then, make sure the module is loaded when your Rails App starts:
In config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Finally, include it in your models:
i.e.) app/models/thing.rb
class Thing < ActiveRecord::Base
attr_accessible :first_name, :last_name
include AdditionMod
end
You can test it in the console:
#thing = Thing.create(first_name: "Awesome", last_name: "Module")
#thing.full_name
=> "Awesome Module"
Found out that I can use H-store in conjunction with postgres that allows me to have a column that contains a schema less hash that can be used with the power of postgres (for an example take a look at http://hstoredemo.herokuapp.com/)