I am using Ruby on Rails 3 and I would like to inizialize an ActiveRecord Tableless Model.
In my model I have:
class Account < ActiveRecord::Base
# The following ActiveRecord Tableless Model statement is from http://codetunes.com/2008/07/20/tableless-models-in-rails/
def self.columns()
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
attr_reader :id,
:firstname,
:lastname,
def initialize(attributes = {})
#id = attributes[:id]
#firstname = attributes[:firstname]
#lastname = attributes[:lastname]
end
end
If in a controller, for example in the application_controller.rb file, I do:
#new_account = Account.new({:id => "1", :firstname => "Test name", :lastname => "Test lastname"})
a debug\inspect output of the #new_account variable is
"#<Account >"
Why? How I should inizialize properly that ActiveRecord Tableless Model and make it to work?
According to that blog post it would have to look like this:
class Account < ActiveRecord::Base
class_inheritable_accessor :columns
def self.columns()
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
column :id, :integer
column :firstname, :string
column :lastname, :string
end
And then:
#new_account = Account.new({:id => "1", :firstname => "Test name", :lastname => "Test lastname"})
Did you already try it like that?
I my view, you don't need to extend ActiveRecord::Base class.
You can write your own model class something like this
# models/letter.rb
class Letter
attr_reader :char
def self.all
('A'..'Z').map { |c| new(c) }
end
def self.find(param)
all.detect { |l| l.to_param == param } || raise(ActiveRecord::RecordNotFound)
end
def initialize(char)
#char = char
end
def to_param
#char.downcase
end
def products
Product.find(:all, :conditions => ["name LIKE ?", #char + '%'], :order => "name")
end
end
# letters_controller.rb
def index
#letters = Letter.all
end
def show
#letter = Letter.find(params[:id])
end
I hope it will help you.
Reference: http://railscasts.com/episodes/121-non-active-record-model
Related
I'm receiving a JSON object and nested array via a Rails 4 api with params like so:
{
"token" => "123"
"lessons" => [
{
"token_id" => "j12l3n123",
"attr_1" => "hello",
"attr_2" = "is it me you're looking for"
},
{
"token_id" => "j12l",
"attr_1" => "Nope",
"attr_3" = "You're not."
}
]
}
And I have a controller like so:
def update_all
#fetch collection with one db hit
token_ids = params[:lessons].map{|l| l[:token_id]}
#lessons = Lesson.where(token_id: token_ids)
params[:lessons].each do |l|
lesson = #lessons.detect { |lesson| lesson.token_id == l[:token_id] }
# How do I update the record with strong params?
lesson.update_attributes(lesson_params)
end
end
private
def lesson_params
params.permit(
:attr_1,
:attr_2,
:attr_3
)
end
How do i update each record with the right object in the array, and use strong parameters to do so?
def update_all
lesson_params.each do |l|
lesson = Lesson.where(token_id: l[:token_id]).first
lesson.update_attributes(l)
end
end
private
def lesson_params
params.require(:lessons).map do |l|
ActionController::Parameters.new(l.to_hash).permit(
:attr_1,
:attr_2,
:attr_3
)
end
end
def lesson_params
params.permit(:token, lessons: [:token_id, :attr_1, :attr_2, :attr_3 ])
end
in Controller something like following
def update_all
lesson_params[:lessons].each do |lesson_param|
lesson = Lesson.find(lesson_param[:token_id])
lesson.update_attributes(lesson_param)
end
end
I'm working on my Rails API. Currently I want to save an Object through post-requests from my Device. The requests are working fine, but there is a problem saving it to the DB. Rails says:
ActiveRecord::UnknownAttributeError (unknown attribute 'idea_id' for IdeaTag.):
app/controllers/data_controller.rb:42:in `update_ideas'
So, I know this means it can't find the attribute "idea_id" in "IdeaTag".
Heres my data_controller.rb
class DataController < ApplicationController
skip_before_filter :verify_authenticity_token
def token
name=params[:owner]
password=params[:password]
#owner = Owner.authenticate_by_name(name, password)
if #owner
if #owner.user_uuid.blank?
#user = User.new
#user.token = SecureRandom.uuid
#user.name = #owner.name
#user.owner_uuid = #owner.uuid
#user.created_at = Time.now
#user.updated_at = Time.now
#user.isLoggedIn = false
#user.save!
end
#user = User.find_by_uuid(#owner.user_uuid)
if #user.token.blank?
token = SecureRandom.uuid
#user.token = token
#user.save
end
else
render nothing: true, status: :unauthorized
end
end
def update_ideas
uuid = params[:uuid]
text = params[:text]
title = params[:title]
owner_uuid = params[:owner_uuid]
tag_id_1 = params[:tag_id_1]
tag_id_2 = params[:tag_id_2]
tag_id_3 = params[:tag_id_3]
tag_id_4 = params[:tag_id_4]
updated_at = params[:timeStamp]
#idea = Idea.new(:uuid => uuid, :text => text, :title => title, :owner_uuid => owner_uuid, :tag_ids => [tag_id_1, tag_id_2, tag_id_3, tag_id_4], :updated_at => updated_at)
#idea.save!
render json: {:status => 200}
end
def getjson
token = params[:token]
#user = User.find_by_token(token)
#users = User.all
#owner = Owner.find_by_user_uuid(#user.uuid)
#Owners = Owner.all
ownerUuid = #owner.uuid
#tags=Tag.all
#ideas=Idea.where(:owner_uuid => ownerUuid)
#votes=Vote.all
#votings=Voting.all
end
# def token_auth
# token = params[:token]
# #owner = Owner.find_by_token(token)
# if #owner
# update_ideas
# end
# end
end
the error happens in method "update_ideas" the following line
#idea = Idea.new(:uuid => uuid, :text => te...
Idea Model:
class Idea < ActiveRecord::Base
self.primary_key = :uuid
has_many :idea_tags
has_many :tags, through: :idea_tags
belongs_to :voting
has_many :votes
belongs_to :owner
end
Idea migration file
class CreateIdeas < ActiveRecord::Migration
def change
create_table :ideas, :id => false, :primary_key => :uuid do |i|
i.string :uuid
i.string :title
i.string :text
i.string :owner_uuid
i.string :voting_uuid
i.datetime :created_at
i.datetime :updated_at
end
end
end
How do i save Objects like this proper?
Since you are using uuid as primary key for ideas, I'm guessing you have idea_uuid field in IdeaTag? If yes, you need to add foreign_key: 'idea_uuid to has_many :idea_tags, otherwise it will by default assume foreign_key is idea_id . You might have to add it to belongs_to methods as well.
has_many :idea_tags, foreign_key: 'idea_uuid'
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Is there a clean way to destroy all children NOT included in array of passed nested attributes?
Now I have to find difference between actual children and nested attributes array, and then set _destroy: true for each, but it looks ugly.
class Report < ActiveRecord::Base
has_many :consumed_products
accepts_nested_attributes_for :consumed_products, allow_destroy: true
def nested_attributes_destroy_difference(attrs)
combined = attrs.reduce({}) {|h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h}
diff = consumed_products - consumed_products.where(combined)
attrs + diff.map{|i| {id: i.id, _destroy: true} }
end
end
class Api::V2::ReportsController < Api::V2::BaseController
def update
report = Report.find(params[:id])
report_attributes = report_params
if params[:consumed_products]
report_attributes.merge!(consumed_products_attributes: report.nested_attributes_destroy_difference(consumed_products_attributes))
end
report.assign_attributes report_attributes
end
private
def consumed_products_attributes
params[:consumed_products].map do |p|
{product_id: p[:id], product_measure_id: p[:measure_id], quantity: p[:quantity]}
end
end
def report_params
#...
end
end
I have created a simple before_validation:
before_validation :strip_tabs
def strip_tabs
end
In my class I want to loop through all my attributes and remove tabs from each value. Most posts I found on SO are people who want to set 1 attribute. But I want to edit all my values.
Question:
How can I loop all self attributes of a model and edit them.
Friend suggested this, but content_column_names does not exist:
self.content_column_names.each {|n| self[n] = self[n].squish}
UPDATE 1: More code:
class PersonalInfo
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
extend ActiveModel::Translation
extend ActiveModel::Callbacks
include Sappable
require 'ext/string'
attr_accessor \
:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang
validates :e_mail, :email => {:strict_mode => true}
validate :validate_telephone_number
validate :age_is_min_17?
before_validation :strip_tabs
def strip_tabs
binding.remote_pry
end
def age_is_min_17?
birthdate_visible = PersonalField.not_hidden.find_by_name "BIRTHDATE"
if birthdate_visible && birthdate && birthdate > (Date.current - 17.years)
#errors.add(:birthdate, I18n.t("apply.errors.birthdate"))
end
end
def validate_telephone_number
telephone_visible = PersonalField.not_hidden.find_by_name "TELEPHONE"
telephone_current = telephone.dup
if telephone_visible && telephone_current && !telephone_current.empty?
if telephone_current[0] == '+' || telephone_current[0] == '0'
telephone_current[0] = ''
#errors.add(:telephone, I18n.t("apply.errors.telephone")) if !telephone_current.is_number?
else
#errors.add(:telephone, I18n.t("apply.errors.telephone"))
end
end
end
def initialize(hash)
simple_attributes = [:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang]
simple_attributes.each do |attr|
set_attr_from_json(attr, hash)
end
set_attr_from_json(:birthdate, hash) {|date| Date.parse(date) rescue nil}
end
end
Update 2: Rails Version:
I'm using Rails '3.2.17'
You can do as following:
before_validation :strip_tabs
def strip_tabs
self.attributes.map do |column, value|
self[column] = value.squish.presence
end
end
But I think that .squish will not work on created_at, updated_at, id, ... Because they are not String!
def strip_tabs
self.attributes.map do |column, value|
self[column] = value.kind_of?(String) ? value.squish.presence : value
end
end
Since your class is not a Rails model (ActiveRecord::Base), you can do as following:
def strip_tabs
self.instance_variables.map do |attr|
value = self.instance_variable_get(attr)
value = value.squish if value.kind_of?(String)
self.instance_variable_set(attr, value)
end
end
This should work
def strip_tabs
self.attributes.each do |attr_name, attr_value|
modified_value = ... # calculate your modified value here
self.write_attribute attr_name, modified_value
end
end
Because it's not an ActiveRecord model you won't have attributes or column_names, but you already have an array of your attribute names in your initialize function. I would suggest making that into a constant so you can access it throughout the model:
class PersonalInfo
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
extend ActiveModel::Translation
extend ActiveModel::Callbacks
include Sappable
require 'ext/string'
SIMPLE_ATTRIBUTES = [:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang]
attr_accessor *SIMPLE_ATTRIBUTES
before_validation :strip_tabs
def strip_tabs
SIMPLE_ATTRIBUTES.each{ |attr| self[attr] = self[attr].squish }
end
...
def initialize(hash)
SIMPLE_ATTRIBUTES.each do |attr|
set_attr_from_json(attr, hash)
end
set_attr_from_json(:birthdate, hash) {|date| Date.parse(date) rescue nil}
end
end
I'm trying to create a form using textarea and a submit button that will allow users to do bulk insert. For example, the input would look like this:
0001;MR A
0002;MR B
The result would look like this:
mysql> select * from members;
+------+------+------+
| id | no | name |
+------+------+------+
| 1 | 0001 | MR A |
+------+------+------+
| 2 | 0002 | MR B |
+------+------+------+
I'm very new to Rails and I'm not sure on how to proceed with this one. Should I use attr_accessor? How do I handle failed validations in the form view? Is there any example? Thanks in advance.
Update
Based on MissingHandle's comment, I created a Scaffold and replace the Model's code with this:
class MemberBulk < ActiveRecord::Base
attr_accessor :member
def self.columns
#columsn ||= []
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
column :data, :text
validates :data, :create_members, :presence => true
def create_members
rows = self.data.split("\r\n")
#member = Array.new
rows.each_with_index { |row, i|
rows[i] = row.strip
cols = row.split(";")
p = Member.new
p.no = cols[0]
p.name = cols[1]
if p.valid?
member << p
else
p.errors.map { |k, v| errors.add(:data, "\"#{row}\" #{v}") }
end
}
end
def create_or_update
member.each { |p|
p.save
}
end
end
I know the code is far from complete, but I need to know is this the correct way to do it?
class MemberBulk < ActiveRecord::Base
#Tells Rails this is not actually tied to a database table
# or is it self.abstract_class = true
# or #abstract_class = true
# ?
abstract_class = true
# members holds array of members to be saved
# submitted_text is the data submitted in the form for a bulk update
attr_accessor :members, :submitted_text
attr_accessible :submitted_text
before_validation :build_members_from_text
def build_members_from_text
self.members = []
submitted_text.each_line("\r\n") do |member_as_text|
member_as_array = member_as_text.split(";")
self.members << Member.new(:number => member_as_array[0], :name => member_as_array[1])
end
end
def valid?
self.members.all?{ |m| m.valid? }
end
def save
self.members.all?{ |m| m.save }
end
end
class Member < ActiveRecord::Base
validates :number, :presence => true, :numericality => true
validates :name, :presence => true
end
So, in this code, members is an array that is a collection of the individual Member objects. And my thinking is that as much as possible, you want to hand off work to the Member class, as it is the class that will actually be tied to a database table, and on which you can expect standard rails model behavior. In order to accomplish this, I override two methods common to all ActiveRecord models: save and valid. A MemberBulk will only be valid if all it's members are valid and it will only count as saved if all of it's members are saved. You should probably also override the errors method to return the errors of it's underlying members, possibly with an indication of which one it is in the submitted text.
In the end I had to change from using Abstract Class to Active Model (not sure why, but it stoppped working the moment I upgrade to Rails v3.1). Here's the working code:
class MemberBulk
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :input, :data
validates :input, presence: true
def initialize(attributes = {})no
attributes.each do |name, value|
send("#{name}=", value) if respond_to?("#{name}=")
end
end
def persisted?
false
end
def save
unless self.valid?
return false
end
data = Array.new
# Check for spaces
input.strip.split("\r\n").each do |i|
if i.strip.empty?
errors.add(:input, "There shouldn't be any empty lines")
end
no, nama = i.strip.split(";")
if no.nil? or nama.nil?
errors.add(:input, "#{i} doesn't have no or name")
else
no.strip!
nama.strip!
if no.empty? or nama.empty?
errors.add(:input, "#{i} doesn't have no or name")
end
end
p = Member.new(no: no, nama: nama)
if p.valid?
data << p
else
p.errors.full_messages.each do |error|
errors.add(:input, "\"#{i}\": #{error}")
end
end
end # input.strip
if errors.empty?
if data.any?
begin
data.each do |d|
d.save
end
rescue Exception => e
raise ActiveRecord::Rollback
end
else
errors.add(:input, "No data to be processed")
return false
end
else
return false
end
end # def
end