Rails 4 save unusual model - ruby-on-rails

I want to get data from form, then process it in model, and after that save. I don't save data right from form.
I made a model like this:
class NewsParser < ActiveRecord::Base
def initialize a, h
#queries = a
#contents = h
end
Migration:
class CreateNewsParsers < ActiveRecord::Migration
def change
create_table :news_parsers do |t|
t.text :contents
t.text :queries
t.timestamps
end
end
end
And I want to save it to Database. So in controller I write:
class NewsParsersController < ApplicationController
def create
#news_parser = NewsParser.new([1,2,3], {1 => 3})
#news_parser.save
end
private
def news_parser_params
params.require(:news_parser).permit(:a, :h)
end
end
It makes next error on the save line:
undefined method `[]' for nil:NilClass
How can I save my model?

As your method name is news_parser_params,i guess giving this line
#my_model = MyModel.new ([1,2,3], {1 => 3})
as
#my_model = MyModel.new(news_parser_params) should work!
Update
Now change this line back to the original
params.require(:my_model).permit(:a, :h)
to
params.require(:my_model).permit(:queries, :contents)

In your NewsParser model add these two lines
serialize :queries, Array
serialize :contents, Hash
In your controller in news_parser_params method
params.require(:news_parser).permit(:contents => {}, :queries => [])
also here
#news_parser = NewsParser.new(:queries => [1,2,3], :contents => {1 => 3})

I got it.
The first thing, that I have done was to remove initialize method from model, as it was in first message.
class NewsParser < ActiveRecord::Base
def initialize a, h
#queries = a
#contents = h
end
end
Next. There is a quote in "The Rails 4 Way", that helped me to save attribute values.
...there is also a write_attribute method that lets you change attribute values.
class Project < ActiveRecord::Base
# The description for a project cannot be changed to a blank string
def description=(new_value)
write_attribute(:description, new_value) unless new_value.blank?
end
end
So now it looks that way:
class NewsParser < ActiveRecord::Base
serialize :queries, Array
serialize :contents, Hash
def add_query str
#queries ||= []
#queries << CGI.escape(str)
write_attribute(:queries, #queries)
end
end
And permits there don't need at all.

Related

Can I have a way to customize field settings in rails activerecord?

I want to add a array field(named tags) in my sqlite database, so I have done some followings:
# migration
add_column :tags, :string
# controller
def update
tags = params[:tags] # a array from frontend
project.tags = tags.join(',')
project.save!
end
def show
project_hash = project.as_json
project_hash['tags'] = project_hash['tags'].split(',')
render json: project_hash
end
But I want to customize field setting and getting method directyly in active model, which is like below:
# model
def tags=(array)
self.real_tags_column = array.join(',')
end
def tag
self.real_tags_column.split(',')
end
It should work like this:
def tags
self['tags'].split(',')
end
def tags=(array)
self['tags'] = array.join(',')
end
If it doesn't, try read_attribute/write_attribute.
Yes you can use Active record serialize attribute instead of customisation.
# Serialize a preferences attribute.
class User < ActiveRecord::Base
serialize :preferences
end
# Serialize preferences using JSON as coder.
class User < ActiveRecord::Base
serialize :preferences, JSON
end
# Serialize preferences as Hash using YAML coder.
class User < ActiveRecord::Base
serialize :preferences, Hash
end
Please review ActiveRecord::AttributeMethods::Serialization::ClassMethods & how to save array to database in rails

how can we get only newly added object after update using accepts_nested_attributes_for assocation in rails

My association looks like this :
class Abc < ApplicationRecord
has_many :def
accepts_nested_attributes_for :def, allow_destroy: true
end
class AbcController < ApplicationController
def update
abc = Abc.find(params[:id])
if abc.update(abc_params)
# TODO Here update is sucessful but how to get all newly added def in database with their id?
end
end
private
def abc_params
params.require(:abc).permit(def_attributes: [:name, :title,:wordcount, :id])
end
end
We know accepts_nested attributes creates a new nested object in the database so how can I get all the newly added(not already existing) def object with their database id in AbcController update function ?
One solution is (not a direct one)..
def update
abc = Abc.find(params[:id])
number_of_defs = abc.defs.length
if abc.update(abc_params)
number_newly_added_defs = abc.defs.length - number_of_defs
newly_added_defs = abc.defs.last(number_newly_added_defs)
end
end
you will get the desired output.

How to get many sum of child columns?

I have two tables, Member and MemberRecord.
This are their relationship:
# Member Model
class Member < ActiveRecord::Base
has_many :member_records, :dependent => :destroy
end
# MemberRecord Model
class MemberRecord < ActiveRecord::Base
belongs_to :member
end
In MemberRecord There are many columns: two_pointer_attempt, two_pointer_made, three_pointer_attempt, three_pointer_made, free_throws_attempt, free_throws_made, offensive_rebound, defensive_rebound, assist, block, steal, turnover, foul, score
Can I get those columns sum in more efficient way?
This is what I did so far:
class Member < ActiveRecord::Base
belongs_to :team
has_many :member_records, :dependent => :destroy
validates :name, :number, presence: true
validates_uniqueness_of :name, scope: :team_id
validates_inclusion_of :number, in: 0..99
def sum_two_pointer_made
self.member_records.sum(:two_pointer_made)
end
def sum_two_pointer_attempt
self.member_records.sum(:two_pointer_attempt)
end
def sum_two_pointer_total
sum_two_pointer_made + sum_two_pointer_attempt
end
def sum_three_pointer_made
self.member_records.sum(:three_pointer_made)
end
def sum_three_pointer_attempt
self.member_records.sum(:three_pointer_attempt)
end
def sum_three_pointer_total
sum_three_pointer_made + sum_three_pointer_attempt
end
def sum_free_throws_made
self.member_records.sum(:free_throws_made)
end
def sum_free_throws_attempt
self.member_records.sum(:free_throws_attempt)
end
def sum_free_throws_total
sum_free_throws_made + sum_free_throws_attempt
end
def sum_offensive_rebound
self.member_records.sum(:offensive_rebound)
end
def sum_defensive_rebound
self.member_records.sum(:defensive_rebound)
end
def sum_assist
self.member_records.sum(:assist)
end
def sum_block
self.member_records.sum(:block)
end
def sum_steal
self.member_records.sum(:steal)
end
def sum_turnover
self.member_records.sum(:turnover)
end
def sum_foul
self.member_records.sum(:foul)
end
def sum_score
self.member_records.sum(:score)
end
end
I will give you an example with two columns and you can extend it for your number of columns.
class Member < ActiveRecord::Base
# add associations here as already present
MR_SUM_COLUMNS = %w{
assist
block
} # add more member record columns here
MR_SUM_COLUMNS.each do |column|
define_method "member_record_#{column}_sum" do
member_record_sums.send(column)
end
end
private
def member_record_sums
#_member_record_sums ||=
begin
tn = MemberRecord.table_name
sums_str =
MR_SUM_COLUMNS.map do |c|
"SUM(#{tn}.#{c}) AS #{c}"
end.join(', ')
self.member_records.select(sums_str).first
end
end
end
m = Member.first
s1 = m.member_record_assist_sum
s2 = m.member_record_block_sum
Explanation:
In ActiveRecord's select method, you can store the sum of a column as a particular value. For example:
# you have only two members with ids 1 and 2
m = Member.select("SUM(id) AS id_sum").first
m.id_sum #=> 3
So we're storing all sums of member_records in one go: in the member_record_sums method. We are also using an instance variable to store the results so that subsequent calls to the method do not query the database.
From there, all we have to do is define our sum-lookup methods dynamically.

In Rails, what is the inverse of update_attributes?

In Rails, what is the inverse of update_attributes! ?
In other words, what maps a record to an attributes hash that would recreate that record and all of it children records?
The answer is not ActiveRecord.attributes as that will not recurse into child object.
To clarify if you have the following:
class Foo < ActiveRecord::Base
has_many :bars
accepts_nested_attributes_for :bars
end
Then you can pass an hash like
{"name" => "a foo", "bars_attributes" => [{"name" => "a bar} ...]}
to update_attributes. But it's not clear how to easily generate such a hash programatically for this purpose.
EDIT:
As I have mentioned in a comment, I can do something like:
foo.as_json(:include => :bars)
but I wanted a solution that uses the accepts_nested_attributes_for :bars declaration to avoid having to explicitly include associations.
Not sure how that would be the "inverse", but while Rails might not "have the answer" per-see, there is nothing stopping you from traversing through an object and creating this VERY efficiently.
Something to get you started:
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#method-i-accepts_nested_attributes_for
You'll notice in the accepts_nested_attributes_for method, rails sets a hash for all models nested, in nested_attributes_options. So we can use that to get these nested associations, to populate this new hash.
def to_nested_hash
nested_hash = self.attributes.delete_if {|key, value| [:id, :created_at, :deleted_at].include? key.to_sym } # And any other attributes you don't want
associations = self.nested_attributes_options.keys
associations.each do |association|
key = "#{association}_attributes"
nested_hash[key] = []
self.send(association).find_each do |child|
nested_hash[key] << child.attributes.delete_if {|key, value| [:id, :created_at, :deleted_at].include? key.to_sym }
end
end
return nested_hash
end
OR just thought of this:
Using your example above:
foo.as_json(:include => foo.nested_attributes_options.keys)
One thing to note, this won't give you the bars_attributes where my first suggestions will. (neither will serializable_hash)
You can use the following method to include nested options in hash
class Foo < ActiveRecord::Base
has_many :bars
accepts_nested_attributes_for :bars
def to_nested_hash(options = nil)
options ||= {}
if options[:except]
incl = self.nested_attributes_options.keys.map(&:to_s) - Array(options[:except]).map(&:to_s)
else
incl = self.nested_attributes_options.keys
end
options = { :include => incl }.merge(options)
self.serializable_hash(options)
end
end
If for some situations you don't want bars, you can pass options
foo.to_nested_hash(:except => :bars)
Edit: Another option if you want same behaviour in as_json, to_json and to_xml
class Foo < ActiveRecord::Base
has_many :bars
accepts_nested_attributes_for :bars
def serializable_hash(options = nil)
options ||= {}
if options[:except]
incl = self.nested_attributes_options.keys.map(&:to_s) - Array(options[:except]).map(&:to_s)
else
incl = self.nested_attributes_options.keys
end
options = { :include => incl }.merge(options)
super(options)
end
def to_nested_hash(options = nil)
self.serializable_hash(options)
end
end

How do I save a model with this dynamically generated field?

I have a rails model that looks something like this:
class Recipe < ActiveRecord::Base
has_many :ingredients
attr_accessor :ingredients_string
attr_accessible :title, :directions, :ingredients, :ingredients_string
before_save :set_ingredients
def ingredients_string
ingredients.join("\n")
end
private
def set_ingredients
self.ingredients.each { |x| x.destroy }
self.ingredients_string ||= false
if self.ingredients_string
self.ingredients_string.split("\n").each do |x|
ingredient = Ingredient.create(:ingredient_string => x)
self.ingredients << ingredient
end
end
end
end
The idea is that when I create the ingredient from the webpage, I pass in the ingredients_string and let the model sort it all out. Of course, if I am editing an ingredient I need to re-create that string. The bug is basically this: how do I inform the view of the ingredient_string (elegantly) and still check to see if the ingredient_string is defined in the set_ingredients method?
Using these two together are probably causing your issues. Both are trying to define an ingredients_string method that do different things
attr_accessor :ingredients_string
def ingredients_string
ingredients.join("\n")
end
Get rid of the attr_accessor, the before_save, set_ingredients method and define your own ingredients_string= method, something like this:
def ingredients_string=(ingredients)
ingredients.each { |x| x.destroy }
ingredients_string ||= false
if ingredients_string
ingredients_string.split("\n").each do |x|
ingredient = Ingredient.create(:ingredient_string => x)
self.ingredients << ingredient
end
end
end
Note I just borrowed your implementation of set_ingredients. There's probably a more elegant way to break up that string and create/delete Ingredient model associations as needed, but it's late and I can't think of it right now. :)
The previous answer is very good but it could do with a few changes.
def ingredients_string=(text)
ingredients.each { |x| x.destroy }
unless text.blank?
text.split("\n").each do |x|
ingredient = Ingredient.find_or_create_by_ingredient_string(:ingredient_string => x)
self.ingredients
I basically just modified Otto's answer:
class Recipe < ActiveRecord::Base
has_many :ingredients
attr_accessible :title, :directions, :ingredients, :ingredients_string
def ingredients_string=(ingredient_string)
ingredient_string ||= false
if ingredient_string
self.ingredients.each { |x| x.destroy }
unless ingredient_string.blank?
ingredient_string.split("\n").each do |x|
ingredient = Ingredient.create(:ingredient_string => x)
self.ingredients << ingredient
end
end
end
end
def ingredients_string
ingredients.join("\n")
end
end

Resources