I have a model Person and based on a condition I'd like to add a field to it based on a condition.
That is, I'd need an applied field if the condition in the controller is met.
I did #person.applied = true before the method returns, and if I do puts #person.applied, true is logged, as expected. However, if I do puts #person, the applied field is not listed, while all the others (stored in the database) are.
I also have attr_accessor :applied in the model.
What am I doing wrong?
EDIT (snippets)
# people_controller.rb
def show
if #application
#person.applied = true
end
puts #person.applied # logs true
puts #person # applied is not included still
render json: #person
# person.rb
class Person < ApplicationRecord
attr_accessor :applied
end
make this:
# person.rb
class Person < ApplicationRecord
attribute :applied
end
# people_controller.rb
def show
if #application
#person.applied = true
end
p #person.applied # logs true
p #person # applied is not included still
render json: #person
# rest of your code
this works fine with rails 6 and sqlite3
Please note that this is a virtual attribute. you have to set it in the controller actions that you want to use it because it does not persist in the database.
Related
I have an authentication feature that provides me the username of the current user. I also have a table of events (created by users). And when a user creates an event, how could I save a field called host with the current name of this user in the table events?
The concept is called "deserialization", which is why I made my own gem for this: quickery.
Using quickery
class Event < ApplicationRecord
belongs_to: user
quickery { user: { username: :host } }
end
Using Normal-Way
class Event < ApplicationRecord
belongs_to :user
before_save do
host = user.username
end
end
class User < ApplicationRecord
has_many :events
# you may want to comment out this `after_destroy`, if you don't want to cascade when deleted
after_destroy do
events.update_all(host: nil)
end
# you may want to comment out this `after_update`, if you don't want each `event.host` to be updated (automatically) whenever this `user.username` gets updated
after_update do
events.update_all(host: username)
end
end
Usage Example (for either above)
user = User.create!(username: 'foobar')
event = Event.create!(user: user)
puts event.host
# => 'foobar'
Or, if your Event doesn't belongs_to :user, then you'll need to update this manually in the controller as follows
class EventsController < ApplicationController
def create
#event = Event.new
#event.assign_attributes(event_params)
#event.host = current_user.username
if #event.save
# success, do something UPDATE THIS
else
# validation errors, do something UPDATE THIS
end
end
def update
#event = Event.find(params[:id])
#event.assign_attributes(event_params)
#event.host = current_user.username
if #event.save
# success, do something UPDATE THIS
else
# validation errors, do something UPDATE THIS
end
end
private
def event_params
params.require(:event).permit(:someattribute1, :someattribute2) # etc. UPDATE THIS
end
end
I have the code that I need to do all the scraping and then printing the results to the console, but, I am confused about how to use it in an app.
The way it's supposed to work is through the list#new action I take user input for one parameter, :url. This URL is then passed to the scraping code, which obtains all the additional parameters and adds everything to Postgres tables. Using all of this newly acquired data, a new list is rendered.
The questions that I have:
the lists controller:
class UsersController < ApplicationController
.
.
.
def create
#list = List.new ( #what goes in here?
#only one param comes from the user
if #list.save
#how to set it up so that the save is successful
#only if the extra params have been scraped?
.
.
.
I assume this will go into the models/list.rb:
class List < ActiveRecord::Base
require 'open-uri'
url = #assuming that the url is proper and for something this code is supposed to scrape
#is it better to add the url to db first or send it straight from the input
#and how is that defined here
doc = Nokogiri::HTML(open(url))
.
.
.
Could you give me some guidance here, please?
The services file:
class ScrapingService
require 'open-uri'
require 'nokogiri'
def initialize(list)
#list = list
end
url = :url
doc = Nokogiri::HTML(open(url))
name = doc.at_css(".currentverylong").text
author = doc.at_css(".user").text
def scraped_successfully?
if name != nil && author != nil
true
else
false
end
end
private
attr_reader :list
end
Some questions that I have are:
How do I properly introduce :url into HTML(open...? The way I have it now throws no implicit conversion of Symbol into String error.
The part where :url along with :name and :author are supposed to be saved into one db entry is really murky.
Any article suggestions on this stuff are always welcome.
app/controllers/lists_controller.rb
class UsersController < ApplicationController
def create
#list = List.new(list_params)
if #list.save
redirect_to #list
else
render :new
end
private
#Assuming that you are using Rails 4 or the strong_params gem
def list_params
params.require(:list).permit(:url)
end
end
app/models/list.rb
class List < ActiveRecord::Base
# This runs only when you try to create a list. If you want to run this
# validation when the user updates it, the remove the on: :create
before_validation :ensure_website_is_scrapable, on: :create
private
def ensure_website_is_scrapable
if ScrapingService.new(self).scraped_successfully?
true
else
errors.add(:url, 'The website is not scrapable')
end
end
end
app/services/scraping_service.rb
class ScrapingService
def initialize(list)
#list = list
end
def scraped_successfully?
# Do the scraping logic here and return true if it was successful or false otherwise
# Of course split the implementation to smaller methods
end
private
attr_reader :list
end
So i'm working on a sort of custom-rolled history tracking for a RoR application. The part i'm hung up on is getting the logged in users information to tie to the record. I've figured out getting the user, its by a submodule which is attached to the ActionController::Base class. The problem is, I'm having trouble retrieving it from the submodule.
Here is my code:
module Trackable
# This is the submodule
module TrackableExtension
extend ActiveSupport::Concern
attr_accessor :user
included do
before_filter :get_user
end
def get_user
#user ||= current_user # if I log this, it is indeed a User object
end
end
# Automatically call track changes when
# a model is saved
extend ActiveSupport::Concern
included do
after_update :track_changes
after_destroy :track_destroy
after_create :track_create
has_many :lead_histories, :as => :historical
end
### ---------------------------------------------------------------
### Tracking Methods
def track_changes
self.changes.keys.each do |key|
next if %w(created_at updated_at id).include?(key)
history = LeadHistory.new
history.changed_column_name = key
history.previous_value = self.changes[key][0]
history.new_value = self.changes[key][1]
history.historical_type = self.class.to_s
history.historical_id = self.id
history.task_committed = change_task_committed(history)
history.lead = self.lead
# Here is where are trying to access that user.
# #user is nil, how can I fix that??
history.user = #user
history.save
end
end
In my models then its as simple as:
class Lead < ActiveRecord::Base
include Trackable
# other stuff
end
I got this to work by setting a Trackable module variable.
In my TrackableExtension::get_user method I do the following:
def get_user
::Trackable._user = current_user #current_user is the ActionController::Base method I have implemented
end
Then for the Trackable module I added:
class << self
def _user
#_user
end
def _user=(user)
#_user = user
end
end
Then, in any Trackable method I can do a Trackable::_user and it gets the value properly.
Given the following:
class WebsitesController < ApplicationController
# POST /websites/save
# POST /websites/save.json
def save
Website.exists?(name: params[:website][:name]) ? update : create
end
# POST /websites
# POST /websites.json
def create
#server = Server.find_or_create_by_name(params[:server_id])
#website = #server.websites.new(params[:website])
#etc... #website.save
end
# PUT /websites/1
# PUT /websites/1.json
def update
#website = Website.find_by_name(params[:website][:name])
#etc... #website.update_attributes
end
end
The client does not have any IDs of these models
The request that gets sent only has the names, but not the ids.
And the following models
class Website < ActiveRecord::Base
serialize :website_errors
attr_accessible :plugins_attributes
has_many :plugins
accepts_nested_attributes_for :plugins
end
class Plugin < ActiveRecord::Base
belongs_to :website
end
When I make a POST request to /websites/save.json, the Website gets updated correctly if it exists, but the Plugins that belong to it always get recreated causing duplicate content in the Database. Why does this happen? I redirect to the update action which calls update_attributes so how can it be that it does not update it? I take it that it's because no ID is given with the request.
Can I make the Controller listen to plugin_name instead of plugin_id?
Modify your controller to have this:
def update
#website = Website.find_by_name(params[:website][:name])
if #website.update(params)
redirect_to website_path(#website)
else
render :edit
end
end
Also, if you're using strong_parameters, you'll need this at the bottom of your controller:
params.require(:website).
permit(
:name,
...,
plugins_attributes: [
:name,
...,
]
)
end
I am using Rails v2.3
If I have a model:
class car < ActiveRecord::Base
validate :method_1, :method_2, :method_3
...
# custom validation methods
def method_1
...
end
def method_2
...
end
def method_3
...
end
end
As you see above, I have 3 custom validation methods, and I use them for model validation.
If I have another method in this model class which save an new instance of the model like following:
# "flag" here is NOT a DB based attribute
def save_special_car flag
new_car=Car.new(...)
new_car.save #how to skip validation method_2 if flag==true
end
I would like to skip the validation of method_2 in this particular method for saving new car, how to skip the certain validation method?
Update your model to this
class Car < ActiveRecord::Base
# depending on how you deal with mass-assignment
# protection in newer Rails versions,
# you might want to uncomment this line
#
# attr_accessible :skip_method_2
attr_accessor :skip_method_2
validate :method_1, :method_3
validate :method_2, unless: :skip_method_2
private # encapsulation is cool, so we are cool
# custom validation methods
def method_1
# ...
end
def method_2
# ...
end
def method_3
# ...
end
end
Then in your controller put:
def save_special_car
new_car=Car.new(skip_method_2: true)
new_car.save
end
If you're getting :flag via params variable in your controller, you can use
def save_special_car
new_car=Car.new(skip_method_2: params[:flag].present?)
new_car.save
end
The basic usage of conditional validation is:
class Car < ActiveRecord::Base
validate :method_1
validate :method_2, :if => :perform_validation?
validate :method_3, :unless => :skip_validation?
def perform_validation?
# check some condition
end
def skip_validation?
# check some condition
end
# ... actual validation methods omitted
end
Check out the docs for more details.
Adjusting it to your screnario:
class Car < ActiveRecord::Base
validate :method_1, :method_3
validate :method_2, :unless => :flag?
attr_accessor :flag
def flag?
#flag
end
# ... actual validation methods omitted
end
car = Car.new(...)
car.flag = true
car.save
Another technique, which applies more to a migration script than application code, is to redefine the validation method to not do anything:
def save_special_car
new_car=Car.new
new_car.define_singleton_method(:method_2) {}
new_car.save
end
#method_2 is now redefined to do nothing on the instance new_car.
Use block in your validation something like :
validates_presence_of :your_field, :if => lambda{|e| e.your_flag ...your condition}
Depending on weather flag is true of false, use the method save(false) to skip validation.